
在 Go 中应通过预定义的导出错误变量(如 ErrExample)进行 == 恒等比较来识别特定错误,而非调用 err.Error() 后比对字符串——后者易因错误消息变更或包装而失效。
在 go 中应通过预定义的导出错误变量(如 `errexample`)进行 `==` 恒等比较来识别特定错误,而非调用 `err.error()` 后比对字符串——后者易因错误消息变更或包装而失效。
Go 的错误处理哲学强调错误是值(errors are values),而非异常对象。因此,正确识别特定错误的方式不是检查其文本描述,而是利用 Go 错误类型的可比较性(error 是接口,但 errors.New() 返回的底层 *errors.errorString 是可比较的指针类型),通过包级预定义错误变量实现精确、稳定、高效的判断。
✅ 正确做法:定义并比较错误变量
在包中声明导出的错误变量(首字母大写以供外部使用):
package somepackage
import "errors"
// ErrExample 是一个预定义的、可导出的错误值
var ErrExample = errors.New("this is an example")
// DoSomething 可能返回该错误
func DoSomething() error {
return ErrExample // 或其他逻辑分支返回 ErrExample
}
调用方直接使用 == 判断:
import "your-module/somepackage"
func main() {
if err := somepackage.DoSomething(); err != nil {
if err == somepackage.ErrExample {
// ✅ 安全、高效、语义清晰:明确处理此预设错误
log.Println("Handled known example error")
return
}
// 其他错误走通用流程
log.Fatal(err)
}
}
⚠️ 为什么不能用 err.Error() == "xxx"?
- 脆弱性:错误消息属于用户可见文案,可能随版本更新被本地化、优化或重构,导致字符串比对意外失败;
-
包装干扰:若错误被
fmt.Errorf("wrapped: %w", err)或第三方库(如pkg/errors、github.com/pkg/errors)包装,err.Error()返回的是组合字符串,原始消息不再完整匹配; -
性能开销:每次调用
Error()都需分配字符串,而==是常量时间指针比较。
? 进阶:支持错误链判断(Go 1.13+)
当错误可能被包装时,应使用 errors.Is() 判断是否为某类错误(底层基于 Is() 方法或 Unwrap() 链):
import "errors"
var ErrTimeout = errors.New("operation timeout")
func DoWithRetry() error {
return fmt.Errorf("retry failed: %w", ErrTimeout) // 包装
}
// 调用方仍可准确识别
if errors.Is(err, ErrTimeout) {
// ✅ 即使被包装,也能命中
}
? 提示:
errors.Is(err, target)是 Go 标准库推荐的“是否属于某错误类型”的现代方式;而errors.As()则用于提取底层错误实例(如获取*os.PathError)。
? 总结
| 方式 | 是否推荐 | 原因 |
|---|---|---|
err == pkg.ErrXXX |
✅ 强烈推荐(未包装场景) | 简洁、高效、稳定、符合 Go 惯例 |
errors.Is(err, pkg.ErrXXX) |
✅ 推荐(兼容包装场景) | 安全支持错误链,Go 1.13+ 标准方案 |
err.Error() == "xxx" |
❌ 禁止 | 易断裂、不可维护、违背错误设计原则 |
遵循这一实践,你的 Go 错误处理将更健壮、可演进,也更贴近 net/http.ErrBodyNotAllowed、io.EOF 等标准库的设计范式。










