Go自定义错误类型旨在区分错误种类并携带上下文字段,应使用导出字段的struct实现error接口,配合errors.Is/As进行健壮判断,避免敏感数据和大对象,确保调用链中类型穿透一致。
Go 里自定义错误类型不是为了“看起来更专业”,而是为了解决两个实际问题:能区分错误种类(比如 IsTimeoutErr 还是 IsAuthFailed),以及能携带上下文字段(比如失败的 UserID、重试次数 RetryCount)。
用 struct 实现 error 接口是最常用且可控的方式
Go 的 error 就是个接口:type error interface { Error() string }。只要实现这个方法,就是合法错误。用 struct 而非 string 或 fmt.Errorf 包装,是为了可扩展性。
常见错误写法是直接返回 fmt.Errorf("user %d not found", id) —— 这类错误无法被程序逻辑识别,只能靠字符串匹配,脆弱且不可靠。
正确做法是定义一个结构体:
type UserNotFoundError struct {
UserID int
Source string // 可选:标记来自 DB 还是 Cache
}
func (e *UserNotFoundError) Error() string {
return fmt.Sprintf("user %d not found in %s", e.UserID, e.Source)
}
这样调用方就能用类型断言精准识别:
if err != nil {
if _, ok := err.(*UserNotFoundError); ok {
// 触发降级逻辑,不记录告警
return fallbackData()
}
// 其他错误走通用处理
}
- 务必导出结构体字段(首字母大写),否则外部包无法访问
UserID等上下文 - 不要在
Error()方法里做耗时操作(如日志、网络请求),它可能被多次调用 - 如果错误不需要额外字段,用
errors.New("xxx")更轻量,不必强上 struct
用 errors.Is 和 errors.As 替代类型断言
Go 1.13 引入了 errors.Is 和 errors.As,它们能穿透多层包装错误(比如被 fmt.Errorf("wrap: %w", err) 包过),比裸类型断言更健壮。