根本原因是被包装的原始错误未实现unwrap()方法或用值接收者实现error();必须用指针接收者并显式实现unwrap(),且errors.is匹配需同类型指针,errors.as要求字段导出并传入指针。

为什么用 fmt.Errorf("xxx: %w", err) 之后 errors.Is 失效
根本原因不是包装本身有问题,而是被包装的原始错误类型没实现 Unwrap() 方法,或者你用的是值接收者而非指针接收者实现 Error()。Go 的 %w 只对实现了 Unwrap() 的 error 类型生效;如果自定义错误是值类型(比如 func (e MyError) Error()),errors.Is 就无法稳定匹配——每次调用 Error() 都会生成新实例,地址不同,类型断言失败。
- 必须用指针接收者实现
Error():func (e *MyError) Error() string - 若需参与错误链,必须显式实现
Unwrap() error并返回嵌套的err字段 - 不要在
Error()方法里拼接e.Err.Error(),否则可能触发无限递归(尤其当e.Err也是同类型) -
errors.Is(err, target)判断的是类型和值语义,不是字符串,所以target必须是同一类型指针(如*MyError)
如何让 errors.As 正确提取带字段的自定义错误
errors.As 不是“解析字符串”,而是做类型断言 + 值拷贝。它要求目标变量是指针,且自定义错误结构体字段必须导出(首字母大写),否则无法写入。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 定义结构体时字段名必须大写:
Code int✅,code int❌(errors.As无法赋值) - 调用时传入指针:
var e *AppError; if errors.As(err, &e) { ... } - 如果多个错误类型共享字段(如
Code,Retryable),可抽象一个基础结构体BaseError并嵌入,避免重复定义 - 不要在
Is()方法里直接比较e.Code == t.Code就返回 true —— 应先确认t非 nil,再比字段,否则 panic
分层封装时哪些错误该包装,哪些该替换
封装不是越多越好。repository 层遇到 sql.ErrNoRows,应该用业务语义错误替换(如 &NotFoundError{Resource: "user"}),而不是 fmt.Errorf("db query failed: %w", err) 包装后往上扔。前者能被 service 层直接识别并返回 404;后者会让上层只能靠 errors.Is(err, sql.ErrNoRows) 判断,耦合数据库细节。
- 底层 I/O 错误(如
os.IsNotExist,net.ErrClosed)适合包装:保留原始上下文,供调试用 - 已知的业务语义错误(如“用户不存在”“库存不足”)应替换为领域错误类型,不暴露底层技术细节
- 网络超时、临时连接失败这类可重试错误,建议包装并加
Retryable bool字段,方便 handler 层决策 - 所有包装都应使用
%w,避免手动拼接字符串导致errors.Unwrap()失效
自定义错误里要不要放敏感字段
绝对不要在 Error() 返回值里拼接 token、密码、手机号等敏感数据。日志系统或监控平台很可能自动采集 err.Error(),造成泄露。但这些字段可以保留在结构体中,仅通过专用方法暴露(如 LogContext()),由调用方按需决定是否记录。
- 敏感字段命名用小写(如
token string),确保不会被errors.As拷贝出去 -
Error()方法只返回安全、用户可见的信息,比如"failed to fetch user profile" - 如需调试,提供类似
DebugString()的方法,内部才包含完整上下文,且明确标注 “for debugging only” - 字段设计优先考虑最小暴露面:能用
int表达状态码就别存整个http.Response
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










