Java 处理 Exception 的 9 个最佳实践!

摘要:
在Java中处理异常并不是一个简单的事情。异常处理的10个最佳实践,这篇也推荐看下。在Javadoc中加入throws声明,并且描述抛出异常的场景。但这里并不是说要对错误信息长篇大论,因为本来Exception的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。try{newLong;}catch{log.error;}NumberFormatException即告诉了这个异常是格式化错误,异常的额外信息只需要提供这个错误字符串即可。因此,如果先捕获IllegalArgumentException,那么则无法运行到对NumberFormatException的捕获。如果catch了throwable,那么不仅仅会捕获所有exception,还会捕获error。

在Java中处理异常并不是一个简单的事情。

不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。

这也是绝大多数开发团队都会制定一些规则来规范对异常的处理的原因。而团队之间的这些规范往往是截然不同的。

本文给出几个被很多团队使用的异常处理最佳实践。

1. 在Finally块中清理资源或者使用try-with-resource语句

当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。

publicvoiddoNotCloseResourceInTry(){
FileInputStreaminputStream=null;
try{
Filefile=newFile("./tmp.txt");
inputStream=newFileInputStream(file);
//usetheinputStreamtoreadafile
//doNOTdothis
inputStream.close();
}catch(FileNotFoundExceptione){
log.error(e);
}catch(IOExceptione){
log.error(e);
}
}

上述代码在没有任何exception的时候运行是没有问题的。但是当try块中的语句抛出异常或者自己实现的代码抛出异常,那么就不会执行最后的关闭语句,从而资源也无法释放。

合理的做法则是将所有清理的代码都放到finally块中或者使用try-with-resource语句。推荐阅读:10个深恶痛绝的异常。关注微信公众号:Java技术栈,在后台回复:Java,可以获取我整理的 N 篇 Java 教程,都是干货。

publicvoidcloseResourceInFinally(){
FileInputStreaminputStream=null;
try{
Filefile=newFile("./tmp.txt");
inputStream=newFileInputStream(file);
//usetheinputStreamtoreadafile
}catch(FileNotFoundExceptione){
log.error(e);
}finally{
if(inputStream!=null){
try{
inputStream.close();
}catch(IOExceptione){
log.error(e);
}
}
}
}

publicvoidautomaticallyCloseResource(){
Filefile=newFile("./tmp.txt");
try(FileInputStreaminputStream=newFileInputStream(file);){
//usetheinputStreamtoreadafile
}catch(FileNotFoundExceptione){
log.error(e);
}catch(IOExceptione){
log.error(e);
}
}

2. 指定具体的异常

尽可能的使用最具体的异常来声明方法,这样才能使得代码更容易理解。

publicvoiddoNotDoThis()throwsException{
...
}
publicvoiddoThis()throwsNumberFormatException{
...
}

如上,NumberFormatException字面上即可以看出是数字格式化错误。

3. 对异常进行文档说明

当在方法上声明抛出异常时,也需要进行文档说明。和前面的一点一样,都是为了给调用者提供尽可能多的信息,从而可以更好地避免/处理异常。异常处理的10个最佳实践,这篇也推荐看下。

在Javadoc中加入throws声明,并且描述抛出异常的场景。

/**
*Thismethoddoessomethingextremelyuseful...
*
*@paraminput
*@throwsMyBusinessExceptionif...happens
*/
publicvoiddoSomething(Stringinput)throwsMyBusinessException{
...
}

4. 抛出异常的时候包含描述信息

在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。

但这里并不是说要对错误信息长篇大论,因为本来Exception的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。

try{
newLong("xyz");
}catch(NumberFormatExceptione){
log.error(e);
}

NumberFormatException即告诉了这个异常是格式化错误,异常的额外信息只需要提供这个错误字符串即可。当异常的名称不够明显的时候,则需要提供尽可能具体的错误信息。

5. 首先捕获最具体的异常

现在很多IDE都能智能提示这个最佳实践,当你试图首先捕获最笼统的异常时,会提示不能达到的代码

当有多个catch块中,按照捕获顺序只有第一个匹配到的catch块才能执行。因此,如果先捕获IllegalArgumentException,那么则无法运行到对NumberFormatException的捕获。

publicvoidcatchMostSpecificExceptionFirst(){
try{
doSomething("Amessage");
}catch(NumberFormatExceptione){
log.error(e);
}catch(IllegalArgumentExceptione){
log.error(e)
}
}

6. 不要捕获Throwable

Throwable是所有异常和错误的父类。你可以在catch语句中捕获,但是永远不要这么做。

如果catch了throwable,那么不仅仅会捕获所有exception,还会捕获error。而error是表明无法恢复的jvm错误。因此除非绝对肯定能够处理或者被要求处理error,不要捕获throwable。

publicvoiddoNotCatchThrowable(){
try{
//dosomething
}catch(Throwablet){
//don'tdothis!
}
}

7. 不要忽略异常

很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。

publicvoiddoNotIgnoreExceptions(){
try{
//dosomething
}catch(NumberFormatExceptione){
//thiswillneverhappen
}
}

但现实是经常会出现无法预料的异常或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。

合理的做法是至少要记录异常的信息。

publicvoidlogAnException(){
try{
//dosomething
}catch(NumberFormatExceptione){
log.error("Thisshouldneverhappen:"+e);
}
}

8. 不要记录并抛出异常

可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下:

try{
newLong("xyz");
}catch(NumberFormatExceptione){
log.error(e);
throwe;
}

这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下:

17:44:28,945ERRORTestExceptionHandling:65-java.lang.NumberFormatException:Forinputstring:"xyz"
Exceptioninthread"main"java.lang.NumberFormatException:Forinputstring:"xyz"
atjava.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
atjava.lang.Long.parseLong(Long.java:589)
atjava.lang.Long.(Long.java:965)
atcom.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
atcom.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)

如上所示,后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。

publicvoidwrapException(Stringinput)throwsMyBusinessException{
try{
//dosomething
}catch(NumberFormatExceptione){
thrownewMyBusinessException("Amessagethatdescribestheerror.",e);
}
}

因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理。

9. 包装异常时不要抛弃原始的异常

捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。

需要注意的是,包装异常时,一定要把原始的异常设置为cause(Exception有构造方法可以传入cause)。否则,丢失了原始的异常信息会让错误的分析变得困难。

publicvoidwrapException(Stringinput)throwsMyBusinessException{
try{
//dosomething
}catch(NumberFormatExceptione){
thrownewMyBusinessException("Amessagethatdescribestheerror.",e);
}
}

总结

综上可知,当抛出或者捕获异常时,有很多不一样的东西需要考虑。其中的许多点都是为了提升代码的可阅读性或者api的可用性。

异常不仅仅是一个错误控制机制,也是一个沟通媒介,因此与你的协作者讨论这些最佳实践并制定一些规范能够让每个人都理解相关的通用概念并且能够按照同样的方式使用它们。

原文: https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java
译者:飒然Hang
译文:http://www.rowkey.me/blog/2017/09/17/java-exception/

推荐去我的博客阅读更多:

1.Java JVM、集合、多线程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、后端、架构、阿里巴巴等大厂最新面试题

觉得不错,别忘了点赞+转发哦!

免责声明:文章转载自《Java 处理 Exception 的 9 个最佳实践!》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇hiveql函数笔记(二)linux安装jdk与配置-centos7版本下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

java大文件(视频)切片上传

java两台服务器之间,大文件上传(续传),采用了Socket通信机制以及JavaIO流两个技术点,具体思路如下: 实现思路: 1、服:利用ServerSocket搭建服务器,开启相应端口,进行长连接操作 2、服:使用ServerSocket.accept()方法进行阻塞,接收客户端请求 3、服:每接收到一个Socket就建立一个新的线程来处理它 4、客:...

Java IO 关闭流的方式

Java IO 关闭流的方式 分类 练习:将分割文件中的流关闭方式改为finally形式 练习:文件合并中的流关闭方式改为try()形式 传送门:这里更详细 分类 在try中关闭 弊端是如果文件不存在或者读取的时候有问题而抛出异常,那么就不会执行流的关闭语句,存在资源占用隐患 在finally中关闭 这是标准的关闭流的方式 1、首先把引用声...

Android 文件的选择

Android 文件的选择 打开文件选择器 private void showFileChooser() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); intent.addCat...

java异常处理中的return和throw

如果把return和throw放在一起,直接会提示错误。”Unreachable statement”(无法被执行). 然而finally却可以成功骗过编译器让两者并存(是不是可以算是编译器的一个小bug呢),结果是后执行的会覆盖前者。 finally如果有return会覆盖catch里的throw,同样如果finally里有throw会覆盖catch...

ftp上传文件

  /// 上传文件   /// </summary>   /// <param name="fileBytes"></param>   /// <param name="originalName"></param>   ///...

org.apache.commons.httpclient工具类(封装的HttpUtil)

import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOEx...