当你有一个 try, catch 块时,正在检查一个文件对象是否存在冗余和糟糕的风格?

is checking if a file object exists redudent and bad style when you have a try, catch block?

在 try catch 之前检查文件对象是否存在是否过于冗余且被认为是糟糕的风格。如果文件对象不存在,无论如何都会调用 FileNotFoundException?

    if (!in.exists()) {
      System.err.println("Missing important input files!");
      System.exit(1);
    }

    try {
        int [] numbers = new int[100];

        Scanner input = new Scanner(in);
        for (int i = 0; i < numbers.length; i++) {
            numbers[i] = input.nextInt();
        }
        input.close();

        Arrays.sort(numbers);

        PrintWriter output = new PrintWriter("output.txt");
        for (int i = 0; i < numbers.length; i++) {
            output.println(numbers[i]);
        }
        output.close();
    } catch (FileNotFoundException ex) {
        System.err.println("FileNotFoundException");
    }

是的,exists 测试是多余的。 Scanner 中发生隐式存在性测试。 (实际上,它发生在打开文件的系统调用中。)

并且很难(并且错误地)避免捕捉 new Scanner(File) 声明的 IOException。那将是 (IMO) 非常糟糕的风格。

还有一点就是在调用 exists 和打开文件之间存在“竞争条件”。可以想象,JVM 之外的其他东西可以在测试和打开尝试之间的短时间内创建、删除或重命名文件。这种东西在过去的特权升级攻击中被利用过。

这些可以“视为已读”。

所以,IMO,这里唯一真正的争议点是依赖异常来检查文件是否存在在风格上是否错误(在这种情况下)。


有人会这样争论:

  1. 异常不应该用于流量控制; (参见 https://wiki.c2.com/?DontUseExceptionsForFlowControl)。

  2. 检测丢失文件是流量控制。

  3. 因此你不应该为此使用 try / catch。

对此的反对论点是“异常不应该用于流量控制”实际上应该说“异常不应该用于 正常 流量控制”,并且处理边缘情况(例如丢失文件)不是正常流控制。

我们可以通过查看反对使用异常进行流程控制的论点来分解它。主要有:

  • 可读性 - try / catch 代码比简单测试更难阅读。我认为这不适用于这种情况。在这种情况下,您无论如何都必须处理异常。添加的 exists 测试只是添加代码,因此需要阅读的代码更多,可读性更差。

  • 效率 - 在 Java 中创建、抛出和捕获异常比简单的 if 测试更昂贵。有两个反击:

    • 单个测试的效率很可能无关紧要。在这种情况下,可能节省的 微秒 完全无关紧要。

    • 在这种情况下,我们还必须考虑在非异常情况下发生的冗余 exists 测试的成本。那是一个系统调用。它可能比通过避免异常处理而潜在的节省成本更高。如果我们假设输入文件 usually 存在,那么我们将 usually 支付性能损失。 (算一算....)

  • “我不喜欢例外”1 - 嗯,是的,但这不是有效的文体论点,所以我们不能用文体术语来解决它。

最后一个反驳论点是,如果 Java 设计者没有打算在缺少所需文件的情况下使用异常,那么他们就不会声明 API 来抛出异常。他们当然不会决定将例外 checked.


1 - 或者 <insert_name_of_some_expert> 认为他们不好。