2. error 接口:定义、实现与基础用法
Go 语言的错误处理核心是一个极其简洁的内建接口——error。理解它的定义、如何实现自定义错误,以及 errors.New 与 fmt.Errorf 的底层机制,是掌握错误码断言的前提。
2.1 error 接口定义
Go 源码中 error 的定义只有一行:
type error interface {
Error() string
}
任何实现了 Error() string 方法的类型都自动满足 error 接口。这意味着你可以用任意结构体、指针类型甚至基本类型来承载错误信息。
| 特性 | 说明 |
|---|---|
| 接口方法数 | 1 个:Error() string |
| 返回值 | 人类可读的错误描述字符串 |
| 实现方式 | 隐式实现,无需 implements 关键字 |
| 零值 | nil,表示无错误 |
关键认知: error 接口只负责“描述错误”,不携带错误码、堆栈或类型信息。这正是后续章节引入错误码断言的原因——我们需要在 error 之上附加结构化数据。
2.2 实现自定义错误
通过定义结构体并实现 Error() 方法,你可以创建携带额外上下文的错误类型。
// 自定义错误类型:携带错误码和内部原因
type ReportError struct {
Code int
Message string
Err error // 包装原始错误
}
func (e *ReportError) 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 parseReport(data []byte) error {
if len(data) == 0 {
return &ReportError{
Code: 1001,
Message: "empty report data",
Err: nil,
}
}
// 正常处理...
return nil
}
自定义错误的优势在于:
- 携带结构化字段:错误码、严重级别、时间戳等
- 支持错误链:通过
Err字段保留原始错误 - 可被类型断言识别:调用方可以用
errors.As提取具体类型
注意: 自定义错误类型通常使用指针接收者(
*ReportError),因为错误往往需要被共享和比较。值接收者可能导致接口比较时的意外行为。
2.3 errors.New 与 fmt.Errorf 基础用法
标准库提供了两个最常用的错误构造器:
| 函数 | 签名 | 特点 | 适用场景 |
|---|---|---|---|
errors.New |
func New(text string) error |
固定字符串,无格式化 | 静态错误消息 |
fmt.Errorf |
func Errorf(format string, a ...any) error |
支持 %s、%d、%v 等格式化 |
动态拼接错误信息 |
// errors.New:纯静态错误
var ErrNotFound = errors.New("report not found")
// fmt.Errorf:动态格式化
func validateReportID(id int) error {
if id <= 0 {
return fmt.Errorf("invalid report ID: %d", id)
}
return nil
}
// 组合使用:包装上下文
func loadReport(id int) error {
if id <= 0 {
return fmt.Errorf("load failed: %w", ErrNotFound)
}
return nil
}
最佳实践: 在 Go 1.13+ 中,
fmt.Errorf 支持 %w 动词来包装错误,生成可被 errors.Is 和 errors.As 解包的错误链。这是构建错误码断言体系的基础设施。
2.4 三种错误构造方式对比
下面的 SVG 图展示了 errors.New、fmt.Errorf 和自定义错误类型在内存结构上的差异:
2.5 错误比较与断言基础
在进入错误码断言之前,先掌握两种基本的错误比较方式:
// 1. 值比较:使用 errors.Is 检查错误链中是否包含特定哨兵错误
if errors.Is(err, ErrNotFound) {
// 处理“未找到”逻辑
}
// 2. 类型比较:使用 errors.As 提取特定类型的错误实例
var reportErr *ReportError
if errors.As(err, &reportErr) {
fmt.Printf("错误码: %d, 消息: %s\n", reportErr.Code, reportErr.Message)
}
实战提示: 在报表系统中,推荐优先使用
errors.As 配合自定义错误类型来获取错误码,而不是依赖字符串匹配。字符串匹配脆弱且无法应对国际化场景。
2.6 本章小结
- error 接口:仅包含
Error() string一个方法,所有错误类型隐式实现它。 - 自定义错误:通过结构体 +
Error()方法创建,可携带错误码、原始错误等结构化字段。 - errors.New:构造静态错误消息,返回不可解包的
errorString。 - fmt.Errorf:支持格式化与
%w包装,生成可解包的错误链。 - 比较机制:
errors.Is用于值比较,errors.As用于类型提取,是错误码断言的前置技能。