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

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

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!

在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 this  
        inputStream.close();  
    } catch (FileNotFoundException e) {  
        log.error(e);  
    } catch (IOException e) {  
        log.error(e);  
    }  
}

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

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

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. 对异常进行文档说明

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

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

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/zhibo

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-05-28
本文作者:飒然Hang
本文来自:“互联网架构师 微信公众号”,了解相关信息可以关注“互联网架构师

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
Java 测试技术 开发者
Java单元测试与集成测试:确保代码质量的最佳实践
【4月更文挑战第2天】在软件开发中,单元测试验证单个代码单元(如Java类或方法)的功能,确保其正确性;而集成测试则关注多个组件协作时的交互。JUnit是常见的Java单元测试框架,集成测试则检验组件间接口的兼容性。Spring框架提供了集成测试的支持。遵循良好编码习惯,编写可测试代码,设计全面的测试用例,是保证代码质量和稳定性的关键。
|
1月前
|
安全 Java 开发者
Java中的多线程编程技巧与最佳实践
多线程编程是Java开发中常见的技术挑战之一。本文将介绍Java中的多线程编程技巧与最佳实践,涵盖线程创建、同步机制、线程池管理以及避免常见的多线程编程陷阱等方面,旨在帮助开发者更好地应对多线程编程的复杂性。
|
2月前
|
Java
Java中的多线程编程技巧与最佳实践
在当今软件开发领域,多线程编程已经成为不可或缺的一部分。本文将深入探讨Java中的多线程编程技巧与最佳实践,帮助开发人员更好地利用多线程提高程序性能和并发处理能力。
|
1月前
|
安全 Java 测试技术
Java安全编程:保障应用安全的最佳实践
【4月更文挑战第2天】在Java安全编程中,重点包括代码审查(借助FindBugs、PMD等工具)、严格输入验证(利用正则表达式、OWASP Java Encoder)和输出编码防止XSS攻击、实施访问控制(如Java EE注解、Spring Security)以及使用加密技术(JCE、Bouncy Castle)保护数据。遵循最佳实践,如使用最新Java版本、最小权限原则,定期安全测试,有助于构建安全的应用。开发者应持续学习和应用这些策略以应对安全挑战。
Java安全编程:保障应用安全的最佳实践
|
2月前
Exception in thread “main“ java.lang.NoClassDefFoundError: freemarker/template/Configuration
Exception in thread “main“ java.lang.NoClassDefFoundError: freemarker/template/Configuration
21 0
|
6天前
|
Java 开发者
Java中的异常处理:从基本概念到最佳实践
【4月更文挑战第30天】 在Java编程中,异常处理是确保程序健壮性和稳定性的关键机制。本文将深入探讨Java异常处理的基本概念,包括异常的分类、异常的抛出与捕获,以及如何有效地使用异常来增强代码的可读性和可维护性。此外,我们还将讨论一些关于异常处理的最佳实践,以帮助开发者避免常见的陷阱和误区。
|
10天前
|
Java
Java Exception打印及输出到日志
有时候如果打印出异常的错误,并记录下来,这里记录一下
20 5
|
18天前
|
Java
Java 中的异常处理:理解、实践与最佳实践
【4月更文挑战第18天】在 Java 编程中,异常处理是一个重要的主题。异常是程序运行过程中发生的问题,如果不能妥善处理,会导致程序崩溃或数据丢失。本文将深入探讨 Java 的异常处理机制,包括如何创建自定义异常,如何使用 try-catch-finally 语句块来处理异常,以及如何利用 finally 代码块确保资源的释放。我们还将讨论一些常见的异常处理的最佳实践,以帮助你编写出健壮且易于维护的 Java 代码。
|
21天前
|
Java 编译器 开发者
Java异常处理最佳实践
【4月更文挑战第15天】本文探讨了Java异常处理的最佳实践,旨在提升代码质量和健壮性。主要内容包括理解检查异常和非检查异常的区别,选择合适的异常类型,提供详细异常信息,合理使用try-catch-finally语句,以及利用try-with-resources简化资源管理。此外,建议记录异常信息以辅助调试,并避免在finally块中抛出异常。遵循这些实践能有效管理错误,增强程序稳定性。
|
1月前
|
监控 Java 测试技术
Java并发编程最佳实践:设计高性能的多线程系统
【4月更文挑战第6天】本文探讨了设计高性能Java多线程系统的关键原则和策略,包括理解并发基础、最小化共享状态、使用高级并发API、避免死锁、利用原子操作、优化锁策略、使用线程池以及监控和调优。遵循这些最佳实践可提升并发性能和可靠性,确保代码健壮性。同时,强调测试并发代码的重要性,以发现和解决潜在问题。