panic仅用于程序无法继续的致命错误,如空指针、切片越界;常规错误应返回error,recover仅在中间件等边界位置集中使用以防止崩溃。

panic 是 Go 里的紧急出口,不是错误处理流程
Go 不把 panic 当作常规错误分支来用,它只该用于**程序无法继续运行的致命状态**,比如空指针解引用、切片越界、向已关闭 channel 发送数据。把它当 try/catch 用,等于把消防栓当水龙头拧——能出水,但系统迟早漏水漏到停摆。
常见错误现象:panic: runtime error: index out of range 被包在业务逻辑里反复 recover;或者 http.HandlerFunc 里对用户输入校验失败就 panic,结果整个 HTTP server goroutine 挂掉又没日志。
- 真正该 panic 的场景:初始化阶段依赖项缺失(如 config 文件读不到且无默认值)、全局 mutex 错误调用(
sync.Mutex.Unlock()在未 lock 状态下被调)、断言失败且不可能发生(v := i.(string)前没做类型判断) - 不该 panic 的场景:数据库查询返回
sql.ErrNoRows、JSON 解析失败、用户传了非法参数、文件不存在(除非是启动必需配置) - recover 不是兜底万能药:它只能捕获当前 goroutine 的 panic,且必须在 defer 中调用;跨 goroutine panic 会直接终止进程
error 返回才是 Go 的标准错误流
绝大多数函数该返回 error 类型,由调用方决定怎么处理——重试、记录、转换、向上透传。这和 panic 的“立刻中断+栈展开”有本质区别:前者可控、可组合、可测试;后者破坏控制流、掩盖真实错误上下文。
使用场景对比:
立即学习“go语言免费学习笔记(深入)”;
- 读取配置文件:
os.ReadFile("config.yaml")返回error,上层可以 fallback 到默认值或打印友好提示;若这里panic,服务根本起不来,运维连错在哪都不知道 - HTTP handler 中解析 query 参数:
strconv.Atoi(r.URL.Query().Get("id"))失败应返回 400 和 JSON 错误体;panic会导致连接被意外关闭,客户端收不到任何响应 - 数据库操作:
db.QueryRow(...).Scan(&u.ID)返回sql.ErrNoRows是正常业务分支,不是 bug;强行panic会让所有“查无此人”的请求都触发服务端崩溃
recover 只应在极少数边界位置集中使用
recover 不是 error 处理的替代品,而是为防止 panic 波及整个程序而设的“安全网”。它只该出现在明确知道可能 panic、且有能力恢复并给出合理响应的位置,比如 HTTP 中间件、RPC 服务入口、长期运行的 goroutine 主循环。
实操建议:
- 不要在每个函数里写
defer func(){ if r := recover(); r != nil { ... } }()—— 这既冗余又掩盖问题 - 中间件中 recover 示例(精简):
func recoverMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf("Panic recovered: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } - 注意:recover 后无法“继续执行原逻辑”,只能做清理和响应;且 recover 不会捕获由
os.Exit()或信号终止引起的退出
容易被忽略的 panic 触发点
有些 panic 看似隐蔽,实际在日常编码中高频出现,而且往往不报错直到特定数据进来才崩。
-
nil指针解引用:var s *string; fmt.Println(*s)→panic: runtime error: invalid memory address or nil pointer dereference - map 写入未初始化:
var m map[string]int; m["k"] = 1→panic: assignment to entry in nil map - channel 关闭后发送:
c := make(chan int, 1); close(c); c → <code>panic: send on closed channel - 类型断言失败:
i := interface{}(42); s := i.(string)→panic: interface conversion: interface {} is int, not string(要用s, ok := i.(string)避免)
这些都不是“异常”,而是代码逻辑缺陷。靠 recover 掩盖,不如用静态检查(如 go vet)、单元测试覆盖边界值、以及 IDE 提示提前拦截。










