go自定义error必须用指针接收者实现error()方法,否则非error类型;须用errors.as提取、%w包装、导出字段并配工厂函数,否则退化为字符串错误。

Go 里自定义 error 不是为了让日志更花哨,而是为了让你能用 errors.As 安全提取字段、用 errors.Is 精确分流、让监控系统能结构化解析错误码——不按规范写,等于白写。
自定义 error 必须实现 Error() 方法,且接收者得是指针
结构体没实现 Error(),它就不是 error,if err != nil 永远为 false,fmt.Println(err) 打印出来是 &{...} 这种内存地址。常见错误包括:
- 接收者用了值类型:
func (e MyError) Error() string—— 字段修改不生效,嵌套包装后原始值可能丢失 - 返回类型写成
int或error而非string - 字段名小写(如
message string),外部无法访问,Error()里又没提供公开读取方式
✅ 正确写法示例:
type ValidationError struct {
Field string
Code int
Message string
}
func (e *ValidationError) Error() string {
return e.Message
}
判断错误类型必须用 errors.As,别用类型断言
err.(*ValidationError) 在错误被 fmt.Errorf("xxx: %w", err) 包装一层后直接 panic;err == someErr 只比地址,毫无意义。只有 errors.As 能穿透任意层包装、安全提取。
-
errors.As第二个参数必须是目标类型的指针变量,比如var e *ValidationError; if errors.As(err, &e) { ... } - 不能写成
errors.As(err, (*ValidationError)(nil)),会 panic - 多个包各自定义了同名
*ValidationError,Go 视为不同类型,errors.As必然失败
包装错误必须用 %w,否则错误链就断了
中间层加业务上下文时,用 fmt.Errorf("failed to process user: %w", err);用 %s、+ 或 %v 会把原始错误转成字符串,errors.Is/errors.As 全部失效,APM 工具也还原不了调用路径。
- 如果结构体带
Cause error字段,记得实现Unwrap() error返回它 - 没包装其他错误时,
Unwrap()可直接返回nil - 错误链里某层用了
%v或sprintf,整条链就从那里断掉,后续errors.As查不到底层类型
构造函数封装创建逻辑,别裸露结构体字段
直接 &ValidationError{Field: "age", Code: 400} 散落在各处,字段易漏、校验缺失、语义不统一。工厂函数能强制约束、注入默认值、隔离变更。
- 业务错误码应限定范围,空
Message应拒绝创建 - 推荐返回
error接口而非具体类型,方便后续替换(比如加TraceID) - 构造函数内部可自动注入
time.Now()、reqID等上下文,避免调用方重复传参
真正容易被忽略的点是:错误链是否完整、字段是否导出、errors.As 的变量是否是指针——这三个地方错一个,整个自定义 error 就退化成字符串错误。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











