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