4. 错误码设计原则:HTTP 状态码类比、业务错误码分类、错误码的层级结构

在构建高可观测性的 Go 服务时,错误码不仅仅是“错误编号”,它是服务间通信的契约、故障排查的索引、以及审计追踪的锚点。本章从三个维度拆解错误码的设计原则:HTTP 状态码类比(建立通用语义)、业务错误码分类(划分错误域)、层级结构(组织编码空间)。

4.1 HTTP 状态码类比:建立通用语义

HTTP 状态码是业界最成熟的错误分类体系。将业务错误码与 HTTP 状态码进行语义映射,可以降低跨团队、跨服务的认知成本。核心映射关系如下:

HTTP 状态码 语义 业务错误码映射场景 Go 审计关注点
2xx (成功) 请求正常处理 业务成功,无需错误码 审计日志中不应记录为错误
4xx (客户端错误) 请求方问题 参数校验失败、权限不足、资源不存在 检查是否暴露了内部实现细节
5xx (服务端错误) 服务方问题 数据库超时、依赖服务不可用、内部断言失败 必须记录完整堆栈,禁止吞没
审计基线要点: 业务错误码的“大类”必须与 HTTP 状态码的语义对齐。例如,参数错误不应返回 5xx 类错误码,内部服务不可用不应返回 4xx 类错误码。违反此原则将导致监控告警误报,属于 严重 级别缺陷。
HTTP 状态码与业务错误码语义映射 HTTP 状态码 → 业务错误码语义映射 2xx 成功 业务成功,无错误码 4xx 客户端错误 参数/权限/资源问题 5xx 服务端错误 内部故障/依赖不可用 审计规则:错误码大类必须与 HTTP 语义对齐 ❌ 参数校验失败返回 5xxxx → 语义错位,告警误报 ✅ 参数校验失败返回 4xxxx → 语义正确,快速定位 ✅ 数据库超时返回 5xxxx → 语义正确,触发告警

4.2 业务错误码分类:划分错误域

业务错误码不能是扁平的一维列表,必须按 错误域 进行分类。常见的分类维度包括:

  • 系统级错误 (SYS):基础设施故障、网络超时、磁盘 I/O 错误、内存不足。
  • 应用级错误 (APP):配置错误、依赖服务调用失败、内部状态异常、断言失败。
  • 业务逻辑错误 (BIZ):参数校验失败、权限不足、资源不存在、业务规则冲突。
  • 安全审计错误 (SEC):认证失败、签名校验失败、越权访问、敏感操作未授权。
错误域 错误码前缀 典型场景 审计日志级别
系统级 (SYS) 5xxxx 数据库连接池耗尽、gRPC 调用超时 ERROR
应用级 (APP) 5xxxx 配置加载失败、内部状态机异常 ERROR
业务逻辑 (BIZ) 4xxxx 用户不存在、余额不足、订单状态冲突 WARN
安全审计 (SEC) 4xxxx Token 过期、签名不匹配、越权操作 WARN / ERROR
设计建议: 在 Go 代码中,建议使用 const 块定义错误码,并按照错误域分组。例如:ErrCodeSysDBTimeout = 50001、ErrCodeBizUserNotFound = 40401。避免在代码中直接使用魔法数字。

4.3 错误码的层级结构:组织编码空间

错误码的层级结构决定了编码空间的扩展性和可读性。推荐使用 “大类 + 中类 + 小类” 的三级结构:

  • 第一级(大类):1 位数字,对应 HTTP 语义(4 或 5)。
  • 第二级(中类):2 位数字,对应错误域(00~99)。
  • 第三级(小类):2 位数字,对应具体错误(00~99)。

编码格式示例:4 01 01 → 4(客户端错误)+ 01(参数校验域)+ 01(必填参数缺失)。

错误码三级层级结构 错误码三级层级结构(示例:4 01 01) 4 第一级:大类 客户端错误 01 第二级:中类 参数校验域 01 第三级:小类 必填参数缺失 编码空间容量 大类:2 种(4xx / 5xx) 中类:每种大类下最多 100 个错误域 小类:每个错误域下最多 100 个具体错误

4.4 Go 代码中的错误码断言实践

在 Go 审计中,错误码断言的核心是验证错误码的 层级一致性 和 语义正确性。以下是一个符合层级结构的错误码定义示例:

// 错误码定义:遵循 4/5 + 两位域 + 两位具体错误
const (
    // ===== 4xx 客户端错误 =====
    // 参数校验域 (01)
    ErrCodeParamRequired    = 40101 // 必填参数缺失
    ErrCodeParamInvalid     = 40102 // 参数格式错误
    ErrCodeParamOutOfRange  = 40103 // 参数超出范围

    // 权限校验域 (02)
    ErrCodeAuthTokenExpired = 40201 // Token 已过期
    ErrCodeAuthSignInvalid  = 40202 // 签名校验失败
    ErrCodeAuthForbidden    = 40203 // 无操作权限

    // ===== 5xx 服务端错误 =====
    // 数据库域 (01)
    ErrCodeDBTimeout        = 50101 // 数据库查询超时
    ErrCodeDBConnectionLost = 50102 // 数据库连接丢失
    ErrCodeDBDuplicateKey   = 50103 // 唯一键冲突

    // 依赖服务域 (02)
    ErrCodeDepServiceDown   = 50201 // 依赖服务不可用
    ErrCodeDepServiceTimeout= 50202 // 依赖服务调用超时
)
审计红线: 禁止在错误码中使用 0 或 -1 作为有效错误码。禁止将不同错误域的错误码混用(例如将参数校验错误归入 5xxxx 域)。违反者将导致审计失败,需立即修复。

4.5 错误码断言审计清单

在 Go 代码审计中,针对错误码设计原则,需要逐项检查以下要点:

  1. 语义对齐检查:所有 4xxxx 错误码是否确实由客户端请求引发?所有 5xxxx 错误码是否确实由服务端内部故障引发?
  2. 层级完整性检查:错误码是否包含完整的三级结构?是否存在仅使用两位数字的错误码?
  3. 错误域隔离检查:不同错误域的错误码是否使用了不同的中类编号?是否存在跨域复用?
  4. 常量命名检查:错误码常量是否按照 ErrCode + 域 + 具体描述 的格式命名?是否使用了魔法数字?
  5. 审计日志关联检查:错误码是否在审计日志中完整记录?日志中是否包含错误码对应的中英文描述?
审计工具集成: 建议在 CI/CD 流水线中集成自定义 linter,自动检查错误码定义是否符合层级结构。例如:golangci-lint 配合自定义规则,禁止出现 400、500 等不符合三级结构的错误码。