必须用结构体实现error接口并支持unwrap()和is(),字段小写、语义清晰,error()只返回安全摘要,unwrap()返回非nil错误字段,is()做类型语义比对,errors.as()需指针接收者与指针实例。

直接用 errors.New 或 fmt.Errorf 返回字符串错误,无法携带错误码、重试标志、原始状态等上下文,也难以做类型判断或安全解包——必须用结构体实现 error 接口,并主动支持 Unwrap() 和 Is()。
定义结构体时字段命名要小写且语义清晰
导出字段(大写首字母)会暴露内部细节,增加耦合;非导出字段配合方法暴露能力更可控。常见字段如 code、retryable、cause 是合理选择,避免用 ErrorCode 或 ErrorMessage 这类冗余命名。
-
Error()方法只负责返回用户友好的摘要信息,不拼接敏感数据(如 token、密码、原始请求体) - 若需暴露额外信息,单独定义方法,例如
StatusCode()或IsTimeout() - 不要在
Error()里调用e.cause.Error(),否则可能触发无限递归(尤其当cause也是同类型)
实现 Unwrap() 才能参与标准错误链
Go 1.13+ 的 %w 包装机制依赖 Unwrap() 方法返回底层错误。如果自定义类型不实现它,errors.Unwrap()、errors.Is()、errors.As() 都无法穿透到被包装的原始错误。
- 仅当结构体中存在可包装的
error字段(如Err error)时,才实现Unwrap() - 返回值必须是非
nil,否则会被视为“无包装”,导致链断裂 - 不要返回
nil或硬编码错误,应直接返回字段值:return e.Err
用 Is() 替代字符串匹配做错误判断
用 strings.Contains(err.Error(), "timeout") 判断错误类型极不可靠:消息微调、翻译、日志脱敏都会让匹配失效。正确做法是通过 Is() 实现语义化比对。
-
Is()方法接收target error,需先类型断言为同类型指针,再比对关键字段(如code) -
errors.Is(err, mypkg.ErrNotFound)能跨多层包装生效,前提是每层都实现了Is() - 若多个错误共享相同判断逻辑,可抽象出基础结构体嵌入,避免重复实现
errors.As() 提取结构化字段必须用指针接收者
errors.As(err, &target) 依赖类型一致性:目标变量必须是指向自定义错误类型的指针,且原错误实例也得是指针(&MyError{...}),否则提取失败。
- 结构体定义后,函数返回时务必用
&MyError{...},而非MyError{...} - 接收者必须是指针(
func (e *MyError) Error() string),否则errors.As无法将接口值还原为具体类型 - 字段访问权限不影响
As,但非导出字段需配套提供公开方法(如Code())供外部读取
最易被忽略的是:自定义错误是否真正参与了错误链。很多团队只实现了 Error(),却没补 Unwrap() 和 Is(),结果 errors.Is 查不到根因,errors.As 提不出字段,最终又退回到字符串匹配的老路。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











