Go自定义错误需兼顾可调试性、可读性与错误链完整性:必须用%w包装、结构体实现Unwrap()、Error()小写无句号、分离用户提示与调试信息、禁止泄露敏感数据。

Go 中自定义错误消息不是“加个字符串”那么简单,关键在于让错误既对开发者可调试、又对用户可理解,同时不破坏错误链和类型判断能力。
用 fmt.Errorf 包装时必须用 %w,而不是 %v 或字符串拼接
这是最常踩的坑:用 %v 会切断错误链,导致 errors.Is 和 errors.As 失效。
- ❌ 错误写法:
return fmt.Errorf("failed to save user: %v", err)—— 原始错误被转成字符串,无法再判断是否是os.ErrNotExist - ✅ 正确写法:
return fmt.Errorf("failed to save user: %w", err)—— 保留原始错误,支持后续精准识别 - ⚠️ 注意:
%w只能出现在格式化字符串的最后位置,且只能接一个error类型值
定义结构体错误时,Error() 方法要简洁,Unwrap() 方法要显式返回底层错误
结构体错误不是为了“看起来高级”,而是为了携带业务语义 + 支持标准错误操作。
-
Error()应返回面向调用方的可读字符串(小写开头、无句号),比如"invalid email format",不是堆砌调试信息 - 若需包装底层错误,必须实现
Unwrap() error方法并返回它,否则%w包装或errors.Unwrap都会失败 - 示例中常见错误:忘记加
Unwrap,或返回nil导致链断裂
type ValidationError struct {
Field string
Msg string
Cause error
}
func (e *ValidationError) Error() string {
return "validation error on " + e.Field + ": " + e.Msg
}
func (e *ValidationError) Unwrap() error {
return e.Cause
}
对外暴露的错误文案必须与内部调试信息分离
同一错误在 CLI、HTTP 接口、日志里呈现方式应不同:CLI 要友好图标+中文提示,日志要结构化字段,API 返回要统一错误码+简短 message。
立即学习“go语言免费学习笔记(深入)”;
- 不要在
Error()方法里直接写“请联系管理员”,那是业务响应逻辑,不是错误本身 - 推荐做法:用自定义错误类型封装
UserMsg string字段,上层根据上下文决定怎么展示;调试时用errors.As提取原始错误看堆栈 - 敏感信息如路径、密码、token 绝对不能进
Error()输出,哪怕只在开发环境
错误消息风格要团队统一,小写 + 无句号 + 动词开头是硬约束
这不是审美问题,而是影响 grep 检索、日志聚合和错误统计准确性的实际门槛。
- ✅ 合规示例:
"failed to connect to redis"、"invalid json in request body" - ❌ 违规示例:
"Failed to connect to Redis."(首字母大写+句号)、"Redis connection failed"(名词结构,动词缺失) - 所有预定义错误常量(如
var ErrNotFound = errors.New("resource not found"))也必须遵守该规范
真正难的不是写一个带字段的错误结构体,而是坚持每一层都只加必要上下文、不重复描述、不越权透传。错误链越深,越要克制——因为最终读它的人,可能正凌晨三点盯着生产报警。










