go中自定义错误类型必须用指针接收者实现error()方法,以确保满足error接口、支持字段访问和错误链传递;应使用errors.as提取、%w包装、工厂函数构造,并避免字符串拼接破坏错误语义。

Go 里没有“异常”,只有 error;自定义错误类型不是为了模拟 Java/C# 那套 try-catch,而是为了在多层调用中可靠识别、分类、补充上下文并保留原始根因。
为什么 Error() 方法必须用指针接收者?
结构体值接收者(func (e MyError) Error() string)会导致:调用 fmt.Println(err) 时无法触发该方法(接口匹配失败),err != nil 检查也总为 false——因为 Go 接口底层是类型+方法集,值接收者方法只属于 MyError 类型,而 *MyError 才能同时满足 error 接口和你自己的字段访问需求。
- 字段如
Code、Message通常要导出(首字母大写),否则外部包无法读取 - 如果内部封装了
Err error字段,Error()方法里必须显式调用e.Err.Error()或用%w,否则包装链断裂 - 别写
func (e *MyError) Error() int——返回类型错、方法名大小写错、接收者类型错,三者任一都会让类型不满足error接口
errors.As 是唯一安全的自定义错误提取方式
直接类型断言 err.(*MyError) 在错误被 fmt.Errorf("xxx: %w", err) 包装后会失败;err == someErr 更不可靠,因为每次 new 都是不同地址。
- 正确写法:
var e *MyError; if errors.As(err, &e) { log.Printf("code=%d, msg=%s", e.Code, e.Message) } -
errors.As第二个参数必须是指针变量(&e),不是类型字面量(*MyError) - 它能穿透任意层数的
%w包装,但对%s或+拼接的错误完全无效
工厂函数比裸 struct 初始化更可控
暴露结构体字段会让业务代码随意构造 &MyError{Code: 999, Message: ""},破坏错误语义一致性。工厂函数可统一约束:
- 校验
Code范围(比如只允许 400–599 的业务码) - 拒绝空
Message,或自动补前缀("validation failed: " + msg) - 注入时间戳、traceID、请求 ID 等调试必需字段
- 返回
error接口而非具体类型,方便后续替换实现(比如换成带堆栈的版本)
%w 不是可选,是错误链存活的必要条件
在 service 层调用 repository 出错时,若写 return fmt.Errorf("update user failed: %s", err),原始错误的类型、字段、堆栈全丢光——errors.Is 和 errors.As 全部失效。
- 必须用
return fmt.Errorf("update user failed: %w", err) -
%w只接受一个error类型参数,不能跟其他格式化动词混用(比如%w %s会 panic) - 日志打印时用
%+v才能看到完整错误链,普通%v只显示最外层Error()返回值
最容易被忽略的是:错误链不是靠人肉拼字符串维持的,它依赖 %w + errors.As 这一对机制闭环运作;漏掉任意一环,分类就退化成字符串匹配。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











