2. 基础错误模式:函数返回 error、if err != nil 模式、多返回值中的错误处理
Go 语言在错误处理上采用了一种显式且简洁的设计哲学:错误就是值(Error is a value)。与许多语言使用异常(Exception)不同,Go 要求开发者将错误作为函数返回值的正常组成部分,并在调用处立即检查。这种模式虽然增加了少量代码量,但极大地提升了代码的可读性和控制力。
2.1 核心模式:函数返回 error
在 Go 中,error 是一个预定义的接口类型:
type error interface {
Error() string
}
任何实现了 Error() string 方法的类型都可以作为错误值传递。函数通过将 error 作为最后一个返回值,向调用方报告执行结果。
| 特性 | 说明 |
|---|---|
| 返回值位置 | error 总是作为多返回值中的最后一个 |
| 成功约定 | 函数执行成功时返回 nil |
| 失败约定 | 函数执行失败时返回非 nil 的 error 值 |
| 零值语义 | nil 是 error 接口的零值,表示无错误 |
关键原则: 永远不要忽略错误返回值。即使你认为某个函数“不可能失败”,也要显式处理或使用
_ 忽略(但需谨慎)。
2.2 if err != nil 模式
这是 Go 代码中最常见的控制流模式。调用函数后,立即检查返回的 error 是否为 nil:
result, err := doSomething()
if err != nil {
// 处理错误:记录日志、返回错误、重试等
log.Printf("doSomething 失败: %v", err)
return err
}
// 继续处理 result
这种模式的优势在于:
- 显式性:错误路径和正常路径在代码层面清晰分离
- 局部性:错误处理紧邻错误产生点,便于阅读和维护
- 可组合性:多个错误检查可以串联,形成清晰的执行流
常见陷阱: 不要使用
if err != nil { panic(err) } 来替代错误返回。在业务代码中,panic 只应用于真正的不可恢复异常(如数组越界),而非常规业务错误。
2.3 多返回值中的错误处理
Go 函数支持返回多个值,这使得错误可以与正常结果共存。典型的模式是:
func ReadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %w", err)
}
cfg, err := parseConfig(data)
if err != nil {
return nil, fmt.Errorf("解析配置文件失败: %w", err)
}
return cfg, nil
}
调用方可以同时接收结果和错误:
cfg, err := ReadConfig("/etc/app/config.yaml")
if err != nil {
// 处理错误,此时 cfg 为 nil
return err
}
// 此时 cfg 有效,可以安全使用
| 场景 | 返回值模式 | 示例 |
|---|---|---|
| 查询/读取 | (*T, error) |
user, err := FindUser(id) |
| 写入/操作 | (error) |
err := SaveUser(user) |
| 带状态的操作 | (T, bool, error) |
val, ok, err := TryGet(key) |
2.4 错误处理流程可视化
下图展示了 Go 中典型的错误处理流程,从函数调用到错误传播的完整路径:
2.5 实战中的错误处理模式
在业务代码中,if err != nil 模式会频繁出现。以下是几种常见的处理策略:
- 直接返回:当前函数无法处理,将错误原样返回给上层
- 包装后返回:使用
fmt.Errorf("上下文: %w", err)添加上下文信息 - 降级处理:使用默认值替代,记录错误但不中断流程
- 重试:对于临时性错误,进行有限次数的重试
最佳实践: 在业务层中,尽量使用
fmt.Errorf 的 %w 动词包装错误,保留原始错误链。这样上层可以使用 errors.Is 和 errors.As 进行错误类型判断,实现精细化的错误处理。
2.6 多返回值错误处理的常见误区
| 误区 | 错误示例 | 正确做法 |
|---|---|---|
| 忽略错误 | result, _ := doSomething() |
显式检查 err,即使确定不会失败也要处理 |
| 错误时使用返回值 | result, err := doSomething(); if err != nil { return result } |
错误时返回零值,不要使用可能无效的结果 |
| 重复检查 | if err != nil { return err } else { ... } |
使用 if err != nil { return err } 提前返回,避免 else 嵌套 |
| 错误信息模糊 | return errors.New("error") |
提供有上下文的信息:fmt.Errorf("读取用户 %d 失败: %w", id, err) |