谈谈Java异常处理这件事儿

此文已由作者谢蕾授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

前言

我们对于“异常处理”这个词并不陌生,众多框架和库在异常处理方面都提供了便利,但是对于何种处理才是最佳实践,也是众说纷纭。异常处理是否得当直接关系到软件的健壮性,今天就谈谈我对异常处理这件事儿的拙见。首先,先说一下异常处理的通俗解释:当危险或知道事情不对的时候做出的反馈。

目录结构

  • Java的异常分类

  • 常见的异常处理的方法

  • 推荐的实践方式


1. Java的异常分类

先简单用图介绍一下Java的异常分类: 

JAVA这种面向对象的语言,同样把异常当作对象来处理,Throwable作为所有异常的超类,然后定义了许多异常类,将这些异常类分为两大类:错误Error和异常Exception。其中,Exception异常类又分为RuntimeException和Checked Exception(也叫非运行时异常)。 Error是程序无法处理的错误,比如OutofMemoryError、TheadDeath等。Exception是程序本身可以处理的异常,应当尽可能去处理这些异常。运行时异常都是RuntimeException类及其子类异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理,如NullPointerException。通常,这些异常一般是由错误代码逻辑引起的,我们应该从逻辑角度尽可能避免这类异常的发生。 非运行时异常从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException 、SQLException等以及用户自定义的异常,一般情况下不自定义运行时异常。

2. 常见的异常处理方法

根据Java工程分层的思想,异常处理也是基于分层的思想。每层都和他的上下层之间都有一个契约,契约内容中就包含了异常处理。任何一层都可能遇到不测,如果遇到了未知的错误,不知道如何处理时,通常的做法就是愉快的向上层抛出一个异常,渴求有一层能正确处理掉这个可怜的错误。我们常用的Java异常处理机制通常依赖于try、catch、finally、throw、throws。 常见的异常处理方式如下:

  • 吞掉并抛出

  • 打印log并抛出

  • 嵌套处理并抛出

  • 忽略异常

2.1 吞掉并抛出

     try{
    ...
}catch(Exception e){throw new ApiException(ApiErrorCode.SqlErr, "Failed to get JobID.");
}

注:ApiException是程序自定义的异常,与SQLEXCEPTION、RUNTIMEEXCEPTION等都属于异常的一种。 比较明显,这种处理方式导致丢失了真实的错误信息,不利于上层做出正确的处理。非常不好的实践。

2.2 打印log并抛出

try{              
  boolean ret = sendNotify(info);           
     if(ret){
     i.remove();
     }
     }catch(ParseException | IOException e){
                  
   logger.error("unexpected exception happened while send notify to client! remove notify message! jobID: " + info.getJobID(), e);        
     throw e;
                    }

这个做法想必大家经常看到,在许多工程中都用了这样的做法。但是这样的处理方式也在一定环境下也有问题,比如,当异常发生在调用层次较深的底层时,同样的错误信息处理方式,会导致我们为了定位问题将看到无数的相同错误信息。所以,具体的异常处理方法还需要综合上下文来处理。

2.3 嵌套处理并抛出

 try{
    ...
}catch(SQLException e){throw new XxxException("Error in Xxx", e);
}

这段代码catch住了sql异常,然后将其封装为另外一种异常抛出,期望遇到知心人能读懂它的良苦用心。

2.4 忽略异常

 try{
   ...
}catch(SQLException ex){
   ex.printStacktrace();
 }

经常看到这样的低级错误吗?是的,开发者无情的忽略了异常,并且只把异常信息输出到控制台,然后,程序仍然继续运行,非常有可能导致更多的异常。

3.推荐的实践方式

此节不敢称为最佳实践,因为一切都会随着空间和时间的变化而变化。只能说下笔者见过的对异常处理的一些推荐做法。如有不适当的地方,请大家指正。

  1. 如果不知道如何处理异常,最好在定义方法时throws该异常,可以声明多个异常。

  2. 细化异常,尽可能的指定具体的异常,方便问题定位,尽量不要使用范围太大的Exception。但是,有时一定要最大范围捕获的,比如一些线程不想让它意外退出,那就必须通过最外层捕获它了。

  3. 避免过大的try块(对所有可能出现异常的代码进行try块的包装),这样不利于分析问题产生的原因。

  4. 一个try对应多个catch时,catch的异常定义可以由精确到一般。

  5. 不要无视捕获的异常,小心因小失大。捕获到后要么直接抛出,要么重新抛出新类型的异常。

  6. 不要在finally块中return或throw等终止方法的语句,这样会导致try或catch块中的return或throw失效。

  7. 鼓励程序适时的进行自定义异常类(非检测异常的一种),因为异常的类名往往包含了很多有用信息,我们封装后的异常类可以更加具体的根据代码或业务处理区分异常类型,方便查找错误。

  8. 调试时可以使用printStackTrace()方法,但是发布后要避免使用该方法。

  9. 管理好自己和其他层之间的异常处理,契约精神,注意:不要把自己能处理的异常抛给别人。


网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击


相关文章:
【推荐】 HashMap在并发场景下踩过的坑

posted @ 2018-10-26 09:15  网易数帆  阅读(182)  评论(0编辑  收藏  举报