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 ...
随机推荐
- RxSwift 系列(一) -- Observables
为什么使用RxSwift? 我们编写的代码绝大多数都涉及对外部事件的响应.当用户点击操作时,我们需要编写一个@IBAction事件来响应.我们需要观察通知,以检测键盘何时改变位置.当网络请求响应数据时 ...
- Work 1(导游类)(2017.06.27)
- (转)面试大总结之一:Java搞定面试中的链表题目
面试大总结之一:Java搞定面试中的链表题目 分类: Algorithm Interview2013-11-16 05:53 11628人阅读 评论(40) 收藏 举报 链表是面试中常出现的一类题目, ...
- Asp.net MVC Razor常见问题及解决方法
没有经验的童鞋就是这样磕磕碰碰出来的经验. 1,Datatype的错误提示消息无法自定义 这也许是Asp.net MVC的一个Bug.ViewModel中定义了DataType为Date字段: [Re ...
- dubbo&hsf&spring-cloud简单介绍
Dubbo: 简介:Dubbo是一个分布式服务框架,以及SOA治理方案.其功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等. 底部NIO基于netty ...
- FarmCraft[POI2014]
题目描述 In a village called Byteville, there are houses connected with N-1 roads. For each pair of ho ...
- MongoDB3.4安装配置以及与Robomongo1.1的连接——解决Authentication Failed导致的不能连接问题
本文环境:win10(64)+MongoDB(3.4.5)+Robomongo(1.1) 目录: MongoDB的安装 MongoDB的配置 Robomongo的安装以及与MongoDB的连接 一些新 ...
- phpstudy命令行中数据表插入中文显示不了的问题
在PHPstudy环境下,做MySQL操作,往数据表里面插入数据的时候,如果是中文的数据就会显示不了或者是问号?. 这个问题搞了我一晚上了,终于知道问题所在. 下载的PHPstudy的MySQL数据库 ...
- 数据库分库分表中间件 Sharding-JDBC 源码分析 —— SQL 解析(四)之插入SQL
- 弹性布局详解——5个div让你学会弹性布局
前 言 JRedu 在网页制作过程中,布局是我们最重要的一个环节.可以说布局的好坏直接影响到整个网页的成败!布局成,则事半功倍:布局败,则事倍功半. 随着移动互联的到来,响应式网站风靡.这也就兴 ...