Java - 9个处理异常的最佳准则
其实工作这么久了一直都没搞清楚到底如何来处理异常,偶然看到一篇外文感觉还不错,便把它翻译了下来,原文链接位于本文末尾处。
在java中处理异常并不是一件简单的事,不止初学者觉得它难以理解甚至连有经验的开发者也会花费几个小时来讨论某个异常应该抛出还是处理掉。
这就是为何大多数开发团队都拥有自己的规范来指明如何使用它们,如果你刚来到一个新的团队,你可能会发现新团队的准则与你之前遵循的大有不同。
尽管如此,这里还是有几条最佳准则被大多数团队所遵循。这里有9条准则可以帮助你提高处理异常的水平。
1、在Finally块中清理资源或者使用Try-With-Resource语句
在try块中使用资源的情况在开发中会经常碰到,比如一个InputStream,使用它之后你需要将它关闭。在这种情况下经常会看到在try块中去关闭资源的错误。
 public void doNotCloseResourceInTry() {
     FileInputStream inputStream = null;
     try {
         File file = new File("./tmp.txt");
         inputStream = new FileInputStream(file);
         // use the inputStream to read a file
         // do NOT do this
         inputStream.close();
     } catch (FileNotFoundException e) {
         log.error(e);
     } catch (IOException e) {
         log.error(e);
     }
 }
这样写在没有异常抛出的情况下似乎运行得非常溜,所有在try块下的语句都会被正常执行,且资源都会被关闭。
但使用try块是有它的原因的,你调用的一个或者多个方法可能会抛出异常,或者你自己主动抛出异常,这意味着try块中的语句可能会无法完整的执行,最终导致资源没有关闭。
使用Finally块
与try块不同的是——finally块中的语句总是会被执行,无论是try块中的语句成功执行还是你在catch块中处理了一个异常。因此所有开启的资源都能够确保被关闭。
 public void closeResourceInFinally() {
     FileInputStream inputStream = null;
     try {
         File file = new File("./tmp.txt");
         inputStream = new FileInputStream(file);
         // use the inputStream to read a file
     } catch (FileNotFoundException e) {
         log.error(e);
     } finally {
         if (inputStream != null) {
             try {
                 inputStream.close();
             } catch (IOException e) {
                 log.error(e);
             }
         }
     }
 }
Java 7的Try-With-Resource语句
还有一种选择便是使用try-with-resource,与之相关的详情在我的另一边文章——introduction to Java exception handling中有介绍。
如果你的资源实现了AutoCloseable接口的话你便可以使用try-with-resource语句,这也是大多数Java标准资源的做法,如果你在try-with-resource语句中声明了一个资源,它将会在try块中的语句执行后或者将异常处理后自动关闭。
 public void automaticallyCloseResource() {
     File file = new File("./tmp.txt");
     try (FileInputStream inputStream = new FileInputStream(file);) {
         // use the inputStream to read a file
     } catch (FileNotFoundException e) {
         log.error(e);
     } catch (IOException e) {
         log.error(e);
     }
 }
2、优先使用更明确的异常
抛出的异常越明确越好,你要想着你一个不了解你的代码的同事或者你在几个月后,需要调用你的方法并且处理异常。
因此要确保提供尽可能多的信息,使你的API更容易被理解,使得该方法的调用者能更好的处理异常且避免额外的检查。
所以,应该寻找与你的异常事件最贴切的类,例如:抛出一个NumberFormatException而不是IllegalArgumentException(译者注:这句话缺少上下文,不明白作者的意思)。且避免抛出不明确的异常。
 public void doNotDoThis() throws Exception { ... }
 public void doThis() throws NumberFormatException { ... }
3、在文档里记录你的异常
每当你在方法签名处指定一个异常,都应该同时将它记录到Javadoc里边。
这与前一条准则的目的是一样的:给方法调用者提供尽可能多的信息,让他可以避免触发异常或者方便的他处理异常。
所以确保要在Javadoc里边添加@throws声明并且描述什么样的情况会导致异常。
/**
* This method does something extremely useful ...
*
* @param input
* @throws MyBusinessException if ... happens
*/
public void doSomething(String input) throws MyBusinessException { ... }
4、将异常与它的描述信息一并抛出
这条准则的想法与前两条是相同的,但这次你不是给你的方法调用者提供信息,当这个异常信息被打印到日志文件或者反馈到你的监视工具时,它要能被每个想要了解发生了什么的人理解。
因此,我们应该尽可能精确的描述问题并且提供更接地气的消息来让他人理解发生了什么异常。
别误会我的意思,你没必因此写上一大段话,但你应该用一两句话简明扼要的解释一下异常的原因。以帮助你的运营团队了解发生了什么问题,同时这也会使你更容易分析问题原因。
如果你抛出一个明确的异常,它的类名很可能已经描述了这是怎样一个错误了,所以你不需要提供大量格外的信息,NumberFormatException就是一个很好的例子,当你给java.lang.Long的构造函数提供一个错误格式的String类型参数时便会抛出NumberFormatException。
 try {
     new Long("xyz");
 } catch (NumberFormatException e) {
     log.error(e);
 }
NumberFormatException的类名已经告诉你这是什么类型的问题了,它的异常消息只需要指明导致这个问题的输入字符串,如果异常类的名称不能达其意,你需要在异常消息中提供必要的信息。
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
5、优先捕获更明确的异常
大部分IDE都会帮你遵循这条准则,当你把没那么明确的异常放在前面的时候他们会提示存在无法到达的代码块。
这是因为只有第一个匹配到的catch块才会被执行,所以如果你先捕获IllegalFormatException,你将永远无法到达处理NumberFormatException的catch块,因为NumberFormatException是IllegalArgumentException的子类。
所以应优先捕获明确的异常,将没那么明确的catch块放在后边。
如下代码片段中的try-catch语句。第一个catch块处理所有NumberFormatException,第二个则处理所有非NumberFormatException的IllegalArgumentException。
 public void catchMostSpecificExceptionFirst() {
     try {
         doSomething("A message");
     } catch (NumberFormatException e) {
         log.error(e);
     } catch (IllegalArgumentException e) {
         log.error(e)
     }
 }
6、不要捕获Throwable
Throwable是所有异常(Exception)和错误(Error)的父类,虽然它能在catch从句中使用,但永远都不要这样做!
如果你在catch从句中使用了Throwable,它将不仅捕获所有异常,它还将捕获所有错误,错误是由JVM抛出的,用来表明不打算让应用来处理的严重错误。
OutOfMemoryError和StackOverflowError便是典型的例子,它们都是由于一些超出应用处理范围的情况导致的。
 public void doNotCatchThrowable() {
     try {
         // do something
     } catch (Throwable t) {
         // don't do this!
     }
 }
7、别忽略异常
你是否曾经分析过一份不完整的bug报告?
这通常是由于忽略异常导致的,这开发者大概很确定这里永远都不会抛出异常并加了一个不处理且不打印日志的catch块,当你找到这个块时,甚至很可能发现这么一句著名的注释——“This will never happen”
 public void doNotIgnoreExceptions() {
     try {
         // do something
     } catch (NumberFormatException e) {
         // this will never happen
     }
 }
好吧,你可能正在分析一个不可能发生的问题。
所以,请不要忽略异常,你不知道代码在将来会如何变动,可能有人会将防止该异常事件的校验移除掉且没有意识到这会产生问题,或者抛出异常的这段代码改变了,相同的类现在变成抛出多个异常,而调用它的代码没有预防所有的异常。
你至少要将日志打印出来告诉别人这里发生了异常,方便别人来检查。
 public void logAnException() {
     try {
         // do something
     } catch (NumberFormatException e) {
         log.error("This should never happen: " + e);
     }
 }
8、不要打印异常日志的同时将其抛出
这可能是本文当中最常被忽视一条准则,你会在很多代码片段中甚至库中发现一个异常被捕获打印日志后被重新抛出。
 try {
     new Long("xyz");
 } catch (NumberFormatException e) {
     log.error(e);
     throw e;
 }
这样做可能确实是直观的看到了异常日志,然后将异常重新抛出,所以调用者也能正确的处理异常,但这样做会使一个异常打印多个异常信息。
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
且额外的消息并没有提供任何有用的信息,根据第4条准则,异常消息应该描述异常事件,堆栈信息告诉你异常抛出的所在类、方法、行数。
如果你需要添加额外的信息,你应该将异常捕获并将其包在你的自定义异常中,但要确保遵循第9条准则。
 public void wrapException(String input) throws MyBusinessException {
     try {
         // do something
     } catch (NumberFormatException e) {
         throw new MyBusinessException("A message that describes the error.", e);
     }
 }
所以只有在你想要处理某个异常的时候才应该去捕获它,否则在方法签名处声明抛出该异常让调用者去关注它就好了。
9、包裹某个异常的同时不要丢弃它原本的信息
有时候我们需要捕获一个标准异常并用自定义异常包裹住它,一个典型的例子便是比如某个应用或者框架的指明业务异常,它允许你添加额外的信息,你也可以实现特别的异常处理方法。
当你这么做的时候,要确保将原本的异常作为原因设置到自定义异常里面,Exception类提供指定的构造方法可以接收Throwable类的对象作为参数,否则你将会丢失堆栈信息和原异常的消息,这将会令异常分析变得什么的困难。
 public void wrapException(String input) throws MyBusinessException {
     try {
         // do something
     } catch (NumberFormatException e) {
         throw new MyBusinessException("A message that describes the error.", e);
     }
 }
总结
如你所见,当你捕获或者抛出异常的时候你需要考虑很多事情,总的来说这些准则都是为了提高代码的可读性,API的可用性。
异常往往既是是错误处理机制也是沟通媒介,因此,你应该与你的同事一起讨论这些准则,使每个人都理解这些常规的概念,并以相同的风格在实践中应用它们。
原文链接:https://stackify.com/best-practices-exceptions-java/
译者博客:http://www.cnblogs.com/kcher90/
Java - 9个处理异常的最佳准则的更多相关文章
- Java中的受检异常
		Java中的受检异常 Java提供了三种异常类型,受检异常(checked exception).运行时异常(runtime exception).错误(error).那么这受检异常在实际开发中又有什 ... 
- 【转】Java中关于异常处理的十个最佳实践
		原文地址:http://www.searchsoa.com.cn/showcontent_71960.htm 导读:异常处理是书写强健Java应用的一个重要部分,Java许你创建新的异常,并通过使用 ... 
- 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)
		编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ... 
- java提高篇(十七)-----异常(二)
		承接上篇博文:java提高篇-----异常(一) 五.自定义异常 Java确实给我们提供了非常多的异常,但是异常体系是不可能预见所有的希望加以报告的错误,所以Java允许我们自定义异常来表 ... 
- Java.lang.NoSuchFieldError: INSTANCE异常
		解决方案: java.lang.NoSuchFieldError: INSTANCE异常. 1.jar包重复了. 2.版本还不相同,如果包的版本不同也会报相应的错,不过一般情况自己导入的jar包主要看 ... 
- attilax.java 注解的本质and 使用最佳实践(3)O7
		attilax.java 注解的本质and 使用最佳实践(3)O7 1. 定义pojo 1 2. 建立注解By eclipse tps 1 3. 注解参数的可支持数据类型: 2 4. 注解处理器 2 ... 
- paip.java win程序迁移linux的最佳实践
		paip.java win程序迁移linux的最佳实践 1.class load路径的问题... windows哈第一的从calsses目录加载,,而linux优先从jar加载.. 特别的是修理了ja ... 
- 在Servlet使用getServletContext()获取ServletContext对象出现java.lang.NullPointerException(空指针)异常的解决办法
		今天遇到了一个在servlet的service方法中获取ServletContext对象出现java.lang.NullPointerException(空指针)异常,代码如下: 1 //获取Serv ... 
- java.sql.SQLException: Io 异常: Connection reset
		当数据库连接池中的连接被创建而长时间不使用的情况下,该连接会自动回收并失效,但客户端并不知道,在进行数据库操作时仍然使用的是无效的数据库连接,这样,就导致客户端程序报“ java.sql.SQLExc ... 
随机推荐
- 1、微信小程序----弹幕的实现(无后台)
			小程序刚刚出来,现在网上的demo是多,但是要找到一个自己需要的却不容易.今天跟大家分享自己写的一个弹幕功能. 先来一张效果图: 我的思路是这样的,先用<switch>标签确定是否打开弹幕 ... 
- 获取cpu真实型号
			感谢文洋兄的思路.亲测有效. [root@storage GetCpuType]# ./main.o Intel(R) Xeon(R) CPU C5528 @ 2.13GHz #include < ... 
- selenium2之文件上传
			我们在使用selenium做web自动化测试的时候也许会碰到需要上传文件或者图片的需求.那么下面给大家介绍一下,selenium是怎么实现文件上传和哪些情况不能直接上传. 一.上传控件标签为input ... 
- PHP执行linux命令mkdir权限问题
			在linux系统中,root帐号执行php命令: mkdir('test', 0777); 结果文件的权限依然为: drwxr-xr-x 2 root root Jul 27 19:30 test ... 
- 使用插件bootstrap-table实现表格记录的查询、分页、排序等处理
			在业务系统开发中,对表格记录的查询.分页.排序等处理是非常常见的,在Web开发中,可以采用很多功能强大的插件来满足要求,且能极大的提高开发效率,本随笔介绍这个bootstrap-table是一款非常有 ... 
- Jquery-全选和取消的一个坑
			在做一个商城的购物车的时候遇到了一个坑, 购物车一般都有全选按钮, 再次点击就会全部消除, 在网上查到的答案全部都是使用attr来做的, 无一例外都不能用, 之后才知道要使用jquery的prop和r ... 
- Python序列化和反序列化
			Python序列化和反序列化 通过将对象序列化可以将其存储在变量或者文件中,可以保存当时对象的状态,实现其生命周期的延长.并且需要时可以再次将这个对象读取出来.Python中有几个常用模块可实现这一功 ... 
- ASP.NET Core MVC – Caching Tag Helpers
			简介 缓存可以大大提高应用程序加载时间和响应速度.我们可以使用缓存Tag Helpers缓存不会频繁更改的HTML内容. 在上一篇文章中,我们谈到了Tag Helpers,演示Tag Helpers能 ... 
- HAproxy部署配置
			HAproxy部署配置 拓扑图 说明: haproxy服务器IP:172.16.253.200/16 (外网).192.168.29.140/24(内网) 博客服务器组IP:192.168.29.13 ... 
- 关于appcompat_v7兼容包的详细说明
			1.appcompat_v7包的由来? appcompat_v7是Google提供的向下兼容包,是针对API level 7(Android2.1)及以上版本所开发的,其作用是为了让兼容低版本API( ... 
