优雅使用错误码

前言

系统开发中,随着功能的迭代升级,我们会开发越来越多的接口,每个接口的作用都不一样,有些涉及数据库操作、有些涉及下游接口调用,在接口调用过程中,会有各种各样的问题,比如参数校验是否合法、请求是否超时、数据查询是否错误等,面对各种各样的问题,我们应该做出不同的响应。

如今接口交互的数据都是通过 JSON 格式,每个公司都有一套自己的约束,不过都是通用的方案,比如下面的结构:

1
2
3
4
5
{
"code": "200",
"message": "success",
"data": {}
}

我们会约定,code 为 200 时表示成功,其他表示不同情况下的返回,比如参数不合法,我们会返回 400,无权限返回 403,服务器内部错误返回 500,等等。

推荐一下自己封装的 https://github.com/0x4096/common-base,个人平时写写小项目可以用起来。这里 code 为啥要定义为 string 呢,按照 HTTP 状态码的规范他是数字,这里用 string 是想海纳百川,避免有系统是数字,有系统是字符串,万物皆字符串嘛。

问题

当然,上面状态码的定义是理想态,实际上,我们的系统可能会存在状态码问题。

  1. 万能错误码,大多数程序员为了偷懒,都是随意抛出错误,最常见的可能是“系统错误,请稍后再试” 这种万能的错误信息。一旦他人把这种错误给你,你就需要看服务器日志才知道具体是哪里有问题,非常低效。
  2. 错误码重复配置,同一个错误码各地使用,如何才能避免重复配置?
  3. 接口调用涉及下游、数据库、网络请求等,在各个环节都会产生业务相关错误码,如何标准化管理错误码?

解决方案

面对上述问题,我们从下面几个方面着手解决。

  1. 对错误码分类,确定好每种错误码的职责;
  2. 明确命名规则,能做到见名知意;
  3. 对外统一,需要屏蔽内部信息,给到外部时需要干净简洁;
  4. 配置集中管理;

状态码分类

在接口调用中,请求流程大致如下
image.png

我们可以整体分类为:内部错误码、第三方错误码、外部错误码。

内部错误码

来源:内部错误码为系统内部处理各种业务所产生的错误信息
用途:直接定位到失败原因,比如入参校验、业务逻辑
定位:需要最细粒度的错误分类,便于进行问题排查

外部错误码

来源:根据系统内部错误码转换外部错误码
用途:用于返回给业务方,当前处理结果,以及原因和解决方案
定位:进行错误码归类,屏蔽内部错误信息

第三方错误码

来源:我们依赖第三方、下游
用途:记录每个依赖方的错误码,快速与他们文档核查
定位:外部系统特有,便于文档核查

命名规则

错误码的定位和用途确定后,接下来,我们就需要约束每种错误码的命名规则。

内部错误码

内部错误码是最细粒度的错误码,因为从参数校验、数据库更新、业务处理,到接口返回,各个环节都会产生内部错误码,因此,内部错误规则比较详细:
内部错误码命名,共分为三个层次,规则如下:,如:O_CHN_NOT_ENOUGH_BALANCE ,O 代表外部系统错误;CHN代表渠道错误,NOT_ENOUGH_BALANCE 代表具体错误原因,即余额不足。

整理成表格如下:

层级 前缀 前缀含义 解释
一级 O OUTER 外部系统错误
V VALIDATE 校验类错误码
I INNER 公司内部系统错误,如 redis
二级 CHN CHANNEL 渠道错误
三级 NOT_ENOUGH_BALANCE NOT_ENOUGH_BALANCE 具体错误原因

外部错误码

外部错误码,无需体现具体系统错误原因,只需要进行大体分类即可,如参数错误、余额不足等。针对这部分,可以根据自身业务定义,只要风格统一即可,没有太多约束。

错误码 错误信息 解决方案
PARAM_ERROR 参数校验失败 按照要求传值

参考 common-base 的规范:https://github.com/0x4096/common-base/blob/master/src/main/java/com/github/Is0x4096/common/base/interf/IEnum.java

第三方错误码

第三方错误码,是对接的外部渠道提供的,我们无法自定义,但需要映射为内部错误码。在做支付时,我们以此来屏蔽渠道间的差异。
比如:两个外部渠道支付宝和微信都有各自的参数错误的错误码,但转换后,都是同一个系统内部错误码:

外部渠道 渠道错误码 内部错误码
Alipay ACQ.INVALID_PARAMETER O_CHN_PARAM_ERROR
WeiXinPay PARAM_ERROR O_CHN_PARAM_ERROR

总结

代码不仅仅是写给自己看,也是写给他人看,好的代码不仅在工程结构上要合理,每个模块也需要清晰,细分到每个接口,每个函数,都需要有合适的注释,合理的返回值。一个工程越来越多人接手时,规范至为重要。

我们定义各种状态码,在执行过程中,对不符合预期的场景,我们可以抛异常处理,异常有助于提供一种一致的方式来解决运行时的问题,并且有助于写出干净的代码,同时我们也应该对异常做监控和清理,要知道,线上环境,异常本不应该是常态。