业界错误码的规范很多,但是阅读发现这些规范各不相同,甚至很多点相悖。前段时间查了很多资料、咨询过阿里百度等几家公司的同学整理出一份材料和同事分享交流过一轮,下面是一些汇总,这里是希望各路大神们不吝赐教,一起整理出一份最佳实践。
我并没有找到错误码的明确定义,各公司对错误码的定义相同,个人比较倾向于亚马逊官方文档给出的定义:通过对错误码定义,能够简单的帮助用户或开发者识别和理解异常性质,错误码与错误不是一对一关系,是错误类型的一种抽象代号。这里划重点:错误码表示一类错误。
错误码的作用很多,平时会用来:
1)通过日志进行问题排查,快速定位问题。 2)后端服务之间错误码传递。 3)前端展示的错误提示(错误提示是根据错误码决定)。 4)通过错误码配置监控大盘(强烈推荐,每个接口上报错误码到监控系统,发布服务时查看错误曲线,异常情况一目了然)。
错误码作用很多,但是我认为错误码最基本、最重要是作用是回答:出了什么问题?哪里出了问题?如何处理?
下面我总结了遇到的四个问题,以及谷歌、华为等几家公司是如何解决的:
错误码既要负责描述发生了什么错误,又要负责决定出错后如何进行出错控制,关注点没有做到分离,增大了编码复杂度。举个例子:
int ret = register();// 注册用户
if (ret == -1){ // 用户名不合法
// 提示用户名不合法
}else if(ret == -2){ // 手机号不合法
// 提示重新输入手机号
}else if(ret == -3){ // 写入DB失败
// 重试
}通过上面例子发现,错误码既负责了描述当前注册失败的原因,又控制了代码分支(if else),实际上随着业务发展错误原因不断增多,还可能邮箱不合法、证件号不合法,错误数量将会远远多于3个,代码中需要大量的if else,乱且不好维护。
谷歌/微信APIV3RPC错误模型如下:
package google.rpc;
message Status {
// A simple error code that can be easily handled by the client. The
// actual error code is defined by https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
int32 code = 1;
// A developer-facing human-readable error message in English. It should
// both explain the error and offer an actionable resolution to it.
string message = 2;
// Additional error information that the client code can use to handle
// the error, such as retry delay or a help link.
repeated google.protobuf.Any details = 3;
}code:错误码,谷歌一共定义了16个错误码,不允许自定义。谷歌建议认为让开发人员编写用于处理大量错误逻辑的代码很不友好,建议每个 接口可能返回的错误码不超过3 个。
message:面向开发人员的错误描述。 它既应说明错误,又应提供可行的解决方案,特别注意,谷歌强调错误消息不属于 API 协议,它们随时都会更改,应用代码不得严重依赖于错误消息。在编写错误消息时请考虑以下准则:
details:客户端代码可用于处理错误的其他错误信息,Google API 为错误详细信息定义了一组标准错误负载, 涵盖了对于 API 错误的最常见需求,例如配额失败和无效参数。与错误代码一样,开发者应尽可能使用这些标准载荷,并且只有在可以帮助应用代码处理错误的情况下,才应引入其他错误详细信息类型,若错误信息只能由人工处理,则应根据错误消息内容让开发人员手动处理,而不是引入其他错误详细信息类型。
谷歌HTTP返回模型和RPC由微小区别:
// The error schema for Google REST APIs. NOTE: this schema is not used for
// other wire protocols.
message Error {
// This message has the same semantics as `google.rpc.Status`. It has an extra
// field `status` for backward compatibility with Google API Client Library.
message Status {
// This corresponds to `google.rpc.Status.code`.
int32 code = 1;
// This corresponds to `google.rpc.Status.message`.
string message = 2;
// This is the enum version for `google.rpc.Status.code`.
// 注意看注释,谷歌认为该字段应该是废弃掉,之所以存在是为了兼容
google.rpc.Code status = 4;
// This corresponds to `google.rpc.Status.details`.
repeated google.protobuf.Any details = 5;
}
// The actual error payload. The nested message structure is for backward
// compatibility with Google API client libraries. It also makes the error
// more readable to developers.
Status error = 1;
}阿里巴巴错误码规范:
阿里巴巴对外的文档各不一致,这里选择最广为人知的《阿里巴巴JAVA开发规范》
1、【强制】错误码不能直接输出给用户作为提示信息使用。 说明:堆栈(stack_trace)、错误信息(error_message)、错误码(error_code)、提示信息(user_tip)是一个有效关联并互相转义的和谐整体,但是请勿互相越俎代庖。
2、【强制】服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、 errorMessage、用户提示信息四个部分。 说明:输出给用户的提示信息 要求:简短清晰、提示友好,引导用户进行下一步操作或解释错误原因,提示信息可以包括错误原因、上 下文环境、推荐操作等。errorMessage:简要描述后端出错原因,便于错误排查人员快速定位问题,注意不要包含敏感数据信息。
注意: 1)阿里巴巴和谷歌协议完全相反,阿里巴巴认为错误码和httpcode无关,但是谷歌错误码和httpcode是多对一的关系。 2)阿里巴巴规范中明确定义了user_tip是面向用户,error_message是面向开发者,但是谷歌协议中未对面向用户的user_tip进行单独定义。
微软:
windowsAPI完全是另一种思路,举例说明:
//函数
BOOL DeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped
);
//返回值
If the operation completes successfully, the return value is nonzero.
If the operation fails or is pending, the return value is zero. To get extended error information, call GetLastError.微软并不知直接返回错误码,而是仅返回成功失败,如果开发者关注错误码则可以调用GetLastError函数获取错误码以及错误信息。
举一个错误码重叠导致严重问题的例子: 假设A服务是给用户发放红包,A依赖B服务,B服务-1错误码表示用户无权限领取红包,但是A服务-1的错误码表示该用户已经领取,如果A服务开发者未对B服务的错误码的进行转移处理,直接抛出错误码导致A的调用方逻辑判断错误。
// A服务代码如下
int ret = B::fun();
if(ret){
return ret;
}分析上面例子其实是由两个问题导致的: 1)不同服务相同的错误码表示不同的错误; 2)未对领域外的错误码进行收敛;
针对第一点:
谷歌:
上面已经讲过,谷歌错误码只有16个,并且严格定义了每个错误码表示的错误类型,因此不存在错误码重叠的情况。
阿里:
1、【强制】编号不与公司业务架构,更不与组织架构挂钩,以先到先得的原则在平台申请, 审批生效,编号即被永久固定。 2、【强制】错误码使用者避免随意定义新的错误码。
其它:
也了解到一些公司与阿里完全相反,错误码与业务架构、服务强绑定,以登录失败为例,不同的组织架构、相同组织架构不同的服务均使用不同的错误码。与阿里巴巴规范相比:一个错误码可以唯一定位到一个服务的一类错误,优点是在长链路(尤其是跨业务、跨服务情况下)使用错误码定位问题更高效,缺点是这种情况需要定义大量的错误码,错误码很难有自解释性。
针对第二点,参考各个规范,认为: 1)领域内可以不收敛错误码,但是在跨领域时一定要收敛错误码,不允许把其他领域的错误码直接返回上层服务,参考谷歌规范每个接口收敛至不超过3个为宜。 2)调用组件、公共库等返回的错误码,务必收敛为业务相关的错误码。例如注册接口不应该把DB写入失败错误码返回,而是转译为注册失败。
错误码过细:
错误码定义过细过多、过度随意,将会导致调用方对错误处理的逻辑复杂,无法很好的对错误码进行转义或收敛。以登录接口为例:手机号不合法、账号不合法、密码太简单、验证码错误应统一收敛为参数错误,而不应该每种情况定义一个错误码。
错误码过粗:
若随意复用错误码、错误码拆分不细、错误码过度收敛等情况,将会导致调用方无法准确和正确处理错误或给用户提示。比如注册时需要先查询用户是否存在,读取DB失败和用户已存在应该使用不同的错误码,因为读取DB失败可通过重试解决,用户已存在则不允许用户注册,是两个完全不同类型的错误,一个窍门就是收敛后的错误码只能表示参数错误、逻辑错误、系统错误中的一类。
未完待续…..
下次再来讨论: 错误码无类型划分问题 字母or数字 后台与前端/小程序、后台与后台错误信息如何定义和传递 面向传递、面向日志、面向用户时该如何处理