一、异常处理的现状
印象里接触过的项目,在异常处理上都觉得欠缺妥当, 没有从全局上来考虑。于是大量的代码中可以看到
try{
...
}catch(Exception ex){
ex.printStackTrace();
}
于是异常就被吃掉了, 程序还会若无其事的继续进行。
从各种情况来看,程序员其实很烦恼异常处理,因此碰到要强制捕获的时候,要么就throws,要买就catch(Exception ex)全部抓掉,再一句话: e.printStackTrace()。
抛出异常, 我看到很多程序员也不太喜欢,宁可用int返回值定义各类异常情况。
事实上,异常处理不当,对整个项目危害是很大的,可能造成程序结构和逻辑上都会混乱,常常会容易出错,健壮性不好。因此, 应该努力在项目架构确定的同时也确定对异常的统一约定,应该本着简洁、合理的基本原则。
二、异常的两种类型
非检查异常和已检查异常。
1)非检查异常,Throwable继承树的两个分支被编译器放宽了异常检查行为,允许不作捕获。java.lang.Error和 java.lang.RuntimeException的子类免于编译时检查,这就是未检查异常(unchecked exception)。
2)其他的编译器要求强制捕获的就属于已检查异常(checked exception)
其实要讨论异常处理的基本原则,就是要理解以上两种异常的不同特点,何种情况应该抛出何种异常。容易发现,jdk的很多方法抛出的异常都是checked exception,因为会强制要求我们捕获。
三、错误和意外
在谈论异常抛出的基本准则之前,有必要先看看抛出异常时可能面对的两类情况:错误和意外。
1)意外
假设一个银行支付的场景,有一个CheckingAccount 环节,检查客户的账户余额是否足够,如果发现余额不足,抛出了一个InsufficientFundsException,并且要终止支付动作。
那么这个余额不足就应当那个视为一个意外事件,正常情况下是可以完成支付的,现在余额不足要终止。很显然,对于意外事件,我们是可以预料到的,而且也有相应的处理措施,这里就是终止支付操作,并提示客户需要充值了。
2)错误
如果以上场景, 网线掉了呢?我们可能会接受到一个NetworkException,但显然我们无法通过程序来处理这个问题的。
这时我们将这个异常视为错误, 因为我们没法处理,也无法预期这类问题, 网络的问题,可能是网线掉了,也可能是网络设置不对等原因。
四、异常抛出的基本准则
看了以上两类异常情况,我们大概就能意识到,意外的情形我们应该使用checked exception;而错误情形,我们就应该用unchecked exception。
有个底线,就是问问自己,这个异常如果跑出去是否还有挽救的余地,如果可以则用checked exception;否则就unchecked exception。
像UserNotFoundException、NoAuthenticationException、NotEnoughMoneyException都可以归于checked exception,都代表着一个意外事件,有应对的策略。而IOException、SQLException都是不可预料、不可恢复的,应该用unchecked exception会合适一些。不过jdk的开发人员显然是偏爱checked exception,强类型的更加打眼,能明显看出可能出现的问题, 却不管你能不能搞定它。碰到这个情况,我们可以将checked Exception转换成unchecked Exception,这种做法也出现在了大量的开源框架中,一般都会有个异常转换器来做这个转换工作 。
五、异常处理的准则?
我总结了两条出来:
1)对于unchecked,既然是不可恢复的,那我们就不能及时处理,但我们可能还是需要最后去记录一下,然后给出一个“系统错误”之类的提示。那么就可以在应用程序的上层设置一个屏障专门处理这类异常。
2)对于checked,我们需要及时处理,如果发现无法处理就转化成unchecked再次抛出。
异常处理需要try...catch,如果有几类异常就要写好几个catch,因此很多程序员都比较排斥用异常表示意外和错误,他们采用方法返回值int,然后根据这个返回值switch来确认不同的异常情况和作处理。于是,我也思考了异常跟这种方式的优劣。
用方法的返回值代替异常,我们也能在jdk的代码中看到影子,比如String.indexOf就是返回-1表示找不到字串或者字符,IO类里面更是用-1表示是否流读取结束。
这时看起来还是很容易理解的, 而且不用异常也能减低一些开销。但也能发现一个问题,如果这个返回值不是int这类数字,或者说不是基本类型,那么就不好拿返回值表示异常了,当然还有void的情形更不可行。再想想,其实-1,-2,-3表示异常肯定还是需要定义一个常量类来表示,并取个具有意义的变量命会比较好。
这样我就发现这个方式应付不了大多数情形,而且可读性上也差点,定义好异常,直接throws和catch倒还方便。
六、log异常
出现了异常,我们可能需要log记录下来,因为这个异常有些特殊的业务含义,我们需要归档。
合理的设计应该是:
1)对于checked异常, 抛出的时候我们log一下;
2)Unchecked异常,在最上层的错误屏障log,记录下异常信息和堆栈。
总结一下,异常但并不可怕,关键是我们能认真的理解和分析它们。