通用API数据结构及错误码设计规范

一、前言

1、设计“标准错误信息结构”的背景和意义

    考虑到如下几个方面:

1)便于使用方(大众用户)知道错误的原因

2)便于使用方(程序 或 程序员)知道错误的原因

3)便于知道错误的原因,以及可能的排查和恢复措施

4)知道错误的类型,便于对其进行监控(分类和统计)或者 触发特定动作

    

    设计策略:首先考虑2)3)4)。

    针对于 面向 程序处理 或者 程序员使用的数据,跟 面向于 普通大众用户的数据,其错误码(code)和错误信息(msg)的设计可能是有区别的,因为对于普通大众用户,他们可能看不懂程序报错,而且直接给底层错误信息,影响用户体验。但是没关系,这些在用户端(UI层)都可以处理,从而提高用户体验的。

    所以,我们这个规范,暂时不考虑面向一线普通用户,若要面向一线用户,需要客户端自行处理和包装。


    错误可能有千千万万,该怎么分类?

    很显然,错误的分类,一定是“多级分类”,那到底该有多少级?

    首先,对于代码而言,基本上都会给错误类型取一个名字,例如 IOException。这可以看做是一级分类。

    但是仅仅只有一级分类,还是不足以区分错误信息,例如仅仅知道 RpcException,而不清楚是 网络错误、请求超时、参数错误、业务方异常等类型,很难了解和统计真实情况。

    然而,具体的错误如何分类,分多少层级,可能需要业务方自己去定义 。


    综上分析,制定如下标准:标准的返回(Response)数据结构中,至少应该包含三个字段:

    字段1(status):处理的结果(这个字段用于快速判断 处理结果是否成功,若失败,失败的最高一级类型是什么)

    字段2(code):处理结果的二级分类(对应 处理失败的错误码,或者对于成功的进一步描述)。

    字段3(msg):对处理结果的附加说明(对应 处理失败时的错误描述信息)

    有时候,程序并不能简单的返回成功,而可能是“部分成功” 或者 “不知道是否最终成功”。当然,涉及到这种的业务场景很少,绝大部分的情况是要么成功、要么失败,所以我们习惯性的用true表示成功,false表示失败,或者0代表成功,非0代表失败。(这是使用习惯问题,也得考虑其中)

    我的建议是,调用方优先只判断是否为“绝对的成功”(意味着服务端已经明确表示:请求已经按照API的定义得到了执行),然后再考虑其他“非绝对成功”的特殊情况。程序表示如下:

if (result.status==0) {
    // 请求已经被服务端成功执行!!!
} else {
    // 其他情况都需要特殊处理
}

    为了适配大众的习惯,定义 status 0~200 都代表成功,其中0和200代表“成功”(普通的成功),1~199则为保留字段,若有需要,业务方可以自行扩展。所以,status是一个 int32 字段,这样的字段类型处理性能比较好,而且可以方便的做“大于”、“小于”运算,例如 :

if (result.status >= 400 && result.status <500) {
    alter("客户端异常")
}

    对于第二个字段(code),主要用于对失败时,对错误进行分类。前面说到,分类可以是多级,所以这个code字段,为了灵活性,是string类型,而且其格式是相对自由的。下面给出了几种code的示例:

    Windows错误代码风格:0x8007007B、0xc000000e

    简单 纯数字 风格:780003、631001

    唯一 短url字符串 风格:ea0fc、287c3

    字母+数字 分类风格:E0001、C0002、S0017

    时间戳 + 随机字符 组合风格:ivh32-1560232921

    错误名称风格:FileNotFound、IllegalArguments、Unauthorized

    对于程序员来说,其实什么风格无所谓,关键是:

1)能否知道错误的原因,以及可能的排查和恢复措施

2)根据错误类型,可以对其进行监控(分类和统计)或者 触发特定动作

    一个错误代码,它不能替代错误详情,其设计应该是简洁易用的。我们约定,错误代码总长度不超过32。

    下面是一种建议:

    由于 status(int32)字段只能表示高层级的错误类型,所以 code(string)字段 务必要能定位到具体的异常信息,也就是说 code建议是 “唯一码”,或者是 非常细化的错误类型。可以这样设计:

    status——最高一级的错误类型,code——最低一级(或二级)的错误类型。

    当msg字段能够代表 最低一级的完整错误信息时,code可以作为倒数第二级的错误类型;当msg字段无法完整反映错误信息时,code务必作为最低一级的错误类型(能够直接定位到错误日志中完整的错误信息)。


1、返回(Result)数据由以下字段组成

名称

类型

含义

必须

备注

status

int32

处理成功与否的标志

200、400、500等,见后文

data

Object

自定义返回数据

返回的数据主体,可为空

msg

String

自定义返回消息

对返回结果的附加描述,长3k以内

code

String

自定义返回码

处理结果的业务编码,长32以内

meta

Map<String,String>

通用附加信息

通用附加信息,与具体业务无关

说明:

status字段

0~200—处理成功

400~499—客户端错误,

  • 400——请求无法解析(参数错误、协议错误等)

  • 401*——未认证(未经过身份验证)

  • 402*——客户端错误(统称)

  • 403*——被禁止(拒绝请求)

  • 404*——未找到(没有服务响应该请求)

  • 405*——请求方法不正确

  • 408*——接收请求数据超时

500~599——服务端错误,

  • 500——未预料的异常

  • 501*——内部暂时没有实现该请求功能

  • 502*、504*——作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应(504对应超时)。

  • 503*——由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。

600——未定义、未预料的异常(可能是客户端原因也可能是服务端原因)

备注:在没有更详细的一级分类标准之前,建议status取值只为200、400、500、600之一。

meta字段

可以根据平台需求增加“额外的meta信息”,通常meta信息被框架、中间件层使用(类似于HTTP的Header信息)。


2、请求(Request)数据由以下字段组成

名称

类型

含义

必须

备注

data

Object

请求携带的数据主体

返回的数据主体,可为空

meta

Map<String, String>

中间平台附加信息

通用附加信息,与具体业务无关


3、错误码(Error Code)设计实践

    前文已经说了:

由于 status(int32)字段只能表示高层级的错误类型,所以 code(string)字段 务必要能定位到具体的异常信息,也就是说 code建议是 “唯一码”,或者是 非常细化的错误类型。“code代表最低一级(或二级)的错误类型”  。

    所以,code需要由业务方自己定义。

实践参考:

    通过工具类(例如Java的enum类),可以很方便的定义错误码,如下所示:

// status=200, errorCode = "0"
SUCCESS(200, "0")

// status=400, code="C0001", msg(en_US)="Arguments can not be empty: {}"
PARAM_EMPTY(400, "C0001", null, "Arguments can not be empty: {}")

PARAM_INVALID(400, "C0002", "无效的参数:{}", "Illegal arguments: {}")
// 默认status=400,code=PASSWD_EMPTY
PASSWD_EMPTY("密码不能为空")

MENU_NAME_EMPTY(400, "菜单名称不能为空")
// status=600,code=根据error msg和server id计算出来的唯一码
UNKNOWN_ERR(600, newServCode(msg), msg)

    通过这样的方式,能事先定义好的错误信息,都有明确的error code,通过error code+msg,就能确定错误原因(不需要查看日志)。不能事先定义好的错误信息,则可以通过算法生成唯一的error code,通过该唯一code在日志中检索即可定位到详细的错误信息。举两个例子来说明:

    例1,执行某Controller时,报了未知的RuntimeException,此时外层拦截器捕获后自动生成了唯一error code并记录到日志中,code的生成算法类似于:getCode(String msg, String serverInstanceId),其中msg来源于异常堆栈。

    例2,调用Dubbo时发生了NETWORK Exception,此时错误类型是明确的,但是如果没有错误信息,也很难排查问题,所以,此时也需要生成唯一error code,只不过该error code可以将明确的错误类型作为前缀,code的生成算法类似于:getCode(String type, String msg, String serverInstanceId);其中type来源于异常类型,通常等于异常类的类名。

    

© 2009-2020 Zollty.com 版权所有。渝ICP备20008982号