4. 业务错误码设计原则:如何定义错误码

错误码这东西,说白了就是给开发者看的“暗号”。你在文档里写“错误码 -1 表示失败”,对接的人只能一头雾水。我见过太多项目,错误码随意乱写,最后排查问题全靠猜。今天咱们聊聊怎么设计一套靠谱的业务错误码。

4.1 错误码的核心结构

一个合格的错误码,应该让看的人一秒定位问题。我习惯的结构是 模块 + 编号 + 含义,三部分缺一不可。

推荐格式:XXX - YYYY - ZZZZ

  • 模块前缀(XXX):3~4位字母,代表功能模块。比如 USR 表示用户模块,PAY 表示支付模块。
  • 错误编号(YYYY):4位数字,从 0001 开始递增。奇数留给严重错误,偶数留给警告。
  • 含义描述:一段简短、无歧义的中文/英文描述。例如“用户不存在”。

举个我实际用过的例子:USR-1001 - 用户不存在。看到这个码,对接方立马知道是用户模块的问题,编号 1001 代表“不存在”这一具体错误。

4.2 模块划分原则

模块不能太粗,也不能太细。太粗就像把所有错误都扔进一个“通用错误”里,等于没分;太细会导致模块前缀列表膨胀到几十个,反而难记。

模块 前缀 典型错误
用户模块 USR 账号冻结、密码错误、手机号格式
支付模块 PAY 余额不足、渠道不可用、重复支付
订单模块 ORD 订单不存在、状态异常、超时取消
网络请求 NET 超时、连接失败、协议错误

我在项目中还加过一个 SYS 模块,专门放系统级错误(比如数据库连不上)。这样业务错误码和系统错误码就分清楚了。

4.3 编号设计:区间与含义

编号不要随便从 1 开始排。预留好区间,后续扩展才不会乱。我习惯用千分位划分:

  • 1xxx:参数校验错误(必填项缺失、格式错误)
  • 2xxx:业务逻辑错误(余额不足、状态冲突)
  • 3xxx:权限/认证错误( 过期、无权限)
  • 4xxx:资源不存在错误(用户、订单、商品等)
  • 5xxx:系统内部错误(数据库异常、第三方接口失败)

避坑指南:我曾经遇到过某个模块的编号区间被打乱的情况,同一个区间既放参数错误又放权限错误。排查时看到错误码,得先查文档才知道属于哪类问题。后来强制规定每个模块必须按区间分配,才彻底解决。

4.4 含义描述怎么写

描述要具体、无歧义。别写“操作失败”,写“支付密码输入错误超过3次,账户已锁定”。我见过最差的写法是 -1: error,简直灾难。

好的例子:

  • USR-2001 - 手机号已被其他账号绑定
  • PAY-3002 - 该支付渠道当日限额已超
  • ORD-1103 - 订单创建时间必须在当前时间之前

嗯,如果你做的是海外 SDK,描述最好中英文都提供。像 PAY-3002 - Daily limit exceeded for this payment method,这样对接方不用查翻译。

4.5 通用错误码 vs 业务错误码

我把它们分成两类:

类型 特点 示例
通用错误码 所有模块共用,范围固定 NET-0001 网络超时,AUTH-0001 无效
业务错误码 模块私有,随业务变化 USR-1001 用户不存在,PAY-2003 余额不足

通用错误码我建议放在一个公共文档里,业务错误码放在各模块接口文档里。这样改业务时不会影响到公共部分。

个人习惯:我还会给通用错误码预留 0000~0999 作为成功/异常状态码。比如 COM-0000 - Success,COM-0001 - Unknown error。这样所有模块都能引用,不用重复定义。

4.6 错误码的生命周期管理

错误码一旦对外发布,就别轻易删除或重编号。你可能会问:“为什么?” 你想想看,如果上游 SDṀ 缓存了旧的错误码含义,你的系统突然改掉了,排查时就会对不上。

我制定的规则是:

  1. 新增:在区间尾部追加,不能插入已有编号之间。
  2. 废弃:标记为“已废弃”,但保留在文档中,防止别人误用。
  3. 修改含义:原则上不允许。如果实在要变,必须发新版 SDK,并在 changelog 里明确说明。

4.7 错误码设计核心逻辑图

下面这张图概括了整套设计流程:

业务错误码设计核心逻辑 错误码结构:模块 + 编号 + 含义 模块:明确属于哪个功能 编号:按区间分配意义 含义:无歧义描述 编号区间示例:1xxx=参数,2xxx=业务,3xxx=权限,4xxx=不存在,5xxx=系统 新增:在区间尾部追加 废弃:标记保留,不改编号 关键:错误码一旦对外发布,不可删除、不可改含义 只能新增或废弃,废弃的旧码要永久保留在文档中

4.8 给新人的几条实战建议

  • 别偷懒:错误码文档单独写,不要只写在代码注释里。对接方不看你的源码。
  • 统一管理:用 YAML 或 JSON 维护所有错误码,自动生成 SDK 端的枚举类。我吃过手动维护的亏,两边对不上。
  • 留足预留区间:比如 USR-1001~1999 留给用户模块,即使第一版只用到了 1005,也别让别的模块来抢。
  • 错误码返回值要明确:接口返回的结构里,错误码字段永远是字符串或整型,不要混用类型。

核心总结:好的错误码设计 = 模块清晰 + 编号有含义 + 描述无歧义 + 生命周期受控。能做到这四点,至少能省掉一半的线上排查时间。我见过的所有优秀 SDK,错误码文档必然是一等公民。