自定义 error 类型必须实现 error() string 方法,否则无法被正确识别和打印;应使用 errors.as 判断并提取错误,用 %w 包装错误以保留错误链,推荐通过工厂函数封装错误创建逻辑。

自定义 error 类型必须实现 Error() 方法
Go 的 error 接口只有这一个方法:Error() string。不实现它,就不是合法的 error,无法被 if err != nil 检查,也无法被 fmt.Println 或日志库正常打印。
常见错误是只定义结构体但忘了加方法,或者方法签名写错(比如返回 int、漏掉指针接收者、参数名不匹配)。
- ✅ 正确写法:
func (e *MyError) Error() string { return e.Message } - ❌ 错误写法:
func (e MyError) Error() int { return 1 }(返回类型错、接收者非指针、方法名大小写错) - ⚠️ 注意:如果字段是私有的(如
message string),外部包无法访问,Error()方法里就得靠公开字段或提供 getter,否则构造后连消息都取不到
用 errors.As 判断并提取自定义错误信息
不能用 err == someError 或 err == nil 以外的直接比较来识别自定义错误——因为 error 是接口,底层值可能不同;也不能用类型断言 err.(*MyError),它在嵌套错误(比如用 fmt.Errorf("%w", origErr) 包装过)时会失败。
errors.As 是唯一安全、可穿透包装的判断方式。
- ✅ 正确用法:
var e *MyError; if errors.As(err, &e) { fmt.Println(e.Code) } - ❌ 错误用法:
e := err.(*MyError)—— panic 风险高,且无法处理%w包装链 - ⚠️ 注意:
errors.As第二个参数必须是指向目标类型的指针变量,不是类型字面量
构造函数封装错误创建逻辑
暴露结构体字段(如 Code、Message)会让调用方随意构造错误,破坏业务语义一致性。应该提供工厂函数,统一校验、默认值填充和上下文注入。
例如,业务错误码应限定范围,空消息应拒绝创建,时间戳应自动注入。
- ✅ 推荐写法:
func NewValidationError(field string, msg string) error { return &ValidationError{Field: field, Message: msg, Time: time.Now()} } - ❌ 不推荐:
e := &ValidationError{Field: "email", Message: "invalid format"}—— 散落在各处,难维护、易遗漏字段 - ⚠️ 注意:构造函数返回
error接口而非具体类型,方便后续替换实现(比如换成带 traceID 的版本)
错误包装要用 %w 而非 %s,保留原始错误链
当在中间层(如 service 层)调用底层函数出错,想补充上下文又不想丢失原始错误时,必须用 fmt.Errorf("xxx: %w", err)。用 %s 或 + "msg" 会切断错误链,导致 errors.Is 和 errors.As 失效。
- ✅ 正确包装:
return fmt.Errorf("failed to fetch user from db: %w", err) - ❌ 错误包装:
return fmt.Errorf("failed to fetch user from db: %s", err)—— 原始错误类型丢失 - ⚠️ 注意:
%w只能出现一次,且必须是最后一个参数;多个错误要逐层包装,不能合并成一个字符串
真正容易被忽略的是错误链的完整性——一旦某一层用了 %s,下游所有基于 errors.Is 的业务判断都会失效,而这种问题在线上往往只表现为“某个错误码没被正确识别”,排查成本远高于写对那一行 %w。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











