原文: https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java java
译者:飒然Hangmysql
译文:http://www.rowkey.me/blog/2017/09/17/java-exception/web
在Java中处理异常并非一个简单的事情。不只仅初学者很难理解,即便一些有经验的开发者也须要花费不少时间来思考如何处理异常,包括须要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范对异常的处理的缘由。而团队之间的这些规范每每是大相径庭的。spring
本文给出几个被不少团队使用的异常处理最佳实践。sql
当使用相似InputStream这种须要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。api
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);
}
}
上述代码在没有任何exception的时候运行是没有问题的。可是当try块中的语句抛出异常或者本身实现的代码抛出异常,那么就不会执行最后的关闭语句,从而资源也没法释放。缓存
合理的作法则是将全部清理的代码都放到finally块中或者使用try-with-resource语句。微信
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);
}
}
}
}
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);
}
}
尽量的使用最具体的异常来声明方法,这样才能使得代码更容易理解。mybatis
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}
如上,NumberFormatException字面上便可以看出是数字格式化错误。并发
当在方法上声明抛出异常时,也须要进行文档说明。和前面的一点同样,都是为了给调用者提供尽量多的信息,从而能够更好地避免/处理异常。异常处理的 10 个最佳实践,这篇也推荐看下。
在Javadoc中加入throws声明,而且描述抛出异常的场景。
/**
* This method does something extremely useful ...
*
* @param input
* @throws MyBusinessException if ... happens
*/
public void doSomething(String input) throws MyBusinessException {
...
}
在抛出异常时,须要尽量精确地描述问题和相关信息,这样不管是打印到日志中仍是监控工具中,都可以更容易被人阅读,从而能够更好地定位具体错误信息、错误的严重程度等。
但这里并非说要对错误信息长篇大论,由于原本Exception的类名就可以反映错误的缘由,所以只须要用一到两句话描述便可。
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
NumberFormatException即告诉了这个异常是格式化错误,异常的额外信息只须要提供这个错误字符串便可。当异常的名称不够明显的时候,则须要提供尽量具体的错误信息。
如今不少IDE都能智能提示这个最佳实践,当你试图首先捕获最笼统的异常时,会提示不能达到的代码。当有多个catch块中,按照捕获顺序只有第一个匹配到的catch块才能执行。所以,若是先捕获IllegalArgumentException,那么则没法运行到对NumberFormatException的捕获。
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
Throwable是全部异常和错误的父类。你能够在catch语句中捕获,可是永远不要这么作。若是catch了throwable,那么不只仅会捕获全部exception,还会捕获error。而error是代表没法恢复的jvm错误。所以除非绝对确定可以处理或者被要求处理error,不要捕获throwable。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}
不少时候,开发者颇有自信不会抛出异常,所以写了一个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);
}
}
能够发现不少代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。以下:
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)
如上所示,后面的日志也没有附加更有用的信息。若是想要提供更加有用的信息,那么能够将异常包装为自定义异常。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
所以,仅仅当想要处理异常时才去捕获,不然只须要在方法签名中声明让调用者去处理。
捕获标准异常并包装为自定义异常是一个很常见的作法。这样能够添加更为具体的异常信息并可以作针对的异常处理。
须要注意的是,包装异常时,必定要把原始的异常设置为cause(Exception有构造方法能够传入cause)。不然,丢失了原始的异常信息会让错误的分析变得困难。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
综上可知,当抛出或者捕获异常时,有不少不同的东西须要考虑。其中的许多点都是为了提高代码的可阅读性或者api的可用性。异常不只仅是一个错误控制机制,也是一个沟通媒介,所以与你的协做者讨论这些最佳实践并制定一些规范可以让每一个人都理解相关的通用概念而且可以按照一样的方式使用它们。
更多好文章
世界上最好的关系是相互成就,点赞转发 感恩开心😃
路人甲java
▲长按图片识别二维码关注
路人甲Java:工做10年的前阿里P7,全部文章以系列的方式呈现,带领你们成为java高手,目前已出:java高并发系列、mysql高手系列、Maven高手系列、mybatis系列、spring系列,正在连载springcloud系列,欢迎关注!
本文分享自微信公众号 - 路人甲Java(javacode2018)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。