Java 中处理 Exception 的最佳实践

在Java中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范对异常的处理的原因。而团队之间的这些规范往往是截然不同的。本文给出几个被很多团队使用的异常处理最佳实践。

1. 在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);}
}

2. 指定具体的异常。

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

public void doNotDoThis() throws Exception {...
}
public void doThis() throws NumberFormatException {...
}

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

3. 对异常进行文档说明。

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

/*** This method does something extremely useful ...** @param input* @throws MyBusinessException if ... happens*/
public void doSomething(String input) throws MyBusinessException {...
}

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

在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。但这里并不是说要对错误信息长篇大论,因为本来Exception的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。

try {new Long("xyz");
} catch (NumberFormatException e) {log.error(e);
}

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

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

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

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,那么不仅仅会捕获所有exception,还会捕获error。而error是表明无法恢复的jvm错误。因此除非绝对肯定能够处理或者被要求处理error,不要捕获throwable。

public void doNotCatchThrowable() {try {// do something} catch (Throwable t) {// don't do this!}
}

7. 不要忽略异常。

很多时候,开发者很有自信不会抛出异常,因此写了一个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. 不要记录并抛出异常。

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

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);}
}

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

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

捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。需要注意的是,包装异常时,一定要把原始的异常设置为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 中处理 Exception 的最佳实践相关推荐

  1. Java 异常处理的 9 个最佳实践

    Java 异常处理的 9 个最佳实践 原文地址:https://dzone.com/articles/9-... 翻译出处:https://www.oschina.net/trans... 在 Jav ...

  2. 《转载》Java异常处理的10个最佳实践

    本文转载自 ImportNew - 挖坑的张师傅 异常处理在编写健壮的 Java 应用中扮演着非常重要的角色.异常处理并不是功能性需求,它需要优雅地处理任何错误情况,比如资源不可用.非法的输入.nul ...

  3. 关于JAVA异常处理的20个最佳实践

    关于JAVA异常处理的20个最佳实践 在我们深入了解异常处理最佳实践的深层概念之前,让我们从一个最重要的概念开始,那就是理解在JAVA中有三种一般类型的可抛类: 检查性异常(checked excep ...

  4. 小菜:Java异常处理的10个最佳实践

    转载自   小菜:Java异常处理的10个最佳实践 异常处理在编写健壮的 Java 应用中扮演着非常重要的角色.异常处理并不是功能性需求,它需要优雅地处理任何错误情况,比如资源不可用.非法的输入.nu ...

  5. JAVA应用开发MQ实战最佳实践——Series2:消息队列RocketMQ性能测试案例

    简介:JAVA应用开发MQ实战最佳实践--Series2:消息队列RocketMQ性能测试案例 往期内容 JAVA应用开发MQ实战最佳实践--Series1:RocketMQ综述及代码设计 1. 消息 ...

  6. 在ASP.NET Web API中返回错误的最佳实践

    本文翻译自:Best practice to return errors in ASP.NET Web API I have concerns on the way that we returns e ...

  7. 开发 Java 应用使用 TiDB 的最佳实践

    本文主要介绍如何开发 Java 应用程序以更好地使用 TiDB,包括开发中的常见问题与最佳实践. Java 应用中的数据库相关组件 通常 Java 应用中和数据库相关的常用组件有: 网络协议:客户端通 ...

  8. 【TiDB 最佳实践系列】开发 Java 应用使用 TiDB 的最佳实践

    原文来源: https://tidb.net/blog/ae01003e 作者: Suli, ZhangMing 本文主要介绍如何开发 Java 应用程序以更好地使用 TiDB,包括开发中的常见问题与 ...

  9. java 异常 中英文_史上最全的Java中所有Exception异常中英文对照

    Java中所有Exception异常中英文对照AclNotFoundException, 如果对不存在的访问控制列表进行访问,则会 ArithmeticException 算数异常 ArrayInde ...

最新文章

  1. 第十九篇:主题建模Topic Modelling
  2. 3d饼图 vue_Vue+Echarts构建可视化大数据平台实战项目分享(附源码)(上)
  3. 如题,用C#语言 如何给下拉列表框动态添加数据?,C#复习题 单项选择题
  4. python如何输入和输出_输入和输出
  5. Kafka消息格式中的变长字段(Varints)
  6. java正则匹配的坑_java正则表达式入坑指南
  7. 剑指Offer名企面试官精讲典型编程题pdf
  8. Python----虚拟环境
  9. Redis实现消息队列和订阅发布模式
  10. 网络是怎样连接的-IP与以太网的包收发操作(三)
  11. json接口(使用,以及自定义)
  12. 玩转软件|通过汉化WINRAR,手把手教你如何汉化软件
  13. Ubuntu下配置源地址/本地源/官方源
  14. Latex 中的空格汇总
  15. st8s003 c语言编译器,什么原因导致ST单片机STM8S003F3P6这个8位机缺货
  16. input框禁止输入的四种方式
  17. 图片太大怎么办?压缩图帮你几秒搞定在线图片压缩
  18. ping命令的基本格式
  19. 亚马逊联手AI初创公司开发ChatGPT竞品;百度公布2022年财报;高通推出付费云软件服务丨每日大事件...
  20. 双显卡只用独显好吗_双显卡怎么切换到独立显卡 双显卡只用独显方法

热门文章

  1. Linux netfilter源码分析(7)
  2. 【Shell教程】一----什么是Shell
  3. 总结-最全linux工具的使用
  4. app抢购脚本如何编写_如何用1个记事本文件征服全世界?——cmd批处理脚本编写...
  5. 【LeetCode】剑指 Offer 09. 用两个栈实现队列
  6. 微信小程序|开发实战篇之九-image-picker图片选择器组件及其子组件
  7. scanf函数的返回值问题
  8. Qt常用类——QWidget
  9. C 标准库 - string.h之strstr使用
  10. 再举个webstrom 正则应用例子。