4. 错误码分类设计:HTTP状态码映射、业务错误码范围划分、预留区间策略

错误码分类是错误码断言体系的基础骨架。一个设计良好的分类方案,能让开发者在看到错误码的瞬间就判断出:错误发生在哪一层、严重程度如何、应该由谁处理。本章从三个维度展开:HTTP 状态码映射(对外协议对齐)、业务错误码范围划分(内部领域隔离)、预留区间策略(未来扩展保障)。

4.1 三层错误码模型

在 Go 微服务架构中,错误码通常分为三层,每层承担不同的职责:

层级 名称 示例 使用者
L1 HTTP 状态码 400, 401, 403, 404, 500 网关、客户端
L2 业务错误码 10001, 20003, 30010 服务间调用、SDK
L3 内部断言码 ASSERT_ORDER_LOCK_FAILED 开发、运维、测试
设计原则:L1 面向外部协议,L2 面向业务逻辑,L3 面向内部诊断。三层之间通过映射表关联,但各自独立演进。

4.2 HTTP 状态码映射策略

HTTP 状态码是错误码体系对外的“第一张脸”。映射策略需要平衡语义准确性和客户端兼容性:

  • 4xx 系列:客户端错误。包括参数校验失败(400)、认证失败(401)、权限不足(403)、资源不存在(404)、请求冲突(409)、频率限制(429)。
  • 5xx 系列:服务端错误。包括内部错误(500)、服务不可用(503)、网关超时(504)。
  • 2xx 系列:成功。业务层即使有逻辑错误(如余额不足),只要请求本身合法,仍返回 200,错误信息放在业务错误码中。
常见误区:不要将业务逻辑错误(如“订单已取消”)映射为 500。500 只应表示服务端无法处理请求,而非业务规则拒绝。业务拒绝应使用 200 + 业务错误码,或 409/422 等精确状态码。

推荐映射表(Go 项目通用版):

HTTP 状态码 含义 典型业务场景 断言处理方式
200 OK 请求成功,业务正常 正常返回
400 Bad Request 参数格式错误、必填字段缺失 断言失败,返回客户端提示
401 Unauthorized Token 过期、签名错误 断言失败,触发重新登录
403 Forbidden 无操作权限 断言失败,记录越权日志
404 Not Found 资源 ID 不存在 断言失败,返回空或降级
409 Conflict 数据版本冲突、重复创建 断言失败,触发重试或人工介入
422 Unprocessable Entity 业务规则校验失败(如余额不足) 断言失败,返回业务提示
429 Too Many Requests 接口限流 断言失败,等待后重试
500 Internal Server Error 数据库异常、panic 未恢复 断言失败,触发告警
503 Service Unavailable 依赖服务宕机、熔断开启 断言失败,触发降级

4.3 业务错误码范围划分

业务错误码是 L2 层的核心,通常使用 6 位或 8 位数字,按模块、功能、错误类型分段。以下是一个经过多个生产项目验证的划分方案:

错误码范围 所属模块 说明
100000 - 199999 用户中心 注册、登录、鉴权、用户信息
200000 - 299999 订单服务 创建订单、支付、退款、取消
300000 - 399999 商品服务 库存、价格、分类、搜索
400000 - 499999 支付服务 渠道对接、对账、退款
500000 - 599999 通知服务 短信、邮件、推送
600000 - 699999 基础组件 缓存、数据库、消息队列
700000 - 799999 预留扩展区 未来新增模块
800000 - 899999 内部断言专用 开发期断言、测试桩
900000 - 999999 全局公共错误 参数校验、限流、熔断
划分要点:每个模块预留 10 万位空间,前两位标识模块,后四位标识具体错误。例如 100001 表示“用户不存在”,100002 表示“密码错误”。模块间不重叠,便于日志聚合和告警分组。

4.4 预留区间策略

预留区间是错误码体系长期可维护性的关键。很多项目在初期只定义了当前用到的错误码,半年后新模块加入时发现码段已乱,不得不重构。以下是三种经过验证的预留策略:

  • 模块级预留:每个模块分配一个连续区间(如 100000-199999),即使当前只用了前 10 个码,剩余空间也不允许其他模块占用。
  • 版本级预留:在错误码中嵌入版本位(如 1 位版本号 + 5 位业务码),支持未来版本扩展而不破坏现有映射。
  • 断言级预留:专门划出一个区间(如 800000-899999)给内部断言使用,这些错误码不会出现在生产环境的对外接口中,仅用于单元测试和集成测试的断言验证。
避坑提示:不要使用“顺序递增”方式分配错误码。例如用户模块从 10001 开始,订单模块从 10002 开始,这种方案在模块数量超过 10 个后必然冲突。务必使用“区间跳跃”分配法,每个模块至少预留 1000 个码位。

4.5 错误码分类架构图

下图展示了三层错误码模型与 HTTP 状态码映射、业务范围划分、预留区间之间的整体关系:

错误码分类架构总览 L1:HTTP 状态码映射层 200 / 400 / 401 / 403 / 404 / 409 / 422 / 429 / 500 / 503 L2:业务错误码范围划分 用户中心 100000-199999 订单服务 200000-299999 商品服务 300000-399999 支付服务 400000-499999 通知服务 500000-599999 基础组件 600000-699999 L3:预留区间策略 模块级预留(700000-799999) | 版本级预留(嵌入版本位) | 断言级预留(800000-899999) 三层映射关系 L1 对外协议 → L2 业务语义 → L3 扩展保障,三层通过映射表关联,各自独立演进

4.6 错误码断言中的分类实践

在 Go 代码中,错误码分类直接影响断言函数的实现。以下是一个基于分类的断言示例:

// 定义错误码分类常量
const (
    // 模块区间
    ErrCodeUserNotFound     = 100001
    ErrCodeUserPasswordWrong = 100002
    ErrCodeOrderCreateFailed = 200001
    ErrCodeOrderNotPaid      = 200002

    // 预留区间(内部断言专用)
    ErrCodeAssertTestOnly    = 800001
)

// 断言函数:根据错误码分类执行不同逻辑
func AssertErrorCode(err error, expectedCode int) bool {
    if err == nil {
        return false
    }

    // 提取业务错误码
    bizCode := ExtractBizCode(err)

    // 分类判断
    switch {
    case bizCode >= 100000 && bizCode < 200000:
        // 用户模块错误:记录审计日志
        log.Printf("[UserModule] assertion failed: code=%d", bizCode)
    case bizCode >= 200000 && bizCode < 300000:
        // 订单模块错误:触发订单补偿流程
        triggerOrderCompensation(bizCode)
    case bizCode >= 800000 && bizCode < 900000:
        // 内部断言错误:仅测试环境生效
        if !isTestEnv() {
            return false
        }
    }

    return bizCode == expectedCode
}
实战建议:在 Go 项目中,将错误码分类定义放在独立的 internal/errcode 包中,所有模块引用同一个分类表。不要在各自模块中独立定义错误码,否则会导致码段冲突和重复。

4.7 分类设计的常见反模式

以下是实际项目中容易出现的错误码分类问题,以及对应的改进方案:

反模式 问题描述 改进方案
扁平化枚举 所有错误码放在一个枚举中,无模块区分 按模块划分区间,每个模块独立文件
HTTP 状态码与业务码混用 将 404 直接作为业务错误码返回 L1 和 L2 分离,通过映射表关联
无预留区间 新模块加入时只能插入已有区间 提前分配 700000-799999 作为扩展区
错误码重复使用 不同模块使用了相同的数字码 建立全局注册表,CI 阶段校验唯一性
断言码与生产码共用 测试断言错误码出现在线上日志 划分独立断言区间,生产环境过滤

4.8 分类设计的演进路线

错误码分类不是一次性设计,而是随着项目规模增长持续演进。推荐以下路线:

  • 阶段一(MVP):只划分 3-5 个模块区间,每个区间预留 1000 个码位,不做版本位。
  • 阶段二(增长期):模块超过 10 个时,引入区间跳跃分配法,每个模块至少预留 10000 个码位。
  • 阶段三(成熟期):引入版本位或服务 ID 位,支持多版本共存和灰度发布。
  • 阶段四(平台期):建立自动化错误码注册中心,CI 阶段自动检测冲突和重复。
核心结论:错误码分类设计的本质是“为未来留出空间”。HTTP 状态码映射解决对外协议对齐问题,业务错误码范围划分解决内部领域隔离问题,预留区间策略解决长期扩展问题。三者结合,才能构建一个可维护、可扩展的错误码断言体系。