Go的error接口仅要求实现Error()方法,无法携带状态码、上下文等结构化信息;自定义错误结构体(如AppError)通过字段和Unwrap()方法支持错误分类、链式提取及errors.Is/As安全判断。
为什么 error 接口本身不支持携带状态码和上下文
Go 的 error 是个接口:type error interface { Error() string },它只强制实现一个方法,天然无法承载结构化信息。直接用 fmt.Errorf 或 errors.New 生成的错误都是无类型的字符串错误,调用方无法可靠判断错误种类(比如是数据库超时还是记录不存在),更没法提取 StatusCode、RequestID 等字段。
自定义错误类型的核心目的,就是让错误可识别、可分类、可扩展。不是为了“看起来高级”,而是为了解决真实场景中 if err != nil 后不知道怎么处理的困境。
如何定义带字段和行为的自定义错误结构体
最常用且推荐的方式是定义一个 struct,内嵌 error 字段或实现 Error() 方法,并添加业务所需字段:
type AppError struct {
Code int
Message string
Cause error
RequestID string
}
func (e *AppError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Cause)
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
func (e *AppError) Unwrap() error { return e.Cause }
-
Code用于 HTTP 状态码或内部错误码,便于上层统一映射 -
Cause字段 +Unwrap()方法,使该错误兼容 Go 1.13+ 的错误链机制,可用errors.Is()和errors.As() - 不要忘记导出字段(首字母大写),否则外部包无法访问
Code或RequestID - 避免在
Error()中拼接敏感信息(如密码、token),日志里打出来就泄露了
怎样用 errors.As() 和 errors.Is() 安全地判断和提取自定义错误
这是自定义错误真正发挥作用的关键——不再靠字符串匹配或类型断言硬编码,而是利用标准库提供的错误检查能力。