go错误需自定义结构体实现error接口以携带上下文,如apierror含code、message等字段并实现error()和unwrap();用errors.as安全解包而非类型断言;error()方法应返回简洁摘要,避免敏感信息或结构体序列化;优先使用go 1.13+原生错误链而非pkg/errors。

Go 错误类型本身不携带上下文,但可以自定义实现
Go 的 error 接口只规定了 Error() string 方法,标准库返回的 errors.New 或 fmt.Errorf 都是纯字符串错误,无法直接附加结构化数据(比如请求 ID、状态码、原始错误码)。想传递额外字段,必须自己实现 error 接口。
常见做法是定义一个结构体,嵌入所需字段,并实现 Error() 方法:
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
RequestID string `json:"request_id"`
Original error `json:"-"` // 不序列化,但保留原始错误链
}
func (e *APIError) Error() string {
return e.Message
}
func (e *APIError) Unwrap() error {
return e.Original
}
这样既能通过 fmt.Printf("%+v", err) 查看完整字段,又能用 errors.Is/errors.As 做类型判断和解包。
用 errors.As 提取附加数据比类型断言更安全
如果直接用 err.(*APIError) 断言,一旦错误被 fmt.Errorf("wrapping: %w", err) 包裹过,就会失败。而 errors.As 会自动沿 Unwrap() 链向下查找,直到匹配目标类型。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 正确:能穿透多层包装提取字段
- ❌ 错误:仅对最外层错误做断言,容易 panic 或返回 nil
使用示例:
if err != nil {
var apiErr *APIError
if errors.As(err, &apiErr) {
log.Printf("API failed with code %d, request_id=%s",
apiErr.Code, apiErr.RequestID)
}
}
避免在 Error() 方法里拼接敏感或可变数据
Error() 方法的返回值会被日志、监控、调试器直接展示,所以不适合放动态内容(如用户邮箱、token)或大段结构体 dump(影响性能和可读性)。
- 只返回简洁、稳定、可索引的错误摘要,比如
"failed to parse user config" - 把详细字段(
RequestID、StatusCode)保留在结构体字段中,由调用方按需访问 - 不要在
Error()里调用json.Marshal或fmt.Sprintf拼接整个结构体 —— 这会让日志冗长且难以过滤
第三方库如 pkg/errors 已淘汰,优先用 Go 1.13+ 原生错误链
旧项目可能还在用 github.com/pkg/errors 的 WithMessage、WithStack,但现在应迁移到原生方案:
-
fmt.Errorf("context: %w", err)替代Wrap -
errors.Is(err, target)和errors.As(err, &target)替代Causes和As - 堆栈信息需额外处理:若真需要,可用
runtime.Caller在自定义 error 构造时捕获,而不是依赖包装器
原生错误链不自动带堆栈,但换来的是确定性行为和更少的隐式依赖 —— 这正是 Go 错误设计的取舍重点。










