Golang如何判断并拆解多层错误

9次阅读

必须用 Errors.Is 而不是 == 是因为 errors.Is 会递归遍历整个错误包装链匹配底层错误(如 os.ErrNotExist),而 == 仅比较地址,包装后必失败;errors.As 用于提取链中任意一层的自定义错误类型(如 *appError)以访问其字段,二者均依赖 Unwrap() 实现。

Golang如何判断并拆解多层错误

直接回答:用 errors.Is 判断是否包含某个底层错误,用 errors.As 提取某一层的具体错误类型——两者都自动遍历整个包装链,无需手动 Unwrap

什么时候必须用 errors.Is 而不是 ==

当你想判断一个被多层 %w 包装的错误是否“本质上是”某个标准错误(比如 os.ErrNotExist 或自定义的 ErrTimeout)时,== 会失败,因为外层包装后地址已变。

  • errors.Is(err, os.ErrNotExist) 会递归检查每一层 Unwrap() 后的结果,直到匹配或返回 nil
  • 即使错误是 fmt.Errorf("loading config: %w", os.ErrNotExist),也能正确识别
  • 不要写 err == os.ErrNotExist —— 这在任何包装后都会返回 false

怎么从错误链里取出你的自定义错误字段?

errors.As 把错误链中“某一层”的具体类型赋值给变量,从而访问其结构体字段(如 CodeRetryAfter)。

type AppError struct {     Code string     Msg  string     Err  error } func (e *AppError) Error() string { return e.Msg } func (e *AppError) Unwrap() error { return e.Err }  // 使用 if err != nil {     var appErr *AppError     if errors.As(err, &appErr) {         log.Printf("业务错误码:%s,消息:%s", appErr.Code, appErr.Msg)     } }
  • errors.As 不要求目标错误一定在最外层,只要链中任意一层是 *AppError 类型就成功
  • 注意传入的是 &appErr指针),否则无法赋值
  • 如果只是想判断类型是否存在,不用字段,也可以用 errors.As(err, new(*AppError))

为什么不能靠字符串匹配或日志搜索来判断错误?

字符串匹配脆弱、易失效;日志里看不到原始错误类型,也无法做程序化处理(比如自动重试、降级、熔断)。

立即学习go语言免费学习笔记(深入)”;

  • 比如 err.Error() 返回 "failed to connect to DB: dial tcp 127.0.0.1:5432: connect: connection refused",但下个版本可能加了时间戳或换行,匹配就崩了
  • errors.Is(err, syscall.ECONNREFUSED) 是类型安全的,且兼容所有包装层级
  • 第三方库(如 pgconn)返回的 *pgconn.PgError 也支持 errors.As 直接提取,无需解析字符串

拆解错误链时最容易忽略的一点

errors.Iserrors.As 都只对实现了 Unwrap() error 的错误生效——如果你自己写的错误类型没实现它,包装链就断了。

  • 自定义错误必须显式实现 Unwrap() error,返回被包装的 Err 字段
  • 漏掉这行,errors.Is 就只能看到最外层,再也触不到原始错误
  • fmt.Errorf("xxx: %w", err) 包装时,标准库自动帮你实现了 Unwrap;但手写结构体时不会自动补

text=ZqhQzanResources