Go 错误处理最佳实践:包装、判断与日志边界
清晰的错误链能够保留失败原因和业务语境,让调用方做出正确决策。Go 的 error 是显式返回值,最佳实践不是在每一层重复打印,而是逐层补充必要信息,在边界处统一记录和转换。
核心原则
- 使用 fmt.Errorf 与 %w 包装底层错误,在不丢失原因的前提下增加操作语境。
- 用 errors.Is 判断哨兵错误、用 errors.As 获取具体错误类型,避免比较错误文本。
- 定义少量稳定的领域错误,供上层决定重试、返回 404 或提示参数问题。
- 只在能够处理错误的地方恢复;无法处理时立即返回,避免继续使用不完整状态。
- 在 HTTP、消息消费等系统边界统一记录一次结构化日志,避免同一错误被重复打印。
推荐的实践步骤
仓储层可以把驱动错误包装为“查询用户”等操作语境,服务层把可识别的不存在错误转换为领域错误,接口层再映射为合适的状态码。日志应包含请求标识、操作名、耗时和完整错误链,但不要把数据库语句、令牌等敏感数据直接返回给客户端。
var ErrNotFound = errors.New("not found")
func findUser(id int64) error {
if err := repo.Find(id); err != nil {
return fmt.Errorf("find user %d: %w", id, err)
}
return nil
}
示例用于说明实现思路,实际项目还应结合所使用的框架版本、部署环境和业务约束进行调整。重要配置要进入版本管理,并在测试环境验证后再发布。
常见误区
- 通过 err.Error() 字符串匹配错误类别,会在文案变化后悄然失效。
- 每一层都记录日志再向上返回,会制造重复告警并增加排查噪声。
- 直接把内部错误完整暴露给客户端,可能泄露表名、文件路径或依赖信息。
上线前检查清单
- 确认“使用 fmt.Errorf 与 %w 包装底层错误,在不丢失原因的前提下增加操作语境”已经通过代码审查或运行验证。
- 确认“用 errors.Is 判断哨兵错误、用 errors.As 获取具体错误类型,避免比较错误文本”已经通过代码审查或运行验证。
- 确认“定义少量稳定的领域错误,供上层决定重试、返回 404 或提示参数问题”已经通过代码审查或运行验证。
- 确认“只在能够处理错误的地方恢复;无法处理时立即返回,避免继续使用不完整状态”已经通过代码审查或运行验证。
- 为失败路径、边界条件和回滚方案准备测试或演练记录。
- 上线后观察错误率、延迟和资源消耗,确认变化符合预期。
总结
Go 错误处理最佳实践:包装、判断与日志边界的关键在于把隐含假设变成可执行的约束,并通过测试、监控和复盘持续验证。先从影响最大的真实场景开始,小步调整并保留回滚能力,通常比一次性大范围改造更安全,也更容易积累可复用的工程经验。