Go的error接口仅需Error()方法,因遵循“错误即值”哲学,强调字符串可读性与鸭子类型;自定义错误需导出、用指针接收者、避免不可比较字段,并通过Unwrap()支持错误链。

为什么 error 接口只要一个 Error() 方法?
因为 Go 的错误设计哲学是「错误即值」,不是异常。它不追求类型继承或复杂分类,只关心「能不能转成字符串给人看」。只要实现了 Error() 方法(返回 string),就自动满足 error 接口——这是 Go 接口鸭子类型的直接体现。
常见错误现象:cannot use "xxx" (untyped string) as error value in return statement,本质是你直接 return 了字符串,没包成实现了 error 接口的类型。
- 别写
return "failed",要写return errors.New("failed")或return fmt.Errorf("failed: %w", err) - 自定义错误类型必须导出(首字母大写),否则其他包无法断言或使用
-
errors.New返回的是*errors.errorString,底层是私有结构体,别依赖它的具体类型
用 fmt.Errorf 还是自己定义结构体?
取决于你是否需要携带额外字段(比如错误码、HTTP 状态、原始参数)或支持断言识别。纯提示性错误,fmt.Errorf 足够;要区分错误种类或透传上下文,就得结构体。
使用场景举例:API 返回 404,你希望调用方能判断是不是「资源不存在」而不是靠字符串匹配。
立即学习“go语言免费学习笔记(深入)”;
- 简单包装:
return fmt.Errorf("read config: %w", io.ErrUnexpectedEOF)—— 保留原始错误链,推荐用于中间层 - 带字段的自定义错误:
type NotFoundError struct { Resource string ID int } func (e *NotFoundError) Error() string { return fmt.Sprintf("not found: %s with id %d", e.Resource, e.ID) } - 记得加
*NotFoundError指针接收者,否则值拷贝后errors.As断言会失败
怎么让自定义错误支持 errors.Is 和 errors.As?
关键在两点:错误值本身要可比较(结构体字段都可比较),且最好实现 Unwrap() 方法来暴露底层错误。Go 1.13+ 的错误链机制依赖这个。
容易踩的坑:errors.As 对非指针类型或含 map/slice 字段的结构体直接 panic;errors.Is 只比对错误链中某一层是否为同一实例或相等值。
- 避免在自定义错误结构体里放
map、slice、func字段——它们不可比较,会导致errors.As失败 - 如果包装了其他错误,加
Unwrap() error方法:func (e *NotFoundError) Unwrap() error { return e.Cause } - 想支持
errors.Is(err, ErrNotFound),得定义一个包级变量var ErrNotFound = &NotFoundError{},然后用指针比较
什么时候该用 errors.New,什么时候该用 fmt.Errorf?
看要不要格式化、要不要嵌套原始错误。errors.New 是最轻量的构造方式,适合静态错误消息;fmt.Errorf 支持占位符和 %w 嵌套,是现代 Go 错误处理的事实标准。
性能影响很小,但语义差别明显:不用 %w 就断了错误链,上层无法用 errors.Unwrap 或 errors.Is 追溯根因。
- 静态错误(如配置缺失):
errors.New("config file not provided") - 带上下文的错误:
fmt.Errorf("failed to parse JSON in %s: %w", filename, jsonErr) - 千万别写
fmt.Errorf("failed: %v", err)—— 这丢掉了错误链,也失去了类型信息 - 如果只是拼接字符串又不嵌套,用
fmt.Sprintf+errors.New更清晰,比如errors.New(fmt.Sprintf("invalid id: %d", id))
真正麻烦的不是定义错误类型,而是统一错误处理边界——比如 HTTP handler 里 recover 后怎么转成标准 error,或者 gRPC 中怎么映射错误码。这些地方一旦松散,后续排查就只能靠日志猜。










