




errors.New 和 fmt.Errorf 不够用,因它们无法携带上下文字段、支持类型断言或区分错误类别;需定义实现 error 接口的自定义类型,显式组合错误并导出字段,配合 Unwrap、Is 方法及 errors.As/Is 进行结构化错误处理。
errors.New 和 fmt.Errorf 不够用当错误需要携带上下文(比如请求 ID、失败的文件路径、重试次数)、支持类型断言、或需区分不同错误类别(如网络超

errors.New 返回的 *errors.errorString 是纯字符串包装,无法扩展字段或方法。fmt.Errorf 默认也不支持类型判断——即使加了 %w 也只是包装,底层仍无结构化数据。
直接实现 error 接口(即有 Error() string 方法),并添加你需要的字段。注意:不要嵌入 error 字段来“继承”,那会干扰类型断言;而是显式组合。
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
func (e *PathError) Unwrap() error {
return e.Err
}
Unwrap() 方法让该错误能被 errors.Is / errors.As 正确处理if pe, ok := err.(*PathError); ok { log.Println(pe.Path) }
Error() 中拼接敏感信息(如密码、token),日志打印时可能泄露errors.As 和 errors.Is 做类型/值匹配这是 Go 1.13+ 错误链的标准用法,替代旧式的字符串匹配或反射判断。
err := doSomething()
var pathErr *PathError
if errors.As(err, &pathErr) {
log.Printf("failed on path: %s", pathErr.Path)
}
if errors.Is(err, fs.ErrPermission) {
// 处理权限错误
}
errors.As 按错误链逐层检查是否可转换为目标类型指针,支持多层包装(如 fmt.Errorf("wrap: %w", e))errors.Is 比较的是错误值相等性(底层调用 Is() 方法),不是字符串内容,所以自定义类型若需参与 Is 判断,应实现 Is(target error) bool 方法errors.As 的第二个参数传值类型(如 &PathError{}),必须传指针变量地址,否则匹配永远失败fmt.Errorf 包装,何时新建类型包装适合临时补充上下文且无需结构化提取的场景;新建类型适合错误语义明确、需被业务逻辑分支处理、或需暴露额外状态的场景。
fmt.Errorf("read config: %w", err) —— 日志友好,保留原始错误,但调用方不关心具体配置名&ConfigLoadError{File: "/etc/app.yaml", Err: err} —— 当上层要根据 File 做 fallback 或告警分类时fmt.Errorf("out of stock: %d", n) 更易维护和测试fmt.Errorf + 错误码常量Is、要不要支持 Unwrap,都取决于你打算怎么用它。