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 项目中如何按领域分层组织错误码常量,确保边界清晰、扩展灵活:

错误码常量组织架构 errcode 包 用户领域 (1000-1999) 订单领域 (2000-2999) 支付领域 (3000-3999) ErrUnknown (1000) ErrUnauthorized (1100) ErrUserNotFound (1200) ErrDuplicateUser (1202) ErrOrderNotFound (2000) ErrOrderCancelled (2001) ErrOrderExpired (2002) ErrInsufficientStock (2003) PaySuccess (3000) PayFailed (3002) PayAmountMismatch (3003) PayDuplicateOrder (3005) 领域错误码区间分配原则 每个领域独立分配 1000 个编码空间,预留扩展位,避免跨领域冲突

4.6 错误码常量最佳实践

  1. 集中定义,按领域分包: 每个限界上下文拥有独立的 errcode 包,避免全局大而全的错误码文件。
  2. 预留区间,避免紧耦合: 每个领域分配 1000 个编码空间(如 1000-1999),即使当前只用 10 个,也为未来扩展留足余地。
  3. 注释即文档: 每个常量上方添加 Go 注释,说明错误含义、触发条件、建议处理方式。
  4. 禁止跨包引用魔法数字: 所有外部调用方必须通过 errcode.ErrXxx 引用,不得直接使用 1001。
  5. 配合 iota 慎用: 数值型错误码建议手动赋值,避免使用 iota 自动递增导致重构时编码漂移。
领域边界防御要点: 错误码常量是领域语言的“原子词汇”。在 Go 中通过 const 定义后,编译器会在编译期检查拼写错误,运行时通过常量名即可追溯领域含义。这是消除魔法数字、提升代码安全性的第一道防线。