Go错误处理核心是显式处理、保留错误链、避免panic滥用:必须检查err并分流,用%w封装上下文,defer前确保resp非空,遵循“error is value”哲学。

Go 的错误处理不是“加个 if err != nil 就完事”,真正踩坑的地方,往往藏在错误被忽略、被掩盖、被错误传播的瞬间。
不检查返回的 err,或检查后不处理
这是最基础也最致命的误区。Go 明确要求调用者显式处理错误,但新手常写成:
file, _ := os.Open("config.json") // 直接丢弃 err
json.NewDecoder(file).Decode(&cfg) // file 可能是 nil,panic 风险
一旦 os.Open 失败,file 为 nil,后续操作直接 panic。正确做法必须检查并分流:
- 立即返回错误(如
return nil, err),让上层决定是否重试或降级 - 记录日志并返回(避免静默失败)
- 仅在极少数明确可忽略的场景(如清理临时文件失败)才用
_,且需注释说明原因
用 panic 替代错误返回
把本该由业务逻辑处理的可恢复错误(如参数校验失败、HTTP 400、数据库约束冲突)扔给 panic,会导致:
- HTTP handler 中 panic 未被 recover → 连接中断、日志丢失、监控失真
- goroutine 崩溃无法追踪上下文(比如哪个请求、哪个用户触发)
- 与 Go “error is value” 的设计哲学背道而驰
除非是启动阶段强依赖不可用(如配置加载失败、端口被占),否则一律用 error 返回。HTTP handler 中应统一用中间件 recover panic 并转为 500 响应,而非主动 panic。
错误链断裂:没用 fmt.Errorf 或 errors.Join 封装
底层函数返回了清晰错误(如 "no such file"),但上层只简单返回 err,丢失调用路径信息:
func LoadConfig() error {
f, err := os.Open("config.yaml")
if err != nil {
return err // ❌ 丢失 "LoadConfig called here" 上下文
}
defer f.Close()
return yaml.NewDecoder(f).Decode(&cfg)
}
应该用带上下文的封装:
-
return fmt.Errorf("load config: %w", err)—— 保留原始错误并添加前缀 -
return errors.Join(err1, err2)—— 合并多个并行错误(如批量调用) - 避免
fmt.Errorf("load config: %s", err.Error())—— 破坏错误链,errors.Is/As失效
defer resp.Body.Close() 前不判空
HTTP 客户端错误处理中高频雷区:
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close() // ❌ 若 err != nil,resp 可能为 nil,panic
必须确保 resp 非空才 defer 关闭:
- 写成
if err != nil { return err }后再defer resp.Body.Close() - 或更安全地:先判
resp,再 defer(尤其在自定义http.Client场景) - 永远记得:所有
io.ReadCloser(包括resp.Body)都必须关闭,否则连接泄露
错误处理最难的部分,从来不是语法,而是判断“这个错误到底该谁负责、该不该继续、该不该暴露给用户”。多一层 fmt.Errorf,少一次 panic,晚一秒 defer——这些细节能让线上问题从“查三天”变成“一眼定位”。










