3. 错误码设计原则:HTTP 状态码类比、业务错误码分类、错误码与错误消息分离
错误码设计是构建可观测、可诊断、可维护的 Go 服务的基础。本章从三个核心维度展开:HTTP 状态码类比帮助理解分层语义,业务错误码分类提供可落地的编码体系,错误码与错误消息分离确保接口的稳定性和国际化能力。
3.1 HTTP 状态码类比:从网络层到业务层
HTTP 状态码的分层设计(1xx/2xx/3xx/4xx/5xx)是业界公认的最佳实践。业务错误码可以借鉴其前缀分类 + 数字编号的结构,但需要针对业务场景做适配。
| HTTP 状态码范围 | 语义 | 业务错误码类比 | Go 示例场景 |
|---|---|---|---|
| 2xx (成功) | 请求被正常处理 | 0 / 200000 (成功) | 查询用户信息成功 |
| 4xx (客户端错误) | 请求参数、权限、资源不存在 | 400xxx ~ 403xxx | 参数校验失败、Token 过期 |
| 5xx (服务端错误) | 系统内部故障、依赖超时 | 500xxx ~ 503xxx | 数据库连接池耗尽、下游 RPC 超时 |
| 1xx (信息) | 协议升级、中间状态 | 100xxx (重定向/重试) | 请求需要重定向到新节点 |
关键原则:业务错误码不应直接复用 HTTP 状态码的数字,而是采用独立编码体系,仅在语义上对齐。例如 HTTP 400 对应业务错误码 400001(参数缺失),而非直接返回 400。
3.2 业务错误码分类:三段式编码结构
推荐使用 6 位数字 的三段式结构:ABC DEF,其中 A 为错误等级,BC 为模块标识,DEF 为具体错误编号。
// 错误码结构:A BC DEF
// A: 错误等级 (1=系统级, 2=服务级, 3=业务级, 4=参数级)
// BC: 模块标识 (01=用户, 02=订单, 03=支付, 04=搜索)
// DEF: 具体错误编号 (001~999)
const (
// 系统级 - 通用
ErrSystemInternal = 100001 // 系统内部错误
ErrSystemTimeout = 100002 // 系统超时
// 服务级 - 用户模块
ErrUserNotFound = 201001 // 用户不存在
ErrUserAuthFailed = 201002 // 认证失败
// 业务级 - 订单模块
ErrOrderNotExist = 302001 // 订单不存在
ErrOrderStatusLock = 302002 // 订单状态锁定
// 参数级 - 搜索模块
ErrSearchParamEmpty = 403001 // 搜索关键词为空
ErrSearchParamLen = 403002 // 搜索词过长
)
编码建议:错误码的模块标识(BC 段)应保持与微服务名称或目录结构一致,便于通过错误码快速定位所属服务。例如搜索服务所有错误码以
04 开头。
3.3 错误码与错误消息分离
错误码是机器可读的稳定标识,错误消息是人类可读的动态描述。两者必须分离,理由如下:
- 国际化支持:同一错误码可对应中、英、日等多语言消息,无需修改代码。
- 接口稳定性:错误码一旦发布即不可变更,错误消息可随时优化措辞。
- 日志聚合:按错误码聚合告警比按文本匹配更可靠。
- 客户端兼容:客户端仅依赖错误码做逻辑判断,不解析消息文本。
// 错误码与消息分离示例
type ErrorCode int
func (e ErrorCode) Message(lang string) string {
m := map[ErrorCode]map[string]string{
ErrUserNotFound: {
"zh": "用户不存在",
"en": "user not found",
"ja": "ユーザーが見つかりません",
},
ErrOrderNotExist: {
"zh": "订单不存在",
"en": "order does not exist",
},
}
if msg, ok := m[e][lang]; ok {
return msg
}
return m[e]["en"] // 默认英文
}
常见反模式:将错误消息硬编码在错误码定义中,例如
ErrUserNotFound = errors.New("用户不存在")。这会导致无法切换语言、无法统一管理、无法在日志中按错误码聚合。
3.4 错误码设计全景图
下图展示了从错误码定义到最终响应的完整链路,强调分层隔离与消息分离的核心设计。
3.5 错误码分类速查表
以下表格汇总了常见业务场景下的错误码分类建议,可直接作为团队编码规范参考。
| 错误等级 (A) | 模块 (BC) | 错误码示例 | 说明 |
|---|---|---|---|
| 1 (系统级) | 00 (通用) | 100001 | 系统内部错误,需立即告警 |
| 1 (系统级) | 00 (通用) | 100002 | 系统超时,需检查依赖 |
| 2 (服务级) | 01 (用户) | 201001 | 用户不存在 |
| 2 (服务级) | 01 (用户) | 201002 | 认证失败 |
| 3 (业务级) | 02 (订单) | 302001 | 订单不存在 |
| 3 (业务级) | 02 (订单) | 302002 | 订单状态锁定 |
| 4 (参数级) | 03 (搜索) | 403001 | 搜索关键词为空 |
| 4 (参数级) | 03 (搜索) | 403002 | 搜索词过长 |
实战建议:在 Go 项目中,将错误码定义放在独立的
pkg/errcode 包中,消息映射放在 pkg/errcode/message 子包。错误码使用 const + iota 或手动编号,确保发布后永不修改已存在的错误码值。