应使用 errors.Is 和 errors.As 替代 == 和类型断言,因其可穿透 %w 包装;自定义错误须实现 Unwrap 方法;错误判断需先判 err != nil 再用 errors.Is/As;日志应展开错误链定位根因。

用 errors.Is 和 errors.As 替代 == 和类型断言
直接用 err == io.EOF 或 if e, ok := err.(*os.PathError); ok 看似简单,但会漏掉包装错误(比如被 fmt.Errorf("failed: %w", err) 包裹后的错误)。Go 1.13 引入的 errors.Is 和 errors.As 才是正确解法。
-
errors.Is(err, io.EOF)能穿透任意层数的%w包装,安全判断是否为某类错误 -
errors.As(err, &pathErr)同样支持深层查找目标错误类型,避免手动层层 unwrap - 别在
switch err.(type)里做判断——它只匹配最外层类型,对fmt.Errorf("%w", …)完全失效
自定义错误类型时优先实现 Unwrap 方法
如果你封装错误(比如加 traceID、上下文字段),必须显式提供 Unwrap() 方法,否则 errors.Is 和 errors.As 无法向下遍历。没有 Unwrap,你的错误就“断链”了。
- 返回
nil表示无下层错误;返回非nil值才参与链式判断 - 不要返回新错误实例(如
fmt.Errorf("wrap: %w", e.cause)),这会破坏原始错误的类型和值语义 - 若错误含多个底层原因(如复合操作失败),可返回切片或自定义
Unwrap()遍历逻辑,但需配合errors.Unwrap的默认行为兼容
避免在 if 条件中嵌套 error 判断
写成 if err != nil && errors.Is(err, fs.ErrNotExist) 是常见但危险的习惯:一旦 err 为 nil,errors.Is 仍会被调用(虽安全),但逻辑已偏离本意——你真正想表达的是「错误存在且属于某类」,应拆开两层判断。
- 先
if err != nil,再在分支内用errors.Is/errors.As分流,语义清晰且易测 -
工具如
staticcheck会警告errors.Is(nil, ...)——不是报错,但说明逻辑冗余 - 复杂分支建议用 map[error]func() 显式映射,而非长 if-else 堆砌,尤其当错误类型多于 3 种时
日志记录前先用 errors.Unwrap 检查根本原因
直接 log.Printf("failed: %v", err) 往往只打出最外层包装信息,丢失原始错误类型和堆栈线索。调试时真正需要的是「谁最先出的错」,而不是「谁最后包的」。
立即学习“go语言免费学习笔记(深入)”;
- 用
errors.Unwrap(err)逐层剥开,直到返回nil,最后一层非nil值才是根因 - 更稳妥的做法是用
fmt.Printf("%+v", err)(需第三方库如github.com/pkg/errors或 Go 1.20+ 内置支持),它自动展开整个错误链 - 注意:不要在日志里反复
Unwrap并拼接字符串——既难读又可能误判(比如中间某层Unwrap()返回了意外错误)
Unwrap,或者在 HTTP handler 中把所有错误统一转成 500 Internal Server Error 而不区分 errors.Is(err, sql.ErrNoRows) 这类预期错误。这两处一漏,后续的错误分类、重试策略、监控告警就全偏了。









