批判我的异常处理策略

Geo*_*pty 2 java exception-handling

在过去7年的大部分时间里,我一直在进行面向对象编程,在此期间使用Java打开和关闭.有些事情我确信我有很好的把握,比如最有用的设计模式.实际上,下面的代码允许我在一天的时间内创建一个小系统,它将处理我们现在准备实现的一个特定实例,同时具有足够的灵活性来处理我已经被告知的未来需求:

public void importAndArchive(File detectedFile) throws FileNotFoundException, IOException {
        File workingCopy = returnWorkingCopy(detectedFile);
        List<String[]> csvData = csvData(workingCopy);
        setHeaderFields(csvData.get(0));

        importData(csvData); //subclass will implement this abstract method

        archiveWorkingCopy(workingCopy);
    }
Run Code Online (Sandbox Code Playgroud)

我没有表现出上面的夸耀我对模板方法的掌握,而是作为一个起点来讨论我的能力中的差距,这是我的OO设计能力的明显.这种差距是异常处理的系统方法.事实上你可以在那个方法签名中看到我暂时重新抛出一些异常,但实际上我已经在应用程序的另一个角落里根除了同样的东西.

在我走得更远之前,由于这是我第一次特别系统化的尝试,我想对我到目前为止所做的事情进行一些验证.我们的应用程序循环遍历一堆文件,"处理"它们并"归档"它们.非常标准的票价.我为了尽快推出原型而做出的一个决定如下.应用程序根据(ResourceBundled)属性文件中的数据进行初始化.ResourceBundle API中的各种方法抛出未经检查的异常,但我暂时并没有真正处理它们,理由是它们会阻止应用程序启动,并且堆栈跟踪暂时就足够了.

但是我选择了一个用于CSV处理的库,它会抛出已检查的异常.然后,NetBeans很容易将这些异常扩散开始;)以下是我重新编写的一个实际处理这些异常的方法:

private String detectImportHandler(File detectedFile) throws Exception {
    String[] firstLine = null;

    try {
        /*
         * CSVReader throws checked exceptions
         */
        CSVReader csvReader = new CSVReader(new FileReader(detectedFile));
        firstLine = csvReader.readNext();
        csvReader.close();
    }
    catch(Exception x) {
        throw new Exception("CSVReader unable to process file: " + x.getMessage());
    }

    try {
        return firstLine[1];
    }
    catch(Exception x) {
        /*
         * since we're re-throwing CSVReader's checked exceptions it seems easiest
         * to also re-throw null-pointer and/or array-out-of-bounds errors
         */
        throw new Exception(
                "First line null or did not have importHandlerType field: " + x.getMessage());
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,在处理文件的循环中调用上述方法:

    try {
        importHandlerType = detectImportHandler(detectedFile);
    }
    catch(Exception x) {
        unusableFileErrors.put(detectedFile.getName(), x.getMessage());
        continue;
    }
Run Code Online (Sandbox Code Playgroud)

unusableFileErrors是一个Map,我想当我完成迭代文件时,我可以使用这个地图和它包含的特定于文件的消息来处理更高级别的事情,例如记录,将文件移动到其他地方系统等

无论如何,我已经走了很长时间.我已经订购了"强大的Java"这本书,我希望它与SO社区之间,我可以改善这个被忽视的能力方面.我在SO上看过其他类似的问题,但我认为在实际代码的上下文中请求具体的建议可能是有益的.

小智 15

几点评论:

  1. 包装异常e时,只需使用e.getMessage().相反,我建议你使用新的例外(消息,e).这将设置异常的原因,因此整个堆栈跟踪还将包含原始异常的堆栈跟踪.这对于调试异常原因非常有用.

  2. 我建议你抛出更明确的异常,例如FileNotFoundException,而不是抛出异常.在有意义的情况下使用Java API定义的异常.

  3. 我建议您明确说明要捕获的异常,而不是捕获异常.

  4. (2的可选替换)一些开发人员更喜欢 - 而不是为每种异常定义异常类 - 抛出一个包含指示异常类型的错误键的常规异常,例如new DetailedException(GeneralBusinessErrors.PARSING_ERROR).这样可以更轻松地为业务错误分配语言相关的资源文件.

  5. 将调试数据添加到您的异常可能很有用.例如,如果找不到文件,则可能是您尝试打开的文件等信息.这在维护中非常有用,您可能无法使用调试器重现问题和/或进行调试.

  6. 让系统记录任何未捕获的未经检查的异常可能很有用.一般来说,记录抛出(捕获或未捕获)的任何异常可能很有用.


Ada*_*ski 6

通常,如果我想将特定实现的异常转换为我想通过API公开的异常,我会使用catch-rethrow方法.

例如,假设您的示例中的import方法采用的是URL而不是a File.如果这个URL碰巧引用了File我不希望FileNotFoundException在API 上公开(这是一个实现细节),而是捕获并重新抛出它(例如作为一个ImportException)但仍然链接到根本FileNotFoundException原因(如David所建议的).

此外,我通常不会捕获并重新抛出未经检查的异常(例如NullPointerException),尽管在某些情况下(例如Spring DataAccessException).