理论
我们开发的每个系统都离不开各种异常,创建异常,抛出异常,捕获异常,每个团队每个项目的实践也不一样,对于异常处理的理解也不尽相同,以下我们提供一种我们的异常处理思路,供大家参考。以下所说跟特定团队,特定项目 综合考虑取舍而来,请自行甄别。
首先异常要解决什么问题,如果应用不出异常,讨论异常实践没有意义,如果应用出现异常,问题的关键就在于异常是否能提供足够的反馈,便于定位问题,并提供必要的上下文信息,最终开发能够快速解决问题。具体来说需要有What(什么东西出错),Where(哪出错了),Why(为什么出错),只要能提供这3W信息,个人认为就是有效的异常,至于是否优雅,是否高效,这些都是在正确有效的前提下来进一步表达的。我们再细化一下实践中的三个异常处理原理,具体明确,提早抛出,延迟捕获。
理论就说到这,以下是我们的工程实践,适用场景为,web应用系统,模块划分为web,service,rpc,manager,dao层。
关于异常的性能
我们来简单测试一下,异常到底消耗多少性能
|
|
我的笔记本(i5,8g,公司标配T450)测试结果是一次异常大概是在0.001毫秒,如果使用ex.getStackTrace(),性能大概会增大到6倍,大概是0.006毫秒,包含了try catch的时间。异常创建的主要性能在同步方法fillInStackTrace中,本测试方法只是简单测试,让大家有一个直观的感受,并发情况下的测试请自行试验。异常对性能的测试可以参考 Java异常及相关调用性能测试
关于性能,补充一下,程序不可能不抛异常,但是异常也不是常态,设计良好稳定运行的业务程序,异常还是少数。
我们的实践
定义错误码接口
|
|
实现业务错误码
|
|
定义业务异常
|
|
使用异常
通过throw BusinessException.asBusinessException抛出异常
异常与返回错误码
|
|
有些人喜欢用返回状态码来标识业务处理结果,C语言最喜欢这么干,至于做应用系统,调用某个业务方法弄一堆状态码是否合适,这个有待商榷,上层调用看到一大堆错误码大多数情况也是无能为力,其实简单粗暴点,如果要表达的是出错了,无法继续运行就抛异常,如果出错了还有补救措施,那就自由选择错误码或者特定的异常。提供jsf接口是需要详细定义错误码,但仅返回一个错误码也不太合适,至少得有个说明什么的。
结论就是,我们推荐使用异常来阻断业务继续。错误码的需求也可以在异常中表达。
异常使用规则
- 最外层一定要捕获异常,最外层是是action,controller,jsf服务提供者实现类。最外层如果不处理异常,会把错误信息传给最终用户。咱先不说业务中或者nginx中自定义错误页面的情况。
- dao,service层放心大胆抛异常,用于阻断程序继续执行。
- 不太推荐层层捕获异常,之后再嵌套包装后抛出来。注意,这里指的是系统中抛出的都是BusinessException这种自定义的运行时异常。业务上已经标转换成了BusinessException异常,没必要再做包装。
- 对于下层模块,异常是否要捕获不做强制要求,反正最外层一定会捕获的。如果我们要屏蔽业务实现的细节,可以捕获后再抛出带特定BusinessErrorCode的异常,这种情况我们做的是错误收敛,如对某些复杂的rpc调用的封装。处于中层的模块,捕获到BusinessException,如果不想做处理,直接上层抛。
结尾
套用老司机的一句话,不要以为自己见过的就是全世界,每一种方案都有其特定的场景,一切都是tradeoff。