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块是有原因的,你会调用一个或多个可能抛出异常的方法,或者你自己抛出异常。这就意味着你可能无法到达try块的末端来执行inputStream.close()语句。因此,你就不会关闭资源。
所以你应该将所有清理代码放到finally块中,或者使用try-with-resource语句。
1.1使用Finally 块
与try块的最后几行不同,finally块总是会被执行。这可能发生在try块成功执行之后,也可能发生在处理catch块中的异常之后。因此,将关闭代码放入finally块可以确保所有打开的资源都能被清理。如下代码:
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);
}
}
}
}
1.2 Java 7中Try-With-Resource语句
另一个选项是try-with-resource语句,如果使用的资源类实现了AutoCloseable接口,就可以使用它。这也是大多数Java标准资源类所做的。当在try子句中打开资源时,它将在执行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中添加一个@throw声明,并描述可能导致异常的情况。
/**
* This method does something extremely useful ...
*
* @param input
* @throws MyBusinessException if ... happens
*/
public void doSomething(String input) throws MyBusinessException {
...
}
4. 抛出异常时携带描述信息
这个方法背后的思想与前文提到的两个类似。不同之处是,你不用向方法的调用者提供信息。当我们在日志文件或监视工具中检测到异常发生时,你需要知道发生了什么情况,而此时你便可以通过异常提示消息来获得你想要的信息。
因此,描述信息应该尽可能准确地描述问题,并提供最可靠的信息来理解异常事件。不要产生误解,你不用写一大段文字来描述它。但是你应该用1-2个短句来解释异常发生的原因。这有助于我们的团队了解问题的严重性,并利于你去分析该事件。
如果你抛出一个特定的异常,它的类名很可能已经描述了错误原因。所以,不需要我们提供很多额外的信息。一个很好的例子是NumberFormatException,它由java.lang类的构造函数抛出。当你将一个字符串转化为Long类型时,抛出异常:
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
NumberFormatException类的名称已经说出了问题的类型。它的描述信息只需要显示出导致该问题产生的输入信息("xyz")有误即可。如果异常类的名称表达性不强,则需要在描述信息中提供所需的信息。
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
5. 优先捕获特定异常
大多数开发工具都可以实现这一方法,当你试图先捕获不太特定的异常时,IDE会提示一个不可到达的代码块。
产生该问题的原因是代码只执行到第一个匹配的异常catch块便会停止。因此,如果你先捕获一个IllegalArgumentException,那么将永远不会到达更具体的异常NumberFormatException的catch块,因为它是IllegalArgumentException的子类。
所以优先先捕获特定的异常类,然后将不是特定的异常类捕获块添加到捕获列表的末尾。
在下面的代码中展示了这样一个try-catch语句:
第一个catch块处理所有的NumberFormatException异常,
第二个catch块处理所有非NumberFormatException的illegalargumentexception异常。
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
6. 不要捕获Throwable!
Throwable是所有异常和错误的父类,千万不要在catch子句中捕获它!!!
如果在catch子句中使用Throwable,它不仅可以捕获所有异常,还将捕获所有错误。JVM会抛出比较严重的错误,而这些错误是应用程序无法处理的。典型的例子是OutOfMemoryError或StackOverflowError。这两种错误都是应用程序无法控制且无法处理的。
所以,最好不要捕获Throwable,除非是在特殊的情况下,你绝对确定你需要且有能力去处理这个错误。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}
7.不要忽略异常
当仅执行了用例的第一部分并出现了错误时你是否分析过错误报告?
这通常是由一个被忽略的异常引起的。开发人员可能非常确信它不会被抛出,并添加了一个catch块来处理或记录它。然而当你找到这个catch区块时,你很可能会发现一句“这永远不会发生”的注释。
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. 不要记录后抛出异常
这可能是本文所列方法中最容易被忽略的。你可以找到许多代码片段甚至是在jar包中,其处理异常的方式是:捕获、记录并重新抛出异常
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的可用性。
异常通常同时是错误处理机制和通知媒介。因此,你应该确保与你的同事讨论你想使用的最佳方法和规则,这样每个人都能理解这个概念,并以相同的方式使用它们。
Via:https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java
作者:Thorben Janssen ,译者:咚咚咚 ,校稿:热海
本文由JPCTT原创编译,Java程序员社区荣誉推出!
长按打开更多惊喜