2、Go内置error接口:error接口的定义、实现自定义error、errors.New与fmt.Errorf的使用
2.1 error接口的定义
Go语言通过一个极简的内置接口error统一了所有错误值的契约。该接口定义在builtin包中,是Go类型系统中最核心的接口之一。
| 接口名称 | 方法签名 | 返回值含义 |
|---|---|---|
error |
Error() string |
返回错误的人类可读描述信息 |
// builtin.go 中的定义(简化)
type error interface {
Error() string
}
任何实现了Error() string方法的类型都自动满足error接口,无需显式声明implements关键字。这种鸭子类型(Duck Typing)机制使得Go的错误处理具有极高的扩展性。
关键认知:
error是一个接口,不是结构体。这意味着任何值只要拥有Error() string方法,就可以作为错误值传递。这是Go错误处理体系的基础契约。
2.2 实现自定义error
通过定义结构体并实现Error()方法,可以创建携带丰富上下文的错误类型。这是构建安全错误码断言体系的第一步。
// 自定义错误类型:携带错误码和上下文
type AppError struct {
Code int // 业务错误码
Message string // 人类可读消息
Err error // 包装的原始错误(可选)
}
// 实现 error 接口
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
// 使用示例
func ProcessOrder(id string) error {
if id == "" {
return &AppError{
Code: 4001,
Message: "订单ID不能为空",
Err: nil,
}
}
// 正常处理...
return nil
}
安全编码要点: 自定义错误类型应避免暴露敏感信息(如数据库连接串、堆栈路径)。
Error()方法返回的字符串应仅包含业务层可公开的信息。
2.3 errors.New 与 fmt.Errorf 的使用
标准库提供了两种快速创建简单错误的方式,适用于不需要额外错误码或上下文的场景。
| 函数 | 签名 | 特点 | 适用场景 |
|---|---|---|---|
errors.New |
func New(text string) error |
返回一个包含固定文本的简单错误 | 静态错误消息,无需格式化 |
fmt.Errorf |
func Errorf(format string, a ...any) error |
支持格式化占位符,可包装错误(%w) | 动态错误消息,需要包装原始错误 |
// errors.New 示例
var ErrNotFound = errors.New("resource not found")
func GetUser(id int) (User, error) {
if id <= 0 {
return User{}, ErrNotFound
}
// ...
}
// fmt.Errorf 示例(Go 1.13+ 支持 %w 包装)
func OpenConfig(path string) error {
f, err := os.Open(path)
if err != nil {
// 使用 %w 包装原始错误,保留错误链
return fmt.Errorf("打开配置文件失败: %w", err)
}
defer f.Close()
// ...
return nil
}
重要区别:
errors.New创建的每个错误都是不同的实例,即使文本相同,使用==比较时也返回false。因此,判断错误类型应使用errors.Is或类型断言,而非直接比较。
2.4 错误接口契约与断言体系
下图展示了Go内置error接口、自定义错误类型与标准库错误创建函数之间的关系,以及它们在安全错误码断言体系中的定位。
2.5 三种方式的对比与选择
| 维度 | errors.New | fmt.Errorf | 自定义struct |
|---|---|---|---|
| 错误码支持 | ❌ 不支持 | ❌ 不支持(需额外包装) | ✅ 原生支持 |
| 格式化消息 | ❌ 固定文本 | ✅ 支持 | ✅ 可自定义 |
| 错误包装(%w) | ❌ 不支持 | ✅ 支持 | ✅ 需手动实现 Unwrap |
| 类型断言 | ❌ 无法断言 | ❌ 无法断言 | ✅ 支持 |
| 适用场景 | 简单哨兵错误 | 动态消息+包装 | 业务错误码+断言 |
实践建议: 在构建安全错误码断言体系时,优先使用自定义错误类型携带错误码。
errors.New和fmt.Errorf适用于快速原型或不需要错误码分类的简单场景。对于生产级系统,建议统一使用自定义错误类型,确保每个错误都携带可断言的错误码。