错误处理¶
学习目标¶
学完本章后,学习者应该能够:
- 理解 Go 中
error的设计和返回约定。 - 正确使用
errors.Is、errors.As和错误包装。 - 区分普通错误、业务错误、系统错误、panic 和 recover。
- 能设计后端服务中的错误码和错误边界。
error 类型¶
Go 的错误是普通值:
函数通常把错误作为最后一个返回值:
这种显式错误处理让失败路径清楚可见。
错误包装¶
包装错误时使用 %w,保留错误链:
调用方可以用 errors.Is 判断哨兵错误:
也可以用 errors.As 提取特定错误类型:
panic 与 recover¶
panic 表示程序进入异常状态,不应用于普通业务错误。
适合 panic 的场景:
- 程序启动时配置缺失且无法继续。
- 违反不可恢复的不变量。
- 测试中快速失败。
HTTP 服务中可以用中间件 recover,避免单个请求 panic 导致整个进程退出,但 recover 后要记录堆栈。
业务错误与系统错误¶
业务错误是预期内失败:
- 用户不存在。
- 参数不合法。
- 余额不足。
- 权限不足。
系统错误是基础设施或程序异常:
- 数据库连接失败。
- Redis 超时。
- 网络错误。
- JSON 编码失败。
两者应该在日志、错误码和告警上区别对待。
Go 后端实际应用例子¶
例子一:定义业务错误¶
type AppError struct {
Code string
Message string
}
func (e *AppError) Error() string {
return e.Code + ": " + e.Message
}
var ErrUserNotFound = &AppError{
Code: "USER_NOT_FOUND",
Message: "user not found",
}
业务错误可以映射到稳定的 HTTP 状态码和响应体。
例子二:Repository 层保留底层错误¶
func (r *UserRepo) Get(ctx context.Context, id int64) (User, error) {
user, err := r.query(ctx, id)
if err != nil {
return User{}, fmt.Errorf("query user by id: %w", err)
}
return user, nil
}
包装错误时补充上下文,但不要在每一层重复打日志。通常在边界层统一记录。
常见误区¶
- 误区一:错误信息越长越好。
错误要有上下文,但不要泄露敏感信息,也不要重复包装到无法阅读。
- 误区二:每一层都记录一次错误日志。
这会造成重复日志。通常在请求边界或任务边界统一记录,内部层返回错误即可。
- 误区三:用 panic 处理业务错误。
业务错误是正常分支,应该显式返回。panic 会破坏控制流,也容易漏掉资源释放和观测信息。
线上问题案例¶
某服务把数据库唯一键冲突直接返回给前端,响应中包含表名和索引名。后来发现这既不友好,也暴露内部结构。
修复方式是在 repository 层识别唯一键冲突,在 service 层转换成业务错误,例如 API_KEY_NAME_EXISTS,handler 层映射为 409。
实战任务¶
设计一个 API Key 创建接口的错误处理:
- 参数不合法返回 400。
- 名称重复返回 409。
- 数据库错误返回 500。
- 日志中保留底层错误。
- 响应中不暴露数据库细节。
参考答案
可以定义业务错误码,例如 INVALID_ARGUMENT、API_KEY_NAME_EXISTS。repository 层识别数据库唯一键冲突并返回业务可识别错误,service 层继续包装上下文,handler 层用 errors.Is 或 errors.As 判断错误类型并映射 HTTP 状态码。
日志记录完整错误链,响应只返回稳定错误码和用户可理解信息。数据库连接失败、SQL 语法错误等系统错误统一返回 500,并触发错误率监控。
面试题¶
1. Go 为什么把 error 设计成普通返回值?¶
参考答案
Go 把错误设计成普通值,让失败路径显式出现在代码中。调用方必须决定如何处理错误,这有助于写出可读、可控的后端服务。
代价是代码中会有较多 if err != nil,但好处是控制流清晰,不容易隐藏异常路径。
2. %w、errors.Is 和 errors.As 分别有什么作用?¶
参考答案
%w 用于包装错误并保留错误链。errors.Is 用于判断错误链中是否包含某个目标错误,常用于哨兵错误。errors.As 用于从错误链中提取某个具体错误类型。
它们让我们既能补充上下文,又能让上层保留错误判断能力。
3. panic 适合用在什么场景?¶
参考答案
panic 适合不可恢复的程序错误,例如启动时关键配置缺失、违反内部不变量,或测试中快速失败。不适合普通业务错误。
Web 服务中可以在中间件 recover,记录堆栈并返回 500,避免单个请求 panic 导致进程退出。