异常处理的原则

今天老狗要跟众小狗们严肃的谈谈异常(Exception),为什么要严肃的呢?因为老狗一提异常,小狗们就开始嗤之以鼻了,瞧不起了,认为这个话题太low,已经准备换台了。

“先生慢走!”老狗向着小狗们高傲的背影嘶吼道“你额头有朝天骨,眼里有灵光,仙人转世,神仙下凡,我终于等到你了。别动,虽然我泄露了天机,灾劫难免,可这是我命中注定,就算我要冒天大的危险,也要给你讲讲异常……

……结果老狗还是没能严肃起来。

对于异常每个程序员都不会陌生,基本上程序员这个物种从刚下地就会try…catch了,finally都是顺道学会的,成熟点的都在throw了,跨入青春期的已经在throws了。在这时期老狗来跟你们神神叨叨的讲异常,难怪各位听不下去。

不过话说回来了,try…catch各位都会,可是在catch里除了System.out.println(e.getMessage)你们还干过别的吗?有没有干过在catch块里什么都没写的事?想过ErrorException的区别吗?看看,这几个狠问题就问的你们目瞪口呆了吧,所以就像老狗的名言所说:“Java里没有难事,Java里没有容易事。”小狗们还是乖乖坐下听我细细道来吧。

一切要从源头说起,Throwable,这个所有异常的祖先类,我给它起了个通俗易懂的中文名叫扔货。扔货生下了俩兄弟ErrorException,之后就挥刀自宫了。俩兄弟里我们跟老二Exception比较熟,老大Error见的少,因为基本上Error一出就日月无光、飞砂走石了。这爷仨个通过自身的努力和各位的帮忙(扔货说:我谢谢你啊!),孕育了庞大的异常家族。

那么这个庞大的家族为人类做出了什么贡献呢?概括的说,它们解答了程序出错时程序员最关心的三个问题:

什么出了错?

在哪出的错?

为什么出错?

首先,异常类本身就回答了“什么出了错”这个问题,比如NullPointerException,什么话都不用多说,光看名字你就知道是访问了空指针了。是不是言简意赅!所以狗狗们,在需要抛出异常的时候,强烈建议抛出针对特定错误而自定义的异常类型!老狗在编程生涯中见过无数个throw new Exception的偷懒行为(惭愧,自己也无数次这样做),这样做绝对,绝对是辜负了整个异常家族的一片苦心!

异常类的StackTrace(异常栈轨迹)回答“在哪出的错”这个问题,我们知道e.printStackTrace()会打印出一大片骇人心目的错误信息,吓得狗狗们不敢细看。这里老狗教给大家一个诀窍,错误信息从上往下看,第一行是异常类和异常信息;第二行就是抛出异常(出错的)方法和具体出错的代码位置(行号);如果没有Caused by的话,最后一行是异常被捕获的位置。

异常信息进一步说明“为什么出错”,比如“java.lang.ArithmeticException: / by zero”,“java.lang.ArithmeticException”告诉我们出了一个算术错误,异常信息“/ by zero”进一步告诉我们,是犯了除0的低级错误。狗狗们在抛出异常的时候,异常信息一定要清晰明白的说明异常原因,比如要抛出“NullPointerException”,最好在异常信息里说明引用的对象名、类名。

有一种情况下例外,在异常信息要抛出给系统的最终用户的时候,此时信息应该尽量简略。例如在springControllerstrutsAction中抛出的异常,异常栈轨迹是会显示在网页上的,此时异常信息就应该尽量简略,避免泄露系统的敏感信息。我见过把SQL语句都抛出到页面上的,充分体现了程序狗狗天真质朴,对险恶的世界毫无防备的性格特点。

前面提到的异常两兄弟之老二“Exception”,它的子孙们大多是高调嘹亮的,一副“来呀!来捕获我呀!”的欠扁相,迫使以德服人的程序狗狗们不得不catch住它们。可是部分宅心仁厚的程序狗狗在catch住异常后,又高抬贵手,弄了个空catch块做做样子,或者System.out.println一下敷衍了事。NoNoNo。大错特错了,须知对敌人的宽容就是对自己的残忍,空catch块固然在语法上没有错误,可是从此你的系统里就留下了一个巨大的黑洞,万恶的异常在系统里大肆破坏后从黑洞中桃之夭夭,留下面对崩溃的系统目瞪口呆、不知所措的用户和程序狗狗。

因此老狗必须郑重讲到异常处理的三原则:

具体明确!

提早抛出!

延迟捕获!

“具体明确”原则不多废话,前面已经说过,抛出针对特定错误而自定义的异常类型,异常信息更是要进一步明确说明异常发生的原因。

“提早抛出”原则又叫“迅速失败”,指在检测到错误时立即抛出异常



public void saveFile(String fileName)throws SaveFileException {

      Filefile = new File(fileName);

      OutputStreamoutputStream = null;

      try{

             outputStream= new FileOutputStream(file);

           

             intsize = 0;

             byte[]bytes = new byte[1024];

             BufferedInputStreambufferedInputStream = new BufferedInputStream(stream);

             while((size = bufferedInputStream.read(bytes)) != -1) {

                    outputStream.write(bytes,0, size);

             }

      }catch (FileNotFoundException | IOException e) {

             thrownew SaveFileException("Save file error.", e);

      }finally {      

             try{

                    outputStream.flush();

                    outputStream.close();

             }catch (IOException e) {

                    thrownew SaveFileException("Close stream error.", e);

             }

      }

}

以上代码有什么问题?

问题在于,如果传入的参数fileNamenull,那么整个方法会抛出一个未经捕获的NullPointException,并且NullPointException的错误信息极不明确,很难定位错误。

既然可以预料如果fileNameNull会引起严重的后果,我们何不提前检测:

if(null == fileName) {

     throw new IllegalFileNameException(“Filename can’t be null.”);

}

如此程序狗狗们一看异常信息就可以明确知道,是因为传入了无效的文件名导致方法出错。

“延迟捕获”原则指:不要在程序或用户有能力处理异常之前捕获它。还是以上面的saveFile方法为例,我们知道saveFile方法有可能抛出IllegalFileNameException,问题是在哪里去捕获IllegalFileNameException更合适。

在调用saveFile方法时即捕获,此时该如何处理呢?总不能让程序随意指定一个文件名去保存吧,这样用户根本不知道他要的文件被存到什么地方了。将异常信息打印到日志中也不是最好的处理办法,这样只有程序狗狗能够获知发生了异常,而用户却更本不知道出了什么事导致他要保存的文件没有保存成功。

最好的时机是程序能够给用户反馈提示的时候,此时捕获IllegalFileNameException异常,给用户弹出对话框提示文件名无效,并给用户提供重新输入文件名的机会。



欢迎扫码关注我的微信公众号:


junior9919


每天学一点,三年成大师。如果你觉得学到了东西,请拿金钱蹂躏我的自尊!


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值