Java 异常处理下篇:11 个异常处理最佳实践

文章目录

  • 前言
  • 最佳实践
    • 早抛出,晚捕获原则
    • 只捕获实际可处理的异常
    • 不要忽略捕捉的异常
    • 抛出具体的检查性异常
    • 正确包装自定义异常中的异常
    • 记录或抛出异常,但不要同时执行
    • finally 中永远不要抛出异常或返回值
    • 避免使用异常进行流程控制
    • 使用模板方法处理重复的 try-catch
    • 尽量只抛出和方法相关的异常
    • 异常处理后清理资源
  • 总结
  • 个人简介

前言

  • 在前两篇文章中,我们深入研究了 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);
        }
    }
}

总结

  • 通过遵循这些最佳实践,我们可以提高代码的质量、可读性和可维护性,从而确保程序在面对各种情况时能够更加健壮和可靠,以及更好的可维护性。

个人简介

你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

保持关注我的博客,让我们共同追求技术卓越。

你可能感兴趣的:(Java,后端经典面试题,Java,java)