在微服务化的今天,服务间的交互越来越复杂,统一异常处理规范作为框架的基础,一旦上线后很难再更改,如果设计不好,会导致后期的维护成本越来越来大。 对于错误码的设计,不同的开发团队有不同的风格习惯。本文分享作者从实践中总结的经验及对应的思考,期望对读者有所启发。
引自阿里巴巴《Java 开发手册》- 异常日志-错误码
错误码的制定原则:快速溯源、简单易记、沟通标准化。
说明:错误码想得过于完美和复杂,就像康熙字典中的生僻字一样,用词似乎精准,但是字典不容易随身携带并且简单易懂。
正例:错误码回答的问题是谁的错?错在哪?
1)错误码必须能够快速知晓错误来源,可快速判断是谁的问题。
2)错误码易于记忆和比对(代码中容易 equals)。
3)错误码能够脱离文档和系统平台达到线下轻量化地自由沟通的目的。
那么用Java异常能表示出来吗?答案显然是否定的
错误码的设计是比较简单的,一般只需要定义一个数字和描述信息即可。不过想设计一套完善错误码系统还有很多需要考虑的场景。
大部分项目错误码设计分为3级能满足业务场景,即项目、模块、错误编码。比如错误码是6位,前两位是项目码、中间两位是模块码,最后两位是异常编号。以下是错误码10203的对应说明:
推荐使用枚举,因为枚举具有不可变性,且所有值都在一个文件里描述。
最原始的错误定义方法是项目中所有的错误码都定义在一个类里,但是这样会随着业务的发展错误码越来越多,最终导致难以维护,推荐的做法是按照项目+模块粒度定义成多个错误码枚举类。有两个问题需要考虑:
(1)项目编码、模块编码的维护:推荐另建一个枚举类统一维护
(2)异常类的统一引用:定义接口,枚举类实现接口
示例:
//异常接口定义
public interface ErrorCode {
}
//模块定义
public enum UserProjectCodes {
LOGIN(1, 1, "登录模块"),
USER(1, 2, "用户模块")
}
//登录模块异常码定义
public enum LoginErrorCodes implements ErrorCode {
USER_NOT_EXIST(0, "用户名不存在"), //错误码: 10100
PASSWORD_ERROR(1, "密码错误"); //错误码: 10101
private final int nodeNum;
private final String msg;
UserLoginErrorCodes(int nodeNum, String msg) {
this.nodeNum = nodeNum;
this.msg = msg;
ErrorManager.register(UserProjectCodes.LOGIN, this);
}
}
错误码本质上就是一个数字,且每一个都需要由RD编码定义,在错误码多的项目很容易重复。最佳实践是在枚举的构造方法里调用Helper类,Helper类统一维护所有的异常码,如有重复则枚举初始化失败。
只有错误码是不够的,还需要反馈给调用方详细的错误信息已方便修正。固定的错误信息字符串在某些场景写也是不够的,这里推荐使用slf4j打日志时使用的动态参数,这种方式相比于String.format格式的好处是不需要关心参数的类型以及记忆%s、%d等的区别,且打印日志时经常使用,降低了团队成员的学习成本。
示例:
//错误码定义
PARAM_ERROR(17, "参数非法,期望得到:{},实际得到:{}")
//错误码使用
ErrorCodes.PARAM_ERROR.format(arg1, arg2);
实现方式:
org.slf4j.helpers.MessageFormatter.arrayFormat(this.message, args).getMessage()
在日常业务开发中,对于异常使用最多的还是抛出Java异常(Exception),异常又分为受检查异常(Exception)和不受检查异常(RuntimeException):
定义两个父类,分别用于首检查异常和非受检查异常。可支持传入错误码,同时需要支持原始的异常传参,这种场景会赋予一个默认的错误码,比如:500服务器内部异常
//父类定义
public abstract class BaseException extends Exception {
protected BaseException(String message) {...}
protected BaseException(String message, Throwable cause) {...}
protected BaseException(Throwable cause) {...}
protected BaseException(ErrorInfo errorInfo) {...}
protected BaseException(ErrorCode errorCode) {...}
protected BaseException(ErrorCode errorCode, Object... args) {...}
}
使用异常能适用于大部分场景,不过对于多条目的场景不是很适合,比如需要批量保存10条记录,某些成功、某些失败,这种场景就不适合直接抛出异常。
在Node.js和Go语言中异常处理采用多返回值方式处理,第一个值是异常,如果为null则表示无异常。在Java里建议采用vavr库中的Either来实现,通常使用左值表示异常,而右值表示正常调用后的返回结果,即: Either<ErrorCode, T>
注意不推荐Pair、Tuple来实现,因为Either只能设置一个左值或右值,而Pair、Tuple无此限制。
在前后端的交互中,后端一般使用JSON方式返回结果,整合前面说的错误码,可定义以下格式:
{
"code": number,
"msg": string,
"data": object
}
在SpringMVC中实现方式是自定义ResponseBodyAdvice和异常拦截,具体实现方式直接查看: 源码
实现了以上步骤之后就可以在SpringMVC框架中愉快的使用了,会自动处理异常及封装成统一返回格式
@GetMapping("/order")
public Order getOrder(Long orderId) {
return service.findById(orderId);
}
本文总结了设计错误码需要考虑的各种因素,并给出了参考示例,基本能满足一般中大型项目。规范有了最重要的还是落地,让团队成员遵守规范才能让项目健康的迭代。
源码地址: https://github.com/sofn/app-engine/tree/master/common-error
原文链接:错误码设计思考
作者简介:木小丰,美团Java技术专家,专注分享软件研发实践、架构思考
vue工程npm run serve/start/dev启动时,node_modules文件报:Cannot read property range of null 错误,该问题是babel-eslint版本更新问题导致的;
在ajax请求后台数据时有时会报 HTTP 400 错误 - 请求无效 (Bad request);出现这个请求无效报错说明请求没有进入到后台服务里;原因:前端提交数据的字段名称或者是字段类型和后台的实体类不一致
我们都知道 try catch 无法捕获 setTimeout 异步任务中的错误,那其中的原因是什么。以及异步代码在 js 中是特别常见的,我们该怎么做才比较?
父页面初始化声明变量a为数组(数组对象是引用类型,赋值传递的是地址),创建iframe子页面后给父页面变量a赋值,赋值后销毁iframe子页面,再次调用变量a的时候就会抛出异常‘SCRIPT5011:不能执行已释放Script的代码’。
js错误的实质,也是发出一个事件,处理他,error实例对象message:错误提示信息,name:错误名称(非标准属性)宿主环境赋予
文件上传的功能时候,调用fs.renameSync方法错误,这个提示是跨区重命名文件出现的权限问题。先从源文件拷贝到另外分区的目标文件,然后再unlink,就可以了。
如果在JavaScript中使用innerHTML,缺点是:内容随处可见;不能像“追加到innerHTML”一样使用;innerHTML不提供验证,因此我们可能会在文档中插入有效的和破坏性的HTML并将其中断
现在,有越来越多所谓的“教程”来帮助我们提高网站的易用性。我们收集了一些在Web开发中容易出错和被忽略的小问题,并且提供了参考的解决方案,以便于帮助Web开发者更好的完善网站。
为什么要做前端错误监控?1. 为了保证产品的质量2. 有些问题只存在于线上特定的环境3. 后端错误有监控,前端错误没有监控,前端错误分为两类: 即时运行错误和资源加载错误
当我们在进行开发的时候,通常需要属于我们自己的错误类来反映任务中可能出现的特殊情况。对于网络操作错误,我们需要 HttpError,对于数据库操作错误,我们需要 DbError,对于搜索操作错误,我们需要 NotFoundError,等等
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!