error接口不能直接加字段,必须用结构体实现,因其仅定义Error()方法而无字段存储能力;需通过结构体挂载code、err等字段,并实现Error()、Unwrap()等方法以支持错误链和类型断言。

为什么 error 接口不能直接加字段,必须用结构体?
Go 的 error 是个接口:type error interface { Error() string }。它只要求一个方法,不提供任何字段存储能力。你想带状态码、请求 ID、原始错误、重试建议?光靠字符串拼接或 fmt.Errorf 包裹根本存不住——这些信息要么丢掉,要么得从字符串里正则硬抠,非常脆弱。
所以必须定义结构体,让它实现 Error() 方法,同时把需要的字段都挂上去。
- 结构体字段名要小写(如
code、traceID),避免外部误改;导出字段只在必要时才用(比如日志系统需要读取Code()) - 不要在
Error()方法里做耗时操作(如网络调用、文件读取),这个方法可能被 panic 捕获或日志频繁调用 - 如果原错误很重要,用
Unwrap()方法返回它,这样errors.Is()和errors.As()才能正常工作
怎么写一个带状态码和原始错误的自定义错误结构体?
典型场景:HTTP 服务返回 404 Not Found,你既要告诉调用方是客户端错,又得保留底层数据库查询失败的细节,方便排查。
type AppError struct {
code int
message string
err error // 原始错误,比如 sql.ErrNoRows
}
func (e *AppError) Error() string {
if e.err == nil {
return e.message
}
return e.message + ": " + e.err.Error()
}
func (e *AppError) Unwrap() error { return e.err }
func (e *AppError) Code() int { return e.code }
- 别忘了给
err字段赋值:用&AppError{code: 404, message: "user not found", err: dbErr},不是AppError{...}(否则指针方法失效) - 如果想让
errors.Is(err, ErrNotFound)生效,得定义包级变量错误实例,比如var ErrNotFound = &AppError{code: 404, message: "not found"} - 注意
Unwrap()返回nil表示没有底层错误,别返回空指针或未初始化结构体
errors.As() 为啥总失败?结构体类型匹配的坑在哪
常见现象:明明用了 &AppError{...},但 errors.As(err, &target) 就是不赋值,target 还是零值。根本原因是类型不匹配——errors.As 要求目标变量是指向**具体类型**的指针,且源错误必须是该类型的指针(不是值)。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
var target AppError; errors.As(err, &target)→ 源是*AppError,目标是*AppError的值拷贝,类型不一致 - 正确写法:
var target *AppError; errors.As(err, &target)→ 两边都是指针类型,才能成功转换 - 如果结构体字段有非导出字段(比如
err),不影响As,但会影响 JSON 序列化(默认忽略) - 嵌套多层错误时,
As只找最内层第一个匹配项,不会递归穿透所有Unwrap()
要不要实现 fmt.Formatter?什么时候值得加
默认 Error() 只返回字符串,但调试时你可能希望 %+v 显示完整结构(字段、地址)、%v 显示精简摘要、%q 加引号。这时候可以实现 fmt.Formatter 接口。
但多数业务错误不需要——除非你在写 SDK 或中间件,需要暴露结构化调试信息给下游开发者。
- 加了
Format()方法后,log.Printf("%+v", err)就能打印字段,比纯字符串友好得多 - 别在
Format()里调用fmt.Sprintf递归格式化自己,容易栈溢出 - 如果结构体含敏感字段(如密码、token),
Format()里要显式过滤,不能直接f.Printf("%+v", e)
真正难的不是写结构体,是决定哪些字段该暴露、哪些该封装、哪些该随错误链传递——这得看你的错误消费者是谁,日志系统认不认字段,监控告警能不能提取 code,而不是只想着“实现接口”。

















