来源 | rowkey.me/

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

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

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

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

当使用类似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 thisinputStream.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);}
}

指定具体的异常

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

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

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的可用性。

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

往期推荐

重磅!可以发朋友圈和搜一搜的PC端微信来了,赶紧来下载!

基于SpringBoot + Vue的小程序商城项目(附源码)

一次 Nacos 的踩坑记录!

无休止加班的真正原因!你们公司是这样吗?

60岁还在写代码的开发者,他的建议或许正是你现在焦虑的根源!

喜欢本文欢迎转发,关注我订阅更多精彩

关注我回复「加群」,加入Spring技术交流群

Exception经验之谈,万万没想到被很多团队采纳!相关推荐

  1. 6月Top 20榜单出炉啦! 万万没想到区块链大佬竟在忙这个...

    6月Top 20榜单出炉啦! 万万没想到区块链大佬竟在忙这个... 一个项目在 GitHub 上的活跃指数,在一定程度上代表了这个项目的开发状态. 频繁更新代码的项目有可能正处于构建和完善中,而停止更 ...

  2. 和信创天下一代云桌面的三个“万万没想到”

    知名女演员李冰冰在第一次荣膺华表奖优秀女主角奖时,发表了一段感人至深的致辞:"刚才我从台下走到台上用了不到一分钟,但实际上我走了十年,终于拿到了这个奖项." 十年磨一剑,不仅可以磨 ...

  3. 显卡暴涨,这我万万没想到啊

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 梦晨 晓查 发自 凹非寺  量子位 报道 | 公众号 QbitAI ...

  4. 万万没想到,坤坤洗白的第一步是周琦…

    前晚(2日)中国男篮与波兰队的关键一战惜败后,几乎所有中国球迷都在哭"琦","琦"到一夜未眠. 周琦关键时刻边线发球失误,硬生生把中国男篮提前出线的jio给拖了 ...

  5. 科学家们竟用乐高观察细胞,网友:万万没想到啊

    杨净 子豪 发自 凹非寺 量子位 报道 | 公众号 QbitAI 玩乐高还能玩出个显微镜?! 原本以为是一个普普通通的玩具. 没想到,还真能当成显微镜来用,是能看到细胞的那种. 真·高端新玩法! 比如 ...

  6. 显卡暴涨,等等党输了,这我万万没想到啊

    梦晨 晓查 发自 凹非寺  量子位 报道 | 公众号 QbitAI 万万没想到,去年信心满满准备好钱包要买30系显卡的我,现在连1660Ti都快买不起了. 不仅官方原价3899的RTX 3070,一路 ...

  7. python这个软件学会能做什么工作-万万没想到,学会Python即使不做程序员都能月入过万!...

    昨天,我公司面试了1个同学,应聘新媒体运营,专业能力还不错.他简历上技能栏还写着会Python,我问了他一个通过爬虫采集数据的问题,他都顺畅的答出来了. 最后聊薪资时,他说期待7000,我直接给他开了 ...

  8. 【杂谈】万万没想到,有三还有个保密的‘朋友圈’,那里面都在弄啥!

    万万没想到,有一天我们也会标题党,透过标题看本质,今天说的确实是那么回事儿也. 一直以来我们都有一个不公开的私密社区叫有三AI知识星球,但是经常遇到一些朋友,甚至是加入季划的同学都在问我,那是个啥?看 ...

  9. 牛客网-数据结构笔试题目(二)-万万没想到之抓捕孔连顺思路解析(附源码)

    题意 我叫王大锤,是一名特工.我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺.和我一起行动的还有另外两名特工,我提议 我们在字节跳动大街的N个建筑中选定3个埋伏地点. 为了相互照应,我们决 ...

最新文章

  1. sublime text 2 c++编译 环境 问题小结
  2. 《JavaScript高级程序设计》读书笔记(十一):内置对象Global
  3. 51CTO-风哥-ORACLE学习计划(持续更新中)
  4. python利用管道popen调用.exe进行交互
  5. 还找不到数据异常原因?这份教程赶快拿走
  6. hmailserver怎么搭建php,hMailServer邮件服务器安装配置(亲测可用)
  7. wandb(wb)(weights and biases): 深度学习轻量级可视化工具入门教程
  8. 光纤传感器实验模块_准分布式光纤光栅传感器(光纤光栅串)的概念
  9. Java黑皮书课后题第1章:1.5(计算表达式)编写程序,显示以下式子的结果
  10. Apache Shiro第2部分–领域,数据库和PGP证书
  11. Redhat7.3安装配置Telnet详细教程
  12. JVM学习-CAS与原子类
  13. php70w-mbstring,php安装
  14. Linux脚本Shell命令
  15. struts2登录注册示例_Struts 2动作示例教程
  16. emacs VS vim 替换为回车符
  17. 条码打印一 - Zebra斑马打印机三种打印方式的利弊
  18. 电脑共享手机连接的WiFi
  19. 2017计算机夏令营汇总
  20. [渝粤教育] 西南科技大学 会计电算化 在线考试复习资料2021版(1)

热门文章

  1. Androidの多线程之更新ui(AsyncTask)
  2. 众方网络电话VOIP FREEGO EIS系统命令
  3. Tomcat 集群问题
  4. eclipse 启动 找不到 JRE JDK的解决方法
  5. linux 发行版 suse opensuse 区别
  6. python3 value查key的三种方法
  7. 区块链 软分叉和硬分叉 简介
  8. linux Makefile中 make -C和M=的作用
  9. metasploit msfconsole 命令参数
  10. erdas图像增强步骤_基于erdas的图像增强处理