优雅使用错误码
前言
系统开发中,随着功能的迭代升级,我们会开发越来越多的接口,每个接口的作用都不一样,有些涉及数据库操作、有些涉及下游接口调用,在接口调用过程中,会有各种各样的问题,比如参数校验是否合法、请求是否超时、数据查询是否错误等,面对各种各样的问题,我们应该做出不同的响应。
如今接口交互的数据都是通过 JSON 格式,每个公司都有一套自己的约束,不过都是通用的方案,比如下面的结构:
1 | { |
我们会约定,code 为 200 时表示成功,其他表示不同情况下的返回,比如参数不合法,我们会返回 400,无权限返回 403,服务器内部错误返回 500,等等。
推荐一下自己封装的 https://github.com/0x4096/common-base,个人平时写写小项目可以用起来。这里 code 为啥要定义为 string 呢,按照 HTTP 状态码的规范他是数字,这里用 string 是想海纳百川,避免有系统是数字,有系统是字符串,万物皆字符串嘛。
问题
当然,上面状态码的定义是理想态,实际上,我们的系统可能会存在状态码问题。
- 万能错误码,大多数程序员为了偷懒,都是随意抛出错误,最常见的可能是“系统错误,请稍后再试” 这种万能的错误信息。一旦他人把这种错误给你,你就需要看服务器日志才知道具体是哪里有问题,非常低效。
- 错误码重复配置,同一个错误码各地使用,如何才能避免重复配置?
- 接口调用涉及下游、数据库、网络请求等,在各个环节都会产生业务相关错误码,如何标准化管理错误码?
解决方案
面对上述问题,我们从下面几个方面着手解决。
- 对错误码分类,确定好每种错误码的职责;
- 明确命名规则,能做到见名知意;
- 对外统一,需要屏蔽内部信息,给到外部时需要干净简洁;
- 配置集中管理;
状态码分类
在接口调用中,请求流程大致如下
我们可以整体分类为:内部错误码、第三方错误码、外部错误码。
内部错误码
来源:内部错误码为系统内部处理各种业务所产生的错误信息
用途:直接定位到失败原因,比如入参校验、业务逻辑
定位:需要最细粒度的错误分类,便于进行问题排查
外部错误码
来源:根据系统内部错误码转换外部错误码
用途:用于返回给业务方,当前处理结果,以及原因和解决方案
定位:进行错误码归类,屏蔽内部错误信息
第三方错误码
来源:我们依赖第三方、下游
用途:记录每个依赖方的错误码,快速与他们文档核查
定位:外部系统特有,便于文档核查
命名规则
错误码的定位和用途确定后,接下来,我们就需要约束每种错误码的命名规则。
内部错误码
内部错误码是最细粒度的错误码,因为从参数校验、数据库更新、业务处理,到接口返回,各个环节都会产生内部错误码,因此,内部错误规则比较详细:
内部错误码命名,共分为三个层次,规则如下:,如: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 |
总结
代码不仅仅是写给自己看,也是写给他人看,好的代码不仅在工程结构上要合理,每个模块也需要清晰,细分到每个接口,每个函数,都需要有合适的注释,合理的返回值。一个工程越来越多人接手时,规范至为重要。
我们定义各种状态码,在执行过程中,对不符合预期的场景,我们可以抛异常处理,异常有助于提供一种一致的方式来解决运行时的问题,并且有助于写出干净的代码,同时我们也应该对异常做监控和清理,要知道,线上环境,异常本不应该是常态。