4. 定义领域错误码:使用 const + int/string 避免魔法数字
在 Go 领域边界防御中,错误码是领域语言的核心组成部分。直接使用魔法数字(如 1001、"E001")散落在业务逻辑中,会导致代码难以维护、排查困难、跨团队协作成本激增。本节将系统讲解如何通过 const + int/string 定义领域错误码常量,彻底消除魔法数字。
4.1 魔法数字的危害
魔法数字在代码中表现为无上下文含义的硬编码值,其危害在领域边界场景中尤为突出:
| 问题 | 具体表现 | 影响范围 |
|---|---|---|
| 可读性差 | if errCode == 1001 无法表达业务语义 |
代码审查、新人上手 |
| 维护困难 | 修改错误码需全局搜索替换,极易遗漏 | 版本迭代、重构 |
| 跨服务耦合 | 不同服务各自定义魔法数字,映射关系混乱 | 微服务通信、API 网关 |
| 调试效率低 | 日志中仅输出数字,无法快速定位领域含义 | 线上排障、告警响应 |
领域边界陷阱: 魔法数字在单体应用中尚可容忍,但在领域驱动设计(DDD)的限界上下文之间,错误码是跨聚合、跨服务的契约。一旦使用魔法数字,领域语言的精确性将彻底丧失,边界防御形同虚设。
4.2 使用 const + int 定义数值型错误码
对于需要数值化传输(如 HTTP 状态码扩展、gRPC 错误码)的场景,推荐使用 const + int 模式:
package errcode
// 用户领域错误码:1000-1999
const (
// 通用错误
ErrSuccess int = 0
ErrUnknown int = 1000
ErrInternal int = 1001
ErrBadRequest int = 1002
// 认证相关
ErrUnauthorized int = 1100
ErrTokenExpired int = 1101
ErrTokenInvalid int = 1102
// 用户资源
ErrUserNotFound int = 1200
ErrUserDisabled int = 1201
ErrDuplicateUser int = 1202
)
// 订单领域错误码:2000-2999
const (
ErrOrderNotFound int = 2000
ErrOrderCancelled int = 2001
ErrOrderExpired int = 2002
ErrInsufficientStock int = 2003
)
编码规范: 建议按领域模块划分错误码区间(如用户 1000-1999、订单 2000-2999),并在包注释中明确区间分配表。这有助于快速定位错误来源,避免跨领域冲突。
4.3 使用 const + string 定义语义型错误码
对于需要人类可读、日志友好、支持多语言映射的场景,const + string 是更优选择:
package errcode
// 支付领域错误码(语义化字符串)
const (
PaySuccess string = "PAY_SUCCESS"
PayPending string = "PAY_PENDING"
PayFailed string = "PAY_FAILED"
PayAmountMismatch string = "PAY_AMOUNT_MISMATCH"
PayChannelUnavailable string = "PAY_CHANNEL_UNAVAILABLE"
PayDuplicateOrder string = "PAY_DUPLICATE_ORDER"
)
// 库存领域错误码
const (
StockOK string = "STOCK_OK"
StockInsufficient string = "STOCK_INSUFFICIENT"
StockLockFailed string = "STOCK_LOCK_FAILED"
StockReserveExpired string = "STOCK_RESERVE_EXPIRED"
)
选择依据: 若错误码需要参与数值运算、位运算或作为 protobuf 枚举值,优先使用
int;若错误码主要用于日志输出、API 响应体、多语言国际化,优先使用 string。两种模式可在同一项目中混合使用,但需保持领域内一致性。
4.4 int vs string 错误码对比
| 维度 | int 错误码 | string 错误码 |
|---|---|---|
| 存储空间 | 4-8 字节,紧凑 | 可变长度,相对较大 |
| 可读性 | 需查表,较差 | 自描述,优秀 |
| 序列化效率 | 高(protobuf varint) | 中(字符串编码) |
| 多语言支持 | 需额外映射表 | 可直接作为 key 映射 |
| 位运算/组合 | 支持 | 不支持 |
| 典型场景 | gRPC 状态码、内部 RPC | RESTful API、日志聚合 |
4.5 错误码常量组织架构
下图展示了在 Go 项目中如何按领域分层组织错误码常量,确保边界清晰、扩展灵活:
4.6 错误码常量最佳实践
- 集中定义,按领域分包: 每个限界上下文拥有独立的
errcode包,避免全局大而全的错误码文件。 - 预留区间,避免紧耦合: 每个领域分配 1000 个编码空间(如 1000-1999),即使当前只用 10 个,也为未来扩展留足余地。
- 注释即文档: 每个常量上方添加 Go 注释,说明错误含义、触发条件、建议处理方式。
- 禁止跨包引用魔法数字: 所有外部调用方必须通过
errcode.ErrXxx引用,不得直接使用1001。 - 配合 iota 慎用: 数值型错误码建议手动赋值,避免使用
iota自动递增导致重构时编码漂移。
领域边界防御要点: 错误码常量是领域语言的“原子词汇”。在 Go 中通过
const 定义后,编译器会在编译期检查拼写错误,运行时通过常量名即可追溯领域含义。这是消除魔法数字、提升代码安全性的第一道防线。