Java异常处理的最佳实践

11

我最近写了下面这段代码,其中使用了大量的异常处理。我认为这使得代码看起来非常难以阅读。我可以通过捕获通用的异常来缩短代码,例如

catch (Exception e){
    e.printStackTrace();
}

但我也听说,捕获通用异常不是一种好的编程实践。

public class DataAnalyzerTester {
    /**
     * @param args args[0] stores the filename
     * @exception NoSuchElementException if user attempts to access empty list element
     * @exception ArithmeticException if user attempts to divide by 0
     * @exception ArrayIndexOutOfBoundsException if user supplied less than 3 arguments
     * @exception IOException problems with creating and writing files
     * @exception RuntimeException if user attempts to pass empty list to constructor
     */
    public static void main(String[] args) {

    try{
        //some code

    } catch (NoSuchElementException e) {
        System.out.println("Accessing element that does not exist: " + e.toString());
    } catch (ArithmeticException e) {
        System.out.println("Division by zero: " + e.toString());
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("Please supply a command line arguement that specifies your file path: " + e.toString());
    } catch (IOException e) {
        System.out.println("Other IO errors: " + e.toString());
    } catch (RuntimeException e) {
        System.out.println(e.toString());
    } 
    }
}

我想知道是否有更好、更简洁的方法来捕获多个异常。


抱歉,我在复制和粘贴时不小心删除了它。 - Guan Summy Huang
你的问题似乎在寻求一种通用解决方案,但我认为这样的解决方案并不存在。异常处理的具体方法将取决于程序本身的需求和结构。 - Hovercraft Full Of Eels
我会建议你捕获一个通用异常,然后使用if语句来确定每个异常是什么,并执行相应的操作。这样你只需要一个catch语句。(如果这是你想要的) - Sweeper
1
一个关于这个主题的好文章:https://stackify.com/best-practices-exceptions-java/ - Vadzim
2个回答

21

首先,除非你有非常好的理由,否则不要捕获RuntimeExceptionExceptionThrowable。这些将捕获大部分被抛出的异常,而Throwable将会捕获所有东西,包括那些你不应该捕获的,比如OutOfMemoryError

其次,避免捕获运行时异常,除非它直接影响到程序的关键操作。(但是,如果有人看到你捕获了NullPointerException,他们完全有权责备你。) 你应该关心的唯一异常是那些你必须处理的异常。在你的异常列表中,你唯一应该关心的是IOException。其他异常都是测试不足或编码不规范的结果;这些异常在应用程序的正常运行时不应该发生。

第三,在Java 7中,你可以使用multi-catch语句处理多个互斥的异常。链接中的示例解释得很清楚,但如果你遇到同时抛出IOExceptionSQLException的代码,可以像这样处理:

try {
    // Dodgy database code here
catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

这样做可以使情况变得更加清晰,因为您无需捕获笨重而庞大的异常链。


谢谢你为我澄清事情。我完全同意你的观点。看起来我太过依赖异常了,像是ArrayIndexOutOfBoundsException和NoSuchElementException,我不应该依赖异常来完成工作。 - Guan Summy Huang
1
阻止人们在高级别使用catch似乎不是很好的做法。我们应该鼓励人们理解异常而不是忽略它们。而你所说的“非常好的理由”相当夸张,通常你只记录异常,而且在某个层面上,如果你至少捕获了一个意外的异常,你很可能总是想要记录,我们不应该告诉人们这样做是不好的实践。捕获Throwable是不好的,我同意你的看法,但是我希望经常看到捕获RuntimeException。 - fd8s0
7
“记录并抛出”是一个有争议的做法,因为它会导致在多个级别上重复记录日志。 - Vadzim

16
首先,“最佳实践”建议的问题在于它倾向于过度简化问题和答案。然后,有人(比如你自己)发现它是矛盾的1
在我看来,最佳实践是对“最佳实践”建议以及经常使用该短语的人持有健康程度的怀疑态度。尝试自己理解实际问题,并得出自己的结论......而不仅仅是依靠别人告诉您什么是“最佳实践”。
1 - 或者更糟糕的是......他们没有注意到矛盾、边缘情况等,盲目地遵循所谓的“最佳实践”。偶尔,他们会发现自己身处黑暗之中,因为“最佳实践”建议是不合适的。
所以问题在哪里?就在这个声明中:“但我也听说捕获通用异常不是一个好的编码实践。”
事实上,通常不是一个好的编码实践来捕获像Exception这样的通用异常。但在某些情况下,这样做是正确的。而您的示例就是一个适当的情况。
为什么呢?
嗯,让我们来看一个捕获Exception是一个坏主意的情况:
    public void doSomething(...) {
        try {
            doSomethingElse(...);
        } catch (Exception ex) {
            // log it ... and continue
        }
    }

为什么这是个坏主意?因为那个catch会捕获和处理未经预料的异常;也就是说,开发者没有想到或者甚至没有考虑过的异常。这没关系...但是代码记录了异常,并且继续运行,好像什么都没发生一样。

这才是真正的问题...试图从一个未经预料的异常中恢复

所谓的“最佳实践”建议“永远不要捕获普通异常”解决了这个问题,但以一种粗略的方式,无法处理边缘情况之一是,如果你立即关闭应用程序,捕获(和记录)通用异常是可以的,就像你正在做的那样。

    public void main(...) {
        try {
            // ...
        } catch (Exception ex) {
            // log exception
            System.err.println("Fatal error; see log file");
            System.exit(1);
        }
    }

现在对比一下你的问题中那个(据说)好的实践版本。它们有什么区别呢?

  1. 你的版本可以生成更加用户友好和不那么令人惊恐的诊断信息...但只限于某个程度。
  2. 你的版本会更加冗长。
  3. 你的版本无法帮助那些需要诊断问题的人,因为堆栈轨迹没有被记录。

针对1和2的反驳如下:

  1. 你可以花费无限的时间来完善应用程序的“用户友好”诊断信息,但仍然无法帮助那些不能或不愿意理解的用户...
  2. 这也取决于典型用户是谁。

正如你所看到的,这比“捕获通用异常是不好的做法”要复杂得多。


当进行期望捕获除错误以外的所有异常的异常层次结构时,捕获通用异常只是一种好的实践。例如,从最具体的异常到不太具体的异常,最后是Exception。但在所有情况下,这可能是一种信息丢失的选项。更具体的异常可能携带通用Exception类没有的详细信息。此时,您已经失去了可能有助于某人进行故障分析的信息。 - scott m gardner
这取决于情境,你是否应该这样做。而进行故障分析的人最好只给他们完整的堆栈跟踪,不要用可能会掩盖真正问题的“有用”东西来复杂化代码。 - Stephen C
2
如果你问一个工程师,比如电气工程师,他会告诉你没有所谓的“最佳实践”。只有针对手头任务的最佳解决方案,这取决于一系列预算:价格、成本、功耗、尺寸、所需寿命等等。这些因素构成了一个星座般的复杂关系,可能在你的职业生涯中再也不会出现。 - user207421
2
...或者说在你迄今的职业生涯中从未遇到过的问题。同时阅读此文:http://www.satisfice.com/blog/archives/27... - Stephen C
值得解释。我们都可能会陷入使用“最佳实践”的陷阱,而实际上并不想花时间去理解它们。 - Charlie Dalsass
同意。最佳实践并不涵盖所有用例、边缘情况和其他未知情况。 - Prasanna Mondkar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接