Java处理Exception被很多团队使用的异常处理最佳实践

小郝不负流年
小郝不负流年   + 关注
2021-02-23 21:45:05   阅读529   评论0

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

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

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

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

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


  1. public void doNotCloseResourceInTry() {  
  2.     FileInputStream inputStream = null;  
  3. try {  
  4.         File file = new File("./tmp.txt");  
  5.         inputStream = new FileInputStream(file);  
  6. // use the inputStream to read a file  
  7. // do NOT do this  
  8.         inputStream.close();  
  9.     } catch (FileNotFoundException e) {  
  10. log.error(e);  
  11.     } catch (IOException e) {  
  12. log.error(e);  
  13.     }  
  14. }  

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

合理的做法则是将所有清理的代码都放到finally块中或者使用try-with-resource语句。


  1. public void closeResourceInFinally() {  
  2.     FileInputStream inputStream = null;  
  3. try {  
  4.         File file = new File("./tmp.txt");  
  5.         inputStream = new FileInputStream(file);  
  6. // use the inputStream to read a file  
  7.     } catch (FileNotFoundException e) {  
  8. log.error(e);  
  9.     } finally {  
  10. if (inputStream != null) {  
  11. try {  
  12.                 inputStream.close();  
  13.             } catch (IOException e) {  
  14. log.error(e);  
  15.             }  
  16.         }  
  17.     }  
  18. }  
  19.   
  20. public void automaticallyCloseResource() {  
  21.     File file = new File("./tmp.txt");  
  22. try (FileInputStream inputStream = new FileInputStream(file);) {  
  23. // use the inputStream to read a file  
  24.     } catch (FileNotFoundException e) {  
  25. log.error(e);  
  26.     } catch (IOException e) {  
  27. log.error(e);  
  28.     }  
  29. }  

指定具体的异常

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


  1. public void doNotDoThis() throws Exception {  
  2.   
  3.     ...  
  4.   
  5. }  
  6.   
  7. public void doThis() throws NumberFormatException {  
  8.   
  9.     ...  
  10.   
  11. }  

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

对异常进行文档说明

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

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


  1. /** 
  2.  
  3.  * This method does something extremely useful ... 
  4.  
  5.  * 
  6.  
  7.  * @param input 
  8.  
  9.  * @throws MyBusinessException if ... happens 
  10.  
  11.  */  
  12.   
  13. public void doSomething(String input) throws MyBusinessException {  
  14.   
  15.     ...  
  16.   
  17. }  

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

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

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


  1. try {  
  2.   
  3. new Long("xyz");  
  4.   
  5. } catch (NumberFormatException e) {  
  6.   
  7. log.error(e);  
  8.   
  9. }  

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

首先捕获最具体的异常

现在很多IDE都能智能提示这个最佳实践,当你试图首先捕获最笼统的异常时,会提示不能达到的代码。当有多个catch块中,按照捕获顺序只有第一个匹配到的catch块才能执行。因此,如果先捕获IllegalArgumentException,那么则无法运行到对NumberFormatException的捕获。


  1. public void catchMostSpecificExceptionFirst() {  
  2.   
  3. try {  
  4.   
  5.         doSomething("A message");  
  6.   
  7.     } catch (NumberFormatException e) {  
  8.   
  9. log.error(e);  
  10.   
  11.     } catch (IllegalArgumentException e) {  
  12.   
  13. log.error(e)  
  14.   
  15.     }  
  16.   
  17. }  

 不要捕获Throwable

Throwable是所有异常和错误的父类。你可以在catch语句中捕获,但是永远不要这么做。如果catch了throwable,那么不仅仅会捕获所有exception,还会捕获error。而error是表明无法恢复的jvm错误。因此除非绝对肯定能够处理或者被要求处理error,不要捕获throwable。


  1. public void doNotCatchThrowable() {  
  2.   
  3. try {  
  4.   
  5. // do something  
  6.   
  7.     } catch (Throwable t) {  
  8.   
  9. // don't do this!  
  10.   
  11.     }  
  12.   
  13. }  

不要忽略异常

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


  1. public void doNotIgnoreExceptions() {  
  2.   
  3. try {  
  4.   
  5. // do something  
  6.   
  7.     } catch (NumberFormatException e) {  
  8.   
  9. // this will never happen  
  10.   
  11.     }  
  12.   
  13. }  

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


  1. public void logAnException() {  
  2.   
  3. try {  
  4.   
  5. // do something  
  6.   
  7.     } catch (NumberFormatException e) {  
  8.   
  9. log.error("This should never happen: " + e);  
  10.   
  11.     }  
  12.   
  13. }  

不要记录并抛出异常

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


  1. try {  
  2.   
  3. new Long("xyz");  
  4.   
  5. } catch (NumberFormatException e) {  
  6.   
  7. log.error(e);  
  8.   
  9. throw e;  
  10.   
  11. }  

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


  1. 17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"  
  2.   
  3. Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"  
  4.   
  5. at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)  
  6.   
  7. at java.lang.Long.parseLong(Long.java:589)  
  8.   
  9. at java.lang.Long.(Long.java:965)  
  10.   
  11. at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)  
  12.   
  13. at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)  

如上所示,后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,

那么可以将异常包装为自定义异常。


  1. public void wrapException(String input) throws MyBusinessException {  
  2.   
  3. try {  
  4.   
  5. // do something  
  6.   
  7.     } catch (NumberFormatException e) {  
  8.   
  9. throw new MyBusinessException("A message that describes the error.", e);  
  10.   
  11.     }  
  12.   
  13. }  

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

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

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

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


  1. public void wrapException(String input) throws MyBusinessException {  
  2.   
  3. try {  
  4.   
  5. // do something  
  6.   
  7.     } catch (NumberFormatException e) {  
  8.   
  9. throw new MyBusinessException("A message that describes the error.", e);  
  10.   
  11.     }  
  12.   
  13. }  

总结

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

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

文章来源:微信公众号-Java程序员社区
对我有用,我要     转载  
文章分类: Java  
所属标签: 异常   Exception  
  • 0条评论
  • 只看作者
  • 按时间|按热度
  • 由于本人多次涉及需要打印这个证明,而每次都会忘记入口,在网上各种搜索各种摸索很是浪费时间。故本次将操作流程整理记录下来,以备忘。同时也分享给大家。1、打开湖北政务服务网,地址:http://zwfw.hubei.gov.cn/s/index.html2、切换区域到“武汉市”3、在“特色服务”模块找到“(个人)武汉市社会保险公共服务平台”4、进入“(个人)武汉市社会保险公共服务平台”,登录账号密码<imgsrc="https://cdnstatic.hoscen.cn/blog/article/184053017752895488/img/497065960be44747825acb86a17483c1.png"style=
  • java中的注解@Generated用来标注源代码中的某些东西是由某些工具生成的,而不是人写的。这个注解可以用于:包、类、注解类、方法、构造方法、变量、本地变量、方法参数。
  • 如何使用postman模拟http发送xml参数报文的POST请求?1、postman工具通过安装软件或使用谷歌插件都可以,这里不再赘述。2、配置postman,选择POST,填写URL;切换到Headers,添加Content-Type:text/xml 3、切换到body,选择raw,XML,下方填写你的请求报文4、点击Send发送请求,如图可以看到响应状态、时间、结果等信息5、讲到这里就结束了,是不是学会了?快去试试吧!
  • 解决办法:是idea的加载有问题,关闭IDEA,在工程的根目录下删除.idea文件,重新打开IDEA加载就好了。
  • Failedtoloadprojectconfiguration:cannotparsefileF:/xx/.idea/modules.xml:ParseErrorat[row,col]:[1,1]Message:文件提前结束。解决办法:关闭idea,删掉这个文件,重新打开idea
  • 建立服务器内网其他IP端口的隧道,可以将远程的服务映射到本地进行访问。finalshell配置隧道方法:
  • 上传图片微服务网关报错:UT000054:Themaximumsize1048576foranindividualfileinamultipartrequestwasexceeded原因:所用容器对文件的限制一般项目用的是spring 对spring参数进行配置即可spring:servlet:multipart:#MultipartPropertiesmax-request-size:10MB#总文件大小max-file-size:10MB#单个文件大小注意如果是nginx代理配置限制,报错信息里面会标记nginx。届时需要设置nginx在server_name下加上client_max_body_size20m;
  • 目录:1、安装node.js环境2、安装cnpm3、安装vue-cli脚手架构建工具4、用vue-cli构建项目5、安装项目所需的依赖6、项目运行7、项目打包1、安装node.js环境下载地址:https://nodejs.org/zh-cn/安装过程没有太多好说的,安装完成后 win+R打开命令行输入node -v , 如图,出现版本号说明安装成功。npm包管理器是集成在node中的 , npm -v可以查看版本2、安装cnpm由于有些npm有些资源被屏蔽或者是国外资源的原因,经常会导致用npm安装依赖包的时
  • 控制台信息:Unabletostartthedaemonprocess.Thisproblemmightbecausedbyincorrectconfigurationofthedaemon.Forexample,anunrecognizedjvmoptionisused.PleaserefertotheUserManualchapteronthedaemonathttps://docs.gradle.org/6.3/userguide/gradle_daemon.htmlProcesscommandline:E:\DevelopTools\Java\OpenJDK8U-jdk_x86-32_windows_hotspot_8u282b08\jdk8u282-b08\bin\java.exe-XX:MaxHeapSize=1024m-Xms1024m-Xmx2048m-Dfile.encoding=UTF-8-Duser.country=CN-Duser.language=zh-Duser.variant-cpE:\DevelopTools\gradle-6.8.2-all\gradle_resp\wrapper\dists\gradle-6.3-bin\8tpu6egwsccjzp10c1jckl0rx\gradle-6.3\lib\gradle-launcher-6.3.jarorg.gradle.launcher.daemon.bootstrap.GradleDaemon6.3Pleasereadthefollowingprocessoutputtofindoutmore:-----------------------ErroroccurredduringinitializationofVMCouldnotreserveenoughspacefor2097152KBobjectheapPickedupJAVA_
  • 问题maven同一个版本号部署远程仓库,出现报错:Returncodeis: 400,ReasonPhrase:Repositorydoesnotallowupdatingassets:maven-releases. 解决maven在部署(deploy)时候抛的异常,存储库不允许更新资产,这个就是和私有maven库更新策略有关。具体设置步骤:1.访问私有库管理界面http://xxx.xxx.xxx.xxx:80812.登录管理员账号(默认:admin/admin123)3.进入设置界面->repository->repositories->maven-releases(自己需要部署的目标库)->setting->Deploymentpollcy(Allowredeploy)允许更新