3. 错误码设计原则:错误码分类、HTTP风格错误码、业务错误码、错误码范围规划
错误码是Go服务中错误断言与故障定位的“第一性信息”。设计一套清晰、可扩展的错误码体系,能显著降低跨团队协作时的认知成本,并让断言逻辑在代码层面保持可读性与可维护性。本章从分类、风格、范围三个维度展开。
3.1 错误码分类:按职责分层
错误码不应是扁平的一维列表,而应按照“系统层 → 通信层 → 业务层”进行分层设计。每一层关注不同的故障域,断言时也能按层快速缩小排查范围。
| 层级 | 职责 | 典型错误码前缀 | 示例 |
|---|---|---|---|
| 系统层 | 基础设施、运行时、依赖服务故障 | SYS_ | SYS_DB_TIMEOUT, SYS_DISK_FULL |
| 通信层 | 网络协议、序列化、RPC调用异常 | NET_ | NET_HTTP_500, NET_GRPC_UNAVAILABLE |
| 业务层 | 业务规则校验、状态冲突、资源不存在 | BIZ_ | BIZ_ORDER_EXPIRED, BIZ_USER_NOT_FOUND |
设计建议: 在Go项目中,建议将错误码定义放在独立的
pkg/errcode 包中,按层级拆分为 sys.go、net.go、biz.go 三个文件,便于维护与代码生成。
3.2 HTTP风格错误码:与RESTful语义对齐
对于对外暴露的HTTP API,错误码应尽量复用HTTP状态码的语义,避免自创一套与标准脱节的编码。这能降低前端与客户端的理解成本,也便于API网关做统一熔断与重试。
- 4xx 系列: 客户端错误,如参数校验失败(400)、鉴权失败(401)、资源不存在(404)。
- 5xx 系列: 服务端错误,如内部异常(500)、服务不可用(503)、上游超时(504)。
- 扩展规则: 当需要更细粒度的业务区分时,可在HTTP状态码后附加业务子码,例如
40001表示“订单金额校验失败”,40002表示“库存不足”。
Go代码示例: 定义带子码的错误结构体
// errcode/errcode.go
type ErrorCode struct {
HTTPStatus int // 对应HTTP状态码
SubCode int // 业务子码
Message string // 用户可读信息
}
var (
ErrOrderAmount = &ErrorCode{HTTPStatus: 400, SubCode: 40001, Message: "订单金额校验失败"}
ErrStockShort = &ErrorCode{HTTPStatus: 400, SubCode: 40002, Message: "库存不足"}
ErrInternal = &ErrorCode{HTTPStatus: 500, SubCode: 50000, Message: "内部服务异常"}
)
3.3 业务错误码:领域内聚、语义自明
业务错误码是断言逻辑中最常接触的部分。设计时应遵循“领域内聚”原则:每个业务模块(如订单、支付、用户)拥有独立的错误码区间,且错误码名称应直接反映业务语义,而非模糊的数字编号。
- 模块前缀: 使用模块缩写作为前缀,如
ORD_(订单)、PAY_(支付)、USR_(用户)。 - 语义化命名: 避免
ORD_001这种无意义编号,改用ORD_AMOUNT_MISMATCH、ORD_STATUS_CONFLICT。 - 断言友好: 错误码应支持
errors.Is或errors.As断言,而非仅靠字符串匹配。
避坑提示: 不要将错误码定义为纯数字常量(如
1001),这会导致代码中到处出现魔法数字,且无法利用Go的类型安全进行断言。始终使用具名常量或结构体指针。
3.4 错误码范围规划:预留扩展空间
错误码范围规划是长期演进的关键。一个常见的反模式是:所有错误码从1开始连续编号,导致新模块加入时无处安插,最终变成混乱的“大锅烩”。
推荐按模块与层级分配固定数值区间,并预留充足的扩展段:
| 区间范围 | 归属模块 | 说明 |
|---|---|---|
| 10000 – 19999 | 系统层(SYS) | 数据库、缓存、磁盘等基础设施 |
| 20000 – 29999 | 通信层(NET) | HTTP、gRPC、MQ等网络通信 |
| 30000 – 39999 | 用户模块(USR) | 登录、注册、权限 |
| 40000 – 49999 | 订单模块(ORD) | 创建、支付、退款 |
| 50000 – 59999 | 支付模块(PAY) | 渠道、对账、风控 |
| 90000 – 99999 | 预留扩展段 | 未来新模块或临时应急码 |
实践建议: 在项目初期就定义好区间分配表,并写入
CONTRIBUTING.md 或 errcode/README.md。每次新增错误码时,必须从对应区间内分配,避免跨区借用。
3.5 错误码设计全景图
下图展示了从分层分类到范围规划的整体设计思路,帮助你在实际项目中快速落地。
3.6 本章小结
- 错误码按系统层、通信层、业务层分层,每层独立前缀与断言域。
- 对外API优先复用HTTP状态码语义,必要时附加业务子码。
- 业务错误码使用模块前缀 + 语义化命名,避免魔法数字。
- 提前规划数值区间,为未来模块预留扩展空间。
- 所有错误码最终通过
errors.Is/errors.As进行断言,而非字符串匹配。