Go错误处理无最佳实践,只有底线规范:须主动检查error、明确传播路径、禁用静默吞错;errors.New适用于静态常量错误,fmt.Errorf(优先%w)用于带上下文的运行时错误;忽略err!=nil后return将致逻辑失效;errors.Is/As仅跨层分类时有效,自定义错误须实现Unwrap。

Go 的错误处理没有“最佳实践”这回事,只有“不踩坑”的底线规范。 它不是靠 try-catch 框架兜底,而是靠开发者主动检查 error 值、明确传播路径、避免静默吞错。用错方式,轻则日志查不到根因,重则服务持续返回 200 却逻辑失效。
什么时候该用 errors.New,什么时候该用 fmt.Errorf
errors.New 只适合无上下文的静态错误(比如预定义的协议错误码),而绝大多数真实场景需要携带运行时信息——这时必须用 fmt.Errorf,且优先用带格式动词的写法,而不是字符串拼接。
-
errors.New("invalid user id"):适用于常量级错误,如ErrNotFound、ErrPermissionDenied,可导出并复用 -
fmt.Errorf("failed to parse timestamp %q: %w", input, err):保留原始错误链,用%w包装,便于后续用errors.Is或errors.As判断 - 避免
fmt.Errorf("failed: " + err.Error()):丢失原始类型、无法 unwarp、破坏错误语义
为什么不能忽略 err != nil 后直接 return
忽略检查或检查后不处理(比如只打日志却不返回),会让调用方误以为操作成功。Go 不强制你处理 error,但它的设计哲学是“显式即安全”。一旦漏掉,上游可能基于错误状态继续执行,导致数据不一致或 panic。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 常见错误:在 HTTP handler 中
json.Unmarshal(req.Body, &v); log.Println(err)后继续往下走 —— 此时v是零值,但 handler 仍尝试保存 - 正确做法:检查后立即
return,或明确转换为业务响应(如http.Error(w, "bad json", http.StatusBadRequest)) - 工具辅助:启用
govet -tests和errcheck静态检查,能抓到大量漏检 case
errors.Is 和 errors.As 的实际使用边界
这两个函数不是为了“优雅地判断错误”,而是为了解耦错误构造与错误消费。它们只在你需要跨包、跨层做错误分类时才真正有用;简单单层逻辑里硬套反而增加理解成本。
立即学习“go语言免费学习笔记(深入)”;
-
errors.Is(err, fs.ErrNotExist):判断是否是标准库定义的特定错误,适用于文件、网络等底层 IO 场景 -
errors.As(err, &target):当你要提取自定义错误结构体字段(比如type ValidationError struct { Field string; Msg string })时才用 - 不要对任意
error都套errors.Is(err, errors.New("xxx")):每次调用都新建一个 error 实例,Is必然失败 - 自定义错误类型务必实现
Unwrap() error方法,否则%w包装后无法被Is/As追踪
最常被绕过的其实是错误的**责任归属**:谁创建错误、谁记录日志、谁向用户暴露、谁负责重试——这些不是语言机制决定的,而是团队协作契约。一个 error 在中间件里被 log.Printf 一次,在 handler 里又被 log.Printf 一次,在 client 端还被转成 alert,就说明错误流没收敛。真正的规范,是让每个 error 只在它该出现的那一层被观测和处置。

















