项目经验_之_规范错误码
前言
Github:https://github.com/HealerJean
一、前言
1、不规范的错误码有什么问题?
1)理解困难
描述:如果错误码的命名或描述不清晰,可能导致其他开发人员难以理解其含义。
举例:例如,一个错误码命名为“ERR1001”,没有进一步的注释或描述,可能导致其他开发人员不知道这个错误码代表的具体问题。
2)不一致性
描述: 如果错误码的命名、描述或分类不统一,可能导致代码的可读性和可维护性降低。
举例:例如,有的错误码使用三位数,有的使用两位数;有的错误码描述具体的问题,而有的描述则较为模糊。
3)排查困难
描述:如果错误码没有清晰的命名和描述,可能使得调试过程变得困难。
举例:当出现问题时,开发人员需要查看大量的日志或代码来定位问题所在。
4)冗余和重复
描述:如果错误码过多或过于复杂,可能导致代码中的错误处理逻辑变得冗余和重复。
举例:同一个错误可能在不同地方有不同的错误码,导致处理逻辑重复。
5)扩展性差
描述:如果错误码已经定义但后来需要添加新的错误码,可能需要修改多个地方的代码,增加了维护成本。
3、规范的错误码那么好,为什么不规范使用呢?
1)缺乏规范和标准:
在某些情况下,可能没有明确的规范或标准来指导如何使用错误码。这可能导致开发人员根据自己的理解和习惯来定义错误码,从而导致不规范的情况。
2)缺乏意识和经验:
某些开发人员可能没有意识到错误码规范化的重要性,或者缺乏足够的经验来正确地设计和使用错误码。
3)历史遗留问题:
在某些项目中,错误码可能已经使用了很长时间,而且已经成为了代码的一部分。在这种情况下,重新规范化错误码可能会涉及到大量的代码修改和测试,这可能会被视为成本较高。
4)个人习惯和偏好:
某些开发人员可能更倾向于按照自己的习惯和偏好来使用错误码,而不是遵循团队的规范。这可能会导致代码中的错误码使用不一致。
4、那怎么规范错误码呢
1)制定规范和标准:
团队可以制定明确的规范和标准,指导如何使用错误码,并将其纳入代码审查和开发流程中。
2)培训和指导:
为新开发人员提供培训和指导,使其了解如何正确地设计和使用错误码。
3)重构和改进:
对于历史遗留问题,可以通过逐步重构和改进的方式来规范化错误码的使用。
4)代码审查和团队协同:
通过代码审查和团队协同来确保错误码的规范化和一致性。
二、错误码标准
1、阿里巴巴:Java
开发手册(嵩山版).pdf
1、【强制】错误码的制定原则:快速溯源、沟通标准化
错误码想得过于完美和复杂,就像康熙字典中的生僻字一样,用词似乎精准,但是字典不容易随身携带并且简单易懂。
正例:错误码回答的问题是谁的错?错在哪?
1)错误码必须能够快速知晓错误来源,可快速判断是谁的问题。
2)错误码必须能够进行清晰地比对(代码中容易
equals
)。3)错误码有利于团队快速对错误原因达到一致认知。
2、【强制】错误码不体现版本号和错误等级信息
错误码以不断追加的方式进行兼容。错误等级由日志和错误码本身的释义来决定。
3、【强制】全部正常,但不得不填充错误码时返回五个零:00000
4、【强制】错误码为字符串类型,共 5
位,分成两个部分:错误产生来源+四位数字编号
错误产生来源分为
A
/B
/C
,⬤
A
表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付超时等问题;⬤
B
表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题;⬤
C
表示错误来源于第三方服务,比如CDN
服务出错,消息投递超时等问题;四位数字编号从0001
到9999
,大类之间的步长间距预留100
5、【强制】编号不与公司业务架构,更不与组织架构挂钩,以先到先得的原则在统一平台上进行,审批生效,编号即被永久固定
6、【强制】错误码使用者避免随意定义新的错误码
尽可能在原有错误码附表中找到语义相同或者相近的错误码在代码中使用即可。
7、【强制】错误码不能直接输出给用户作为提示信息使用。
堆栈(
stack_trace
)、错误信息(error_message
)、错误码(error_code
)、提示信息(user_tip
)是一个有效关联并互相转义的和谐整体,但是请勿互相越俎代庖。
8、【推荐】错误码之外的业务独特信息由 error_message
来承载,而不是让错误码本身涵盖过多具体业务属性
9、【推荐】在获取第三方服务错误码时,向上抛出允许本系统转义,由 C
转为 B
,并且在错误信息上带上原有的第三方错误码。
10、错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码
在无法更加具体确定的错误场景中,可以直接使用一级宏观错误码,分别是:
A0001
(用户端错误)、B0001
(系统执行出错)、C0001
(调用第三方服务出错)。正例:调用第三方服务出错是一级,中间件错误是二级,消息服务出错是三级。
11、错误码即人性,感性认知+口口相传,使用纯数字来进行错误码编排不利于感性记忆和分类
数字是一个整体,每位数字的地位和含义是相同的。
反例:一个五位数字
12345
,第1位是错误等级,第2
位是错误来源,345
是编号,人的大脑不会主动地拆开并分辨每位数字的不同含义。
2、其他平台
平台 | 错误码 |
---|---|
阿里错误码 | |
企业微信 | 分号段-纯数字(5位) |
微信开放平台 | 分号段-纯数字(6位) |
高德开放平台 | 分号段-纯数字(5位) |
京ME错误码 |
3、建议
参考:https://developer.aliyun.com/article/766288
根据号段区分错误类型,这里长度定义了6位,可以根据自己系统规模调整长度
错误类型[3位] + 错误编码<分号段> [3位]分号段>
1)1-数据校验错误
错误 | 错误类型 [3位] | 错误编码 [3位] | 说明 |
---|---|---|---|
通用异常 | 100 | 000 | 参数不合法 |
参数请求错误 | 101 | 001 | |
参数格式不对 | 102 | 000 | 参数格式不合法 |
参数格式不对 | 102 | 001 | 要求字符串类型 |
2)2-运行时错误
错误 | 错误类型 [3位] | 错误编码 [3位] | 说明 |
---|---|---|---|
运行时错误 | 200 | 000 | 运行时异常 |
内存错误 | 201 | 000 | |
redis 错误 |
202 | 000 | |
mysql 错误 |
203 | 000 | |
mysql 错误 |
203 | 001 | mysql 主键冲突 |
mq 错误 |
204 | 000 | |
rpc 错误 |
205 | 000 | |
rpc 错误 |
205 | 001 | rpc 请求超时 |
rpc 错误 |
205 | 001 | rpc 线程池打满 |
rpc 错误 |
205 | 002 | rpc 业务异常 |
3)3-业务已知异常
错误 | 错误类型 [3位] | 错误编码 [3位] | 说明 |
---|---|---|---|
业务异常 | 300 | 000 | 业务异常 |
承保异常 | 301 | 000 | 承保异常 |
承保异常 | 301 | 001 | 风控失败 |
签约异常 | 302 | 000 | 签约失败 |
签约异常 | 302 | 001 | 风控校验拦截 |
签约异常 | 302 | 002 | 钱包校验拦截 |
核保异常 | 303 | 000 | 核保异常 |
4)0 成功-9 失败
错误 | 错误类型 [3位] | 错误编码 [3位] | 说明 |
---|---|---|---|
成功 | 000 | 000 | 固定 000000 (0 开头预留号段,可以根据不用业务场景使用) |
异常 | 999 | 999 | 固定 999999 (完全未知情况下的异常) |
三、规范错使用
1、错误码-分片区
根据号段区分错误类型,这里长度定义了5位,可以根据自己系统规模调整长度
错误码 | 描述 |
---|---|
000000 | 成功 |
100000 | 参数错误 |
300000 | 运行时错误 |
400000 | 业务已知异常 |
999999 | 系统太火爆了,请稍后重试! –>极端情况下才吐出 |
@Getter
@AllArgsConstructor
enum ErrorCodeEnum implements CodeEnum {
ERROR_CODE_SUCCESS("000000", "成功"),
ERROR_CODE_FAIL("999999", "系统太火爆了,请稍后重试!"),
;
private final String code;
private final String msg;
}
@Getter
@AllArgsConstructor
enum ErrorTypeEnum implements CodeEnum {
/**
* 校验错误
*/
ERROR_TYPE_100("100", "参数异常-通用"),
ERROR_TYPE_101("101", "请求方式不合法"),
ERROR_TYPE_102("102", "参数格式不合法"),
/**
* 运行时错误
*/
ERROR_TYPE_200("200", "运行时错误"),
ERROR_TYPE_201("201", "内存错误"),
ERROR_TYPE_202("202", "redis错误"),
ERROR_TYPE_203("203", "mysql错误"),
ERROR_TYPE_204("204", "mq错误"),
ERROR_TYPE_205("205", "rcp错误"),
ERROR_TYPE_206("206", "运行时错误"),
/**
* 业务异常
*/
ERROR_TYPE_300("300", "业务处理失败"),
ERROR_TYPE_301("301", "订单创建"),
;
private final String code;
private final String msg;
}
1)100000-参数异常
非常简单,直接吐出即可
参数 | 说明 |
---|---|
code |
错误码 |
msg |
返回错误信息 |
@Getter
@AllArgsConstructor
enum ParamsErrorEnum implements CodeEnum {
ERROR_CODE_100000("100000", ErrorTypeEnum.ERROR_TYPE_100, "参数不合法"),
ERROR_CODE_101001("101001", ErrorTypeEnum.ERROR_TYPE_101, "请求方式不合法"),
ERROR_CODE_102000("102000", ErrorTypeEnum.ERROR_TYPE_102, "参数格式不合法"),
ERROR_CODE_102001("102001", ErrorTypeEnum.ERROR_TYPE_102, "必须是字符串"),
;
private final String code;
private final ErrorTypeEnum typeEnum;
private final String msg;
}
3)200000-运行时异常
参数 | 说明 |
---|---|
code |
错误码 |
msg |
底层-错误信息 |
@Getter
@AllArgsConstructor
enum PlatformErrorEnum implements CodeEnum {
ERROR_CODE_200000("200000", ErrorTypeEnum.ERROR_TYPE_200, "运行时错误"),
ERROR_CODE_201001("201000", ErrorTypeEnum.ERROR_TYPE_200, "内存错误"),
ERROR_CODE_205000("205000", ErrorTypeEnum.ERROR_TYPE_205, "RPC错误"),
;
private final String code;
private final ErrorTypeEnum typeEnum;
private final String msg;
}
4)300000-业务已知异常
参数 | 说明 |
---|---|
code |
错误码 |
msg |
底层-错误信息 |
showMsg |
吐出-错误信息 |
@Getter
@AllArgsConstructor
enum BusinessErrorEnum implements CodeEnum {
ERROR_CODE_300000("300000", ErrorTypeEnum.ERROR_TYPE_300, "业务处理失败", "系统太火爆了,请稍后重试!"),
ERROR_CODE_301000("301000", ErrorTypeEnum.ERROR_TYPE_301, "订单创建失败", ""),
ERROR_CODE_301001("301001", ErrorTypeEnum.ERROR_TYPE_301, "订单创建失败", "您有一个订单正在创建,请稍后查看"),
;
private final String code;
private final ErrorTypeEnum typeEnum;
private final String msg;
private final String showMsg;
}
2、错误码使用
1)10000-参数异常
构造器 | 说明 |
---|---|
ParameterException(CodeEnum.ParamsErrorEnum paramErrorEnum) |
传入枚举值 |
ParameterException(String showMessage) |
固定code、自定义错误信息 |
ParameterException(CodeEnum.ParamsErrorEnum paramErrorEnum, String showMessage) |
自定义错误信息 |
@Getter
public class ParameterException extends RuntimeException {
/**
* serialVersionUID
*/
private static final long serialVersionUID = -6114625076221233075L;
/**
* 返回错误码
*/
private final String code;
/**
* ParameterErrorException
*
* @param showMessage message
*/
public ParameterException(String showMessage) {
super(showMessage);
this.code = CodeEnum.PlatformErrorEnum.ERROR_CODE_200000.getCode();
}
/**
* BusinessException
*
* @param paramErrorEnum paramErrorEnum
*/
public ParameterException(CodeEnum.ParamsErrorEnum paramErrorEnum) {
super(paramErrorEnum.getMsg());
this.code = paramErrorEnum.getCode();
}
/**
* BusinessException
*
* @param paramErrorEnum paramErrorEnum
*/
public ParameterException(CodeEnum.ParamsErrorEnum paramErrorEnum, String showMessage) {
super(showMessage);
this.code = paramErrorEnum.getCode();
}
}
2)20000-运行时错误
构造器 | 说明 |
---|---|
PlatformException(CodeEnum.PlatformErrorEnum platformErrorEnum) |
传入枚举值 |
PlatformException(CodeEnum.PlatformErrorEnum platformErrorEnum, Throwable e) |
传入枚举值+异常 |
PlatformException(CodeEnum.PlatformErrorEnum platformErrorEnum, String showMessage) |
传入枚举值+自定义错误信息 |
@Getter
public class BusinessException extends RuntimeException {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 799633539625676004L;
/**
* 返回错误码
*/
private final String code;
/**
* 展示信息
*/
private final String showMsg;
/**
* BusinessException
*
* @param businessErrorEnum businessErrorEnum
*/
public BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum) {
super(businessErrorEnum.getMsg());
this.code = businessErrorEnum.getCode();
this.showMsg = businessErrorEnum.getShowMsg();
}
/**
* BusinessException
*
* @param businessErrorEnum businessErrorEnum
*/
public BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum, Throwable e) {
super(businessErrorEnum.getMsg(), e);
this.code = businessErrorEnum.getCode();
this.showMsg = businessErrorEnum.getShowMsg();
}
/**
* BusinessException
*
* @param showMessage message
*/
public BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum, String showMessage) {
super(businessErrorEnum.getMsg());
this.code = CodeEnum.BusinessErrorEnum.ERROR_CODE_300000.getCode();
this.showMsg = showMessage;
}
}
3)30000-业务已知异常
构造器 | 说明 |
---|---|
BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum) |
传入枚举值 |
BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum, Throwable e) |
传入枚举值+异常 |
BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum, String showMessage) |
传入枚举值+自定义错误信息 |
@Getter
public class BusinessException extends RuntimeException {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 799633539625676004L;
/**
* 返回错误码
*/
private final String code;
/**
* 展示信息
*/
private final String showMsg;
/**
* BusinessException
*
* @param businessErrorEnum businessErrorEnum
*/
public BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum) {
super(businessErrorEnum.getMsg());
this.code = businessErrorEnum.getCode();
this.showMsg = businessErrorEnum.getShowMsg();
}
/**
* BusinessException
*
* @param businessErrorEnum businessErrorEnum
*/
public BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum, Throwable e) {
super(businessErrorEnum.getMsg(), e);
this.code = businessErrorEnum.getCode();
this.showMsg = businessErrorEnum.getShowMsg();
}
/**
* BusinessException
*
* @param showMessage message
*/
public BusinessException(CodeEnum.BusinessErrorEnum businessErrorEnum, String showMessage) {
super(businessErrorEnum.getMsg());
this.code = CodeEnum.BusinessErrorEnum.ERROR_CODE_300000.getCode();
this.showMsg = showMessage;
}
}
3、异常吐出
1)100000-参数异常
/**
* 不支持的请求方始
*/
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
@ResponseStatus(value = HttpStatus.METHOD_NOT_ALLOWED)
public BaseRes<?> methodNotSupportExceptionHandler(HttpRequestMethodNotSupportedException e) {
log.error("不支持的请求方式", e);
return BaseRes.buildFailure(CodeEnum.ParamsErrorEnum.ERROR_CODE_101001.getCode(), e.getMessage());
}
/**
* 参数类型错误
* 1、(BindException : 比如 Integer 传入String )
* Field error in object 'demoDTO' on field 'age': rejected value [fasdf]; codes [typeMismatch.demoDTO.age,typeMismatch.age,typeMismatch.java.lang.Integer,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [demoDTO.age,age]; arguments []; default message [age]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Integer' for property 'age'; nested exception is java.lang.NumberFormatException: For input string: "fasdf"]
*/
@ExceptionHandler(value = {BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public BaseRes<?> bindExceptionHandler(BindException e) {
log.error("====参数类型错误===", e);
return BaseRes.buildFailure(CodeEnum.ParamsErrorEnum.ERROR_CODE_100000);
}
/**
* 参数格式问题
*/
@ExceptionHandler(value = {MethodArgumentTypeMismatchException.class, HttpMessageConversionException.class, UnexpectedTypeException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public BaseRes<?> httpMessageConversionExceptionHandler(Exception e) {
log.error("====参数格式异常===", e);
return BaseRes.buildFailure(CodeEnum.ParamsErrorEnum.ERROR_CODE_102000);
}
/**
* 参数错误
*/
@ExceptionHandler(value = ParameterException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public BaseRes<?> parameterErrorExceptionHandler(ParameterException e) {
log.error("====参数异常:code:{},msg:{}", e.getCode(), e.getMessage(), e);
return BaseRes.buildFailure(e.getCode(), e.getMessage());
}
2)200000-运行时异常
/**
* 运行异常,给前台返回异常数据
*/
@ExceptionHandler(value = PlatformException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public BaseRes<?> platformExceptionHandler(PlatformException e) {
log.error("====运行异常:code:{},msg:{},showMsg:{}", e.getCode(), e.getMessage(), e.getShowMsg(), e);
return BaseRes.buildFailure(e.getCode(), e.getShowMsg());
}
4)40000-业务已知异常
/**
* 业务异常,给前台返回异常数据
*/
@ExceptionHandler(value = BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public BaseRes<?> businessExceptionHandler(BusinessException e) {
log.error("====业务异常:code:{},msg:{},showMsg:{}", e.getCode(), e.getMessage(), e.getShowMsg(), e);
return BaseRes.buildFailure(e.getCode(), e.getShowMsg());
}
4、Demo
1)10000-参数异常
@ApiOperation("parameterExceptionEnum")
@LogIndex
@GetMapping("parameterExceptionEnum")
@ResponseBody
public BaseRes<List<UserDemoVO>> parameterExceptionEnum() {
throw new ParameterException(CodeEnum.ParamsErrorEnum.ERROR_CODE_100000);
}
{
"data": null,
"msg": "参数不合法",
"code": "100000",
"success": false
}
@ApiOperation("parameterExceptionMsg")
@LogIndex
@GetMapping("parameterExceptionMsg")
@ResponseBody
public BaseRes<List<UserDemoVO>> parameterExceptionMsg() {
throw new ParameterException("用户Id不能为空");
}
{
"data": null,
"msg": "用户Id不能为空",
"code": "200000",
"success": false
}
2)20000-运行异常
@ApiOperation("platformException")
@LogIndex
@GetMapping("platformException")
@ResponseBody
public BaseRes<List<UserDemoVO>> platformException() {
throw new PlatformException(CodeEnum.PlatformErrorEnum.ERROR_CODE_200000);
}
{
"data": null,
"msg": "系统太火爆了,请稍后重试!",
"code": "200000",
"success": false
}
@ApiOperation("platformExceptionShowMessage")
@LogIndex
@GetMapping("platformExceptionShowMessage")
@ResponseBody
public BaseRes<List<UserDemoVO>> platformExceptionShowMessage() {
throw new PlatformException(CodeEnum.PlatformErrorEnum.ERROR_CODE_201001, "任务处理失败");
}
{
"data": null,
"msg": "任务处理失败",
"code": "201000",
"success": false
}
3)30000-业务已知异常
@ApiOperation("businessExceptionEnum")
@LogIndex
@GetMapping("businessExceptionEnum")
@ResponseBody
public BaseRes<List<UserDemoVO>> businessExceptionEnum() {
throw new BusinessException(CodeEnum.BusinessErrorEnum.ERROR_CODE_300000);
}
{
"data": null,
"msg": "系统太火爆了,请稍后重试!",
"code": "300000",
"success": false
}
@ApiOperation("businessExceptionShowMessage")
@LogIndex
@GetMapping("businessExceptionShowMessage")
@ResponseBody
public BaseRes<List<UserDemoVO>> businessExceptionShowMessage() {
throw new BusinessException(CodeEnum.BusinessErrorEnum.ERROR_CODE_301000, "订单创建失败,我是失败原因");
}
{
"data": null,
"msg": "订单创建失败,我是失败原因",
"code": "300000",
"success": false
}