错误比较
在 Go 语言中,对 error
类型的错误比较有多种形式,其中一些看似简单但隐藏着陷阱。以下是错误比较的详细分析和常见陷阱:
🎯 错误比较的 4 种形式
1. 常规错误比较 (err == target
)
if err == io.EOF {
// 处理 EOF 情况
}
适用场景:比较预定义的哨兵错误(全局变量)
2. 与 nil
比较 (err == nil
)
if err != nil {
// 错误处理
}
安全场景:检查函数是否返回错误
3. errors.Is()
(推荐)
if errors.Is(err, io.EOF) {
// 检查错误链中是否存在特定错误
}
适用场景:处理包装错误(Wrapped Errors)
4. errors.As()
类型匹配
var e *os.PathError
if errors.As(err, &e) {
// 处理特定错误类型
}
适用场景:处理自定义错误类型
🧩 最危险的错误比较陷阱
❌ 陷阱 1:直接比较动态创建的字符串错误
func ReadConfig() error {
return fmt.Errorf("config error")
}
// 错误的比较方式
if err == fmt.Errorf("config error") {
// 永远不会执行(每次返回新实例)
}
原因:fmt.Errorf()
每次返回新实例,内存地址不同
❌ 陷阱 2:比较被包装的错误
wrappedErr := fmt.Errorf("上下文: %w", io.EOF)
// 错误比较
if wrappedErr == io.EOF {
// 永远不会执行
}
正确做法:
if errors.Is(wrappedErr, io.EOF) {
// 正确匹配
}
❌ 陷阱 3:忽略自定义错误的不相等性
type CustomError struct{ Code int }
err1 := CustomError{Code: 404}
err2 := CustomError{Code: 404}
fmt.Println(err1 == err2) // false(编译错误) ❌
// 只有添加比较方法才可编译(但仍不建议)
func (e CustomError) Equal(other CustomError) bool {
return e.Code == other.Code
}
❌ 陷阱 4:错误与接口变量比较
var target error = io.EOF
err := io.EOF
fmt.Println(err == target) // true ✅
var anyInterface interface{} = io.EOF
fmt.Println(err == anyInterface) // true ✅
// 危险比较
var reader io.Reader = io.EOF
fmt.Println(err == reader) // false ❌
原因:io.EOF
实现了 error
和 io.Reader
,但接口类型不同
✅ 错误处理最佳实践对比
错误处理方式
使用场景
示例
推荐度
err == nil
基础错误检查
if err != nil {...}
★★★★★
errors.Is()
匹配错误链
errors.Is(err, io.EOF)
★★★★★
errors.As()
类型断言
errors.As(err, &PathError)
★★★★★
err == target
预定义哨兵错误
err == io.EOF
★★☆☆☆
直接比较字符串
任意场景
err.Error() == "msg"
☆☆☆☆☆
🔧 自定义错误处理模式
1. 实现 Is()
方法(支持错误链)
type DatabaseError struct { Code int }
func (e DatabaseError) Is(target error) bool {
t, ok := target.(DatabaseError)
return ok && e.Code == t.Code
}
func main() {
err := DatabaseError{Code: 404}
wrapped := fmt.Errorf("包裹错误: %w", err)
// 正确比较
if errors.Is(wrapped, DatabaseError{Code: 404}) {
fmt.Println("匹配 404 错误")
}
}
2. 实现 Equal()
方法(用于直接比较)
func (e DatabaseError) Equal(target DatabaseError) bool {
return e.Code == target.Code
}
// 比较
err := DatabaseError{Code: 404}
if err.Equal(DatabaseError{Code: 404}) {
// 精确比较
}
3. 使用常量哨兵错误(最安全)
var ErrNotFound = errors.New("未找到资源")
func main() {
err := getResource()
if errors.Is(err, ErrNotFound) {
// 安全处理
}
}
🚨 需要警惕的比较结果
fmt.Errorf("A") == fmt.Errorf("A")
false
不同内存实例
io.EOF == io.EOF
true
全局单实例
errors.New("A") == errors.New("A")
false
不同内存实例
error(nil) == nil
true
类型匹配
var e error = nil; e == nil
true
接口值为 nil
var e error = (*Error)(nil); e == nil
false
非空接口类型
黄金规则:对于重要的错误处理,总是使用 errors.Is()
或 errors.As()
代替 ==
操作符
通过遵循这些原则,您可以避免常见的错误比较陷阱,编写出更健壮、更易维护的错误处理代码。
最后更新于