前言
- 在前两篇文章中,我们深入研究了
Java
异常处理中的Throwable
类和异常处理常用关键字以及finally
使用中的陷阱。在本文中,我们将探讨异常处理的一些最佳实践,这些实践有助于编写更健壮和可维护的程序代码。
最佳实践
早抛出,晚捕获原则
- 早抛出,晚捕获是异常处理中比较经典的原则,它告诉我们出现异常时应该尽可能早的抛出而不是等异常变得更加复杂后再抛出;而捕获异常时,需要等我们可以处理时再进行捕获,而不是为了捕获而捕获。
public void processInput(String input) {
if (input == null) {
// 尽可能早的抛出而不是等到异常变得更加复杂后抛出
throw new IllegalArgumentException("Input cannot be null");
}
// 继续处理输入
}
只捕获实际可处理的异常
- 不要为了捕获异常而捕获,只捕获可以处理的异常来让代码更加健壮,无法处理异常捕获并没有实际意义。
try {
// 可能抛出异常的代码
} catch (FileNotFoundException e) {
// 处理文件未找到异常
} catch (IOException e) {
// 处理其他 IO 异常
}
// 没有实际意义的捕获
catch (NoSuchMethodException e) {
// 避免这种情况,因为它没有任何帮助
throw e;
}
不要忽略捕捉的异常
- 避免使用空的
catch
块来忽略异常,这可能导致潜在问题被掩盖,使得调试和维护变得更加困难。
try {
// 可能抛出异常的代码
} catch (Exception e) {
// 空的 catch 块,可能掩盖潜在问题
}
抛出具体的检查性异常
- 在方法中抛出具体的受检异常,而不是通用的
Exception
,以便提供更明确的异常信息,让调用者更容易处理异常情况。
public void readFile(String filename) throws IOException {
// 读取文件的代码
}
正确包装自定义异常中的异常
- 在自定义异常中包装其他异常时,确保将原始异常作为参数传递给自定义异常的构造函数,以便堆栈跟踪包含所有相关信息,避免信息丢失。
public class CustomException extends RuntimeException {
public CustomException(String message, Throwable cause) {
super(message, cause);
}
}
记录或抛出异常,但不要同时执行
- 在异常处理中,要么将异常记录下来要么将其抛出,不要两者兼而有之。否则会导致异常被记录多次,对排查问题造成干扰
try {
// 可能抛出异常的代码
} catch (Exception e) {
// 要么记录处理异常
logger.error("An error occurred: {}", e.getMessage(), e);
// 要么抛出异常
throw new CustomException("An error occurred", e);
}
finally 中永远不要抛出异常或返回值
- 在
finally
块中进行资源清理时,永远不要抛出异常或返回值,以避免丢失try-catch
代码块中的异常或者返回值。
try {
// 可能抛出异常的代码
} finally {
// 不要在 finally 块中抛出异常
// 可以记录异常或其他清理操作
}
避免使用异常进行流程控制
- 异常应该用于处理错误和异常情况,而不应该被用作正常流程控制的手段。使用条件语句或其他逻辑结构来处理正常的程序流程。
使用模板方法处理重复的 try-catch
- 如果在多个地方有相似的异常处理逻辑,可以考虑将其提取为一个模板方法,以避免重复代码。比如我们经常使用的关闭连接的代码块,可以按照下面的方式实现,而不是在每个需要关闭的地方使用
try-catch
:
class DBUtil{
public static void closeConnection(Connection conn){
try{
conn.close();
} catch(Exception ex){
//Log Exception - Cannot close connection
}
}
}
public void process() {
Connection conn = null;
try{
conn = getConnection();
....
} finally{
DBUtil.closeConnection(conn);
}
}
尽量只抛出和方法相关的异常
- 相关性对于保持代码的可读性非常重要。一个尝试读取文件的方法,如果抛出
NullPointerException
,那么它不会给调用者任何相关的信息。相反,如果这种异常被包裹在自定义异常中则会更好。NoSuchFileFoundException
则对该方法的调用者更有用。
异常处理后清理资源
- 在异常处理中,确保清理已分配的资源,以避免资源泄漏。可以通过在
finally
块中进行清理操作来实现。
InputStream inputStream = null;
try {
inputStream = new FileInputStream("example.txt");
// 处理输入流
} catch (IOException e) {
// 处理异常
} finally {
// 关闭输入流,确保资源得到释放
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// 记录关闭异常
logger.error("Error closing input stream: {}", e.getMessage(), e);
}
}
}
总结
- 通过遵循这些最佳实践,我们可以提高代码的质量、可读性和可维护性,从而确保程序在面对各种情况时能够更加健壮和可靠,以及更好的可维护性。
个人简介