当前位置: 首页 > 后端技术 > Java

错误码设计思路

时间:2023-04-01 22:16:54 Java

在微服务的今天,服务之间的交互越来越复杂。统一的异常处理规范是框架的基础。一旦上线,就很难再更改了。如果设计不好,会导致后期维护。成本越来越大。对于错误码的设计,不同的开发团队有不同的风格习惯。本文从实践中分享笔者的经验和相应的思考,希望对读者有所启发。本文涉及的源码:https://github.com/sofn/app-engine/tree/master/common-error什么是错误码引用自阿里巴巴《Java 开发手册》-ExceptionLog-ErrorCode错误码公式原则:快速追溯、易记、规范沟通。正例:错误码回答的问题是谁的错?怎么了?1)错误码要能快速知道错误来源,快速判断是谁的问题。2)错误码容易记忆和比较(代码中容易equals)。3)错误代码可以与文档和系统平台分离,达到离线轻量级和自由通信的目的。那么是否可以用Java异常来表示呢?答案显然是否定的。需要能够快速的知道错误的来源:因为复用性问题无法快速定位到异常类,异常类和代码行数不是一个稳定的值。一定要便于记忆和比较:异常类不可比较,不利于前后端交互,可以脱离代码通信:异常类只能存在于Java代码中错误代码设计错误码的设计比较简单,一般只需要定义一个编号和描述信息即可。但是,如果要设计一个完美的错误码系统,还是有很多场景需要考虑的。1.错误码的分层大多数项目的错误码都被设计成3个层次来满足业务场景,即项目、模块、错误码。例如错误码为6位,前两位为项目代码,中间两位为模块代码,最后两位为异常编号。下面是错误码10203对应的描述:2、错误表示方法:枚举或者类推荐使用枚举,因为枚举是不可变的,所有的值都在一个文件中描述。3.多模块错误码定义和接口定义最原始的错误定义方式是将项目中的所有错误码定义在一个类中,但是这样会随着业务的发展导致错误码越来越多,会最终导致维修困难。推荐的方法是按照项目+模块粒度定义多个错误码枚举类。有两个问题需要考虑:(1)项目编码和模块编码的维护:建议另建一个枚举类,统一维护(2)异常类的统一引用:定义接口,枚举类实现接口的例子://异常接口DefinepublicinterfaceErrorCode{}//模块定义publicenumUserProjectCodes{LOGIN(1,1,"登录模块"),USER(1,2,"用户模块")}//登录模块异常代码定义publicenumLoginErrorCodesimplementsErrorCode{USER_NOT_EXIST(0,"用户名不存在"),//错误码:10100PASSWORD_ERROR(1,"密码错误");//错误代码:10101privatefinalintnodeNum;私有最终字符串消息;UserLoginErrorCodes(intnodeNum,Stringmsg){this.nodeNum=nodeNum;this.msg=味精;ErrorManager.register(UserProjectCodes.LOGIN,this);}}4.反重设计的错误码本质上是一个数字,每一个都需要被RD编码,根据定义,错误码多的item很容易重复。最佳做法是在枚举的构造函数中调用Helper类。Helper类统一维护所有的异常代码。如果有重复,枚举初始化就会失败。5.错误扩展信息只有错误代码是不够的,还需要将详细的错误信息反馈给调用方,以便更正。固定的错误消息字符串在某些场景下是不够写的。这里推荐使用slf4j使用的动态参数进行日志记录。与String.format格式相比,这种方式的好处是不需要关心参数的类型,不需要记住%s、%d等,打印日志的时候经常用到,减少了团队成员的学习成本。例://错误码定义PARAM_ERROR(17,"Illegalparameter,expectedtoget:{},actuallygot:{}")//错误码使用ErrorCodes.PARAM_ERROR.format(arg1,arg2);实现方法:org.slf4j.helpers.MessageFormatter.arrayFormat(this.message,args).getMessage()错误码和异常在日常业务开发中,Java异常(Exception)用的最多的是异常,异常又分为checkedexceptions(Exception)和未检查异常(RuntimeException):检查异常:这种在编译时强制检查的异常称为“检查异常”。即,方法声明中声明的异常。未检查异常:在方法声明中没有声明,但在方法运行过程中发生的各种异常称为“未检查异常”。此类异常是错误,会被自动捕获。1、异常绑定错误码定义了两个父类,分别用于first-checked异常和unchecked异常。可以支持传入错误码,同时需要支持原始异常参数。这个场景会给出一个默认的错误代码,例如:500serverinternalexception//parentclassdefinitionpublicabstractclassBaseExceptionextendsException{protectedBaseException(Stringmessage){...}protectedBaseException(Stringmessage,Throwablecause){...}protectedBaseException(Throwablecause){...}protectedBaseException(ErrorInfoerrorInfo){...}protectedBaseException(ErrorCodeerrorCode){...}protectedBaseException(ErrorCodeerrorCode,Object...args){...}}2、有些异常在大多数场景下都可以使用,但不太适合多入口的场景。比如需要批量保存10条记录,有的成功,有的失败,这种场景不适合直接抛异常。在Node.js和Go语言中,异常处理采用多个返回值。第一个值是异常,如果为null则表示没有异常。在Java中,推荐在vavr库中实现Either。通常用左值表示异常,用右值表示正常调用后的返回结果,即:Either注意Pair和Tuple不推荐实现,因为Either只有一个左值orrvalue可以设置,而Pair和Tuple没有这样的限制。错误码和统一返回值在前后端交互中,后端一般使用JSON返回结果。综合上面提到的错误码,可以定义如下格式:{"code":number,"msg":string,"data":object}在SpringMVC中通过自定义ResponseBodyAdvice和异常拦截实现。具体实现方法可以直接查看:源码实现以上步骤后,就可以在SpringMVC框架中愉快使用了,它会自动处理异常并封装成统一的返回格式@GetMapping("/order")publicOrdergetOrder(LongorderId){returnservice.findById(orderId);}总结本文总结了错误代码设计中需要考虑的各种因素,并给出了参考示例,基本可以满足一般大中型项目的需求。最重要的是执行规范。只有让团队成员遵守规范,项目才能健康迭代。源码地址:https://github.com/sofn/app-engine/tree/master/common-error本文链接:ErrorCodeDesignThinkingArchitecturalthinking。欢迎关注公众号:Java研发更多精彩文章:Java线程池高级架构从MVC到DDD的演进