2. error 接口:内置 error 接口的定义、实现自定义错误类型、errors.New 与 fmt.Errorf 基础用法
Go 语言通过内置的 error 接口统一了错误表示。理解该接口的定义、实现方式以及标准库提供的构造工具,是后续学习错误码断言与业务错误处理的基础。本节将围绕接口定义、自定义错误类型、errors.New 与 fmt.Errorf 三个核心知识点展开。
2.1 内置 error 接口定义
Go 的 error 是一个预声明接口类型,定义在 builtin 包中。它只包含一个方法:
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
任何实现了 Error() string 方法的类型都自动满足 error 接口。这意味着 Go 的错误处理基于隐式接口实现,无需显式声明 implements 关键字。
| 特性 | 说明 |
|---|---|
| 方法签名 | Error() string |
| 返回值 | 返回描述错误信息的字符串 |
| 零值 | nil 表示没有错误 |
| 实现方式 | 隐式接口实现,任何类型只要定义了该方法即满足接口 |
关键认知:
error 是 Go 中唯一用于表示错误的接口类型。函数返回 error 时,调用方应始终检查其是否为 nil。这是 Go 错误处理的基本契约。
2.2 实现自定义错误类型
虽然标准库提供了基础构造工具,但在业务系统中,我们通常需要携带更多上下文信息。自定义错误类型可以附加错误码、时间戳、堆栈信息等字段。
示例:定义一个携带错误码的业务错误类型
// BizError 业务错误类型,包含错误码和描述信息
type BizError struct {
Code int // 业务错误码
Message string // 人类可读的错误描述
}
// Error 实现 error 接口
func (e *BizError) Error() string {
return fmt.Sprintf("code=%d, message=%s", e.Code, e.Message)
}
// 使用示例
func GetUser(id int) (*User, error) {
if id <= 0 {
return nil, &BizError{
Code: 1001,
Message: "invalid user id",
}
}
// 正常业务逻辑...
return &User{ID: id, Name: "Alice"}, nil
}
自定义错误类型的常见模式:
- 结构体 + 字段:适用于需要携带多个上下文属性的场景,如错误码、请求 ID、用户 ID。
- 哨兵错误 + 包装:定义全局哨兵错误,再通过包装附加额外信息。
- 枚举错误码 + 错误接口:将错误码定义为枚举类型,统一管理。
实践建议: 在业务项目中,推荐将自定义错误类型定义在独立的
errors 或 errtypes 包中,避免循环依赖。错误码应使用常量或枚举,便于维护和断言。
2.3 errors.New 与 fmt.Errorf 基础用法
标准库提供了两个最常用的错误构造函数:errors.New 和 fmt.Errorf。它们都返回实现了 error 接口的值。
2.3.1 errors.New
errors.New 接收一个字符串参数,返回一个包含该字符串的错误值。它是最简单的错误构造方式。
import "errors"
var ErrNotFound = errors.New("record not found")
var ErrPermissionDenied = errors.New("permission denied")
func FindRecord(id int) (Record, error) {
if id <= 0 {
return Record{}, ErrNotFound
}
// 查找逻辑...
return Record{}, nil
}
特点:
- 返回的错误值不可变,每次调用返回相同的指针(如果字符串相同)。
- 适合定义全局哨兵错误,用于调用方通过
==或errors.Is进行断言。 - 不支持格式化字符串,无法动态拼接上下文信息。
2.3.2 fmt.Errorf
fmt.Errorf 支持格式化字符串,可以动态构造错误信息。它返回一个包含格式化后字符串的错误值。
import "fmt"
func OpenFile(path string) (*os.File, error) {
if path == "" {
return nil, fmt.Errorf("invalid file path: %s", path)
}
// 打开文件逻辑...
return os.Open(path)
}
特点:
- 支持
%s、%d、%v等格式化动词。 - 每次调用返回一个新的错误值(即使格式化结果相同)。
- Go 1.13 之后,
fmt.Errorf支持%w动词进行错误包装(将在后续章节详细讲解)。
| 对比维度 | errors.New | fmt.Errorf |
|---|---|---|
| 参数 | 固定字符串 | 格式化字符串 + 可变参数 |
| 动态内容 | 不支持 | 支持 |
| 返回值复用 | 相同字符串返回相同指针 | 每次调用返回新值 |
| 适用场景 | 哨兵错误、静态错误 | 动态错误信息、包装错误 |
| 错误包装(%w) | 不支持 | 支持(Go 1.13+) |
常见误区: 不要将
errors.New 用于需要动态拼接错误信息的场景,否则每次拼接都会产生新的字符串,且无法利用哨兵错误的断言优势。此时应优先使用 fmt.Errorf 或自定义错误类型。
2.4 错误构造与断言流程
下面的 SVG 图展示了从错误定义、构造到调用方断言的整体流程,帮助理解 error 接口在业务代码中的典型使用链路。
2.5 综合示例:哨兵错误 + 自定义错误类型
以下示例展示了如何在实际业务中组合使用 errors.New 和自定义错误类型:
package service
import (
"errors"
"fmt"
)
// 哨兵错误定义
var (
ErrUserNotFound = errors.New("user not found")
ErrInvalidInput = errors.New("invalid input")
)
// BizError 业务错误,携带错误码
type BizError struct {
Code int
Message string
Err error // 原始错误,用于错误链
}
func (e *BizError) Error() string {
return fmt.Sprintf("code=%d, message=%s, cause=%v", e.Code, e.Message, e.Err)
}
func (e *BizError) Unwrap() error {
return e.Err
}
// GetUserProfile 获取用户资料
func GetUserProfile(userID int) (*Profile, error) {
if userID <= 0 {
return nil, fmt.Errorf("%w: userID=%d", ErrInvalidInput, userID)
}
profile, err := db.FindProfile(userID)
if err != nil {
return nil, &BizError{
Code: 2001,
Message: "database query failed",
Err: err,
}
}
return profile, nil
}
// 调用方断言示例
func HandleRequest(userID int) {
profile, err := GetUserProfile(userID)
if err != nil {
if errors.Is(err, ErrInvalidInput) {
// 处理无效输入
fmt.Println("invalid input:", err)
} else if errors.Is(err, ErrUserNotFound) {
// 处理用户不存在
fmt.Println("user not found:", err)
} else {
// 处理其他业务错误
var bizErr *BizError
if errors.As(err, &bizErr) {
fmt.Printf("business error: code=%d, message=%s\n", bizErr.Code, bizErr.Message)
}
}
return
}
// 正常处理 profile
fmt.Println("profile:", profile)
}
学习要点: 本示例展示了三种错误构造方式的协同使用:
errors.New 定义哨兵错误、fmt.Errorf 包装哨兵错误附加动态信息、自定义 BizError 结构体携带错误码。调用方通过 errors.Is 和 errors.As 进行分层断言,这是 Go 业务错误处理的标准模式。