如何记录未经检查的异常?

25

Joshua Bloch在他的《Effective Java》一书中写道:

"使用Javadoc @throws标记记录方法可能抛出的每个未检查异常,但不要使用throws关键字将未检查的异常包含在方法声明中。"

这听起来确实是合理的,但是如何找出我的方法可能抛出哪些未检查的异常呢?

让我们考虑一个以下的类:

public class FooClass {

    private MyClass[] myClass;

    /**
     * Creates new FooClass
     */
    public FooClass() { 
        // code omitted
        // do something with myClass
    }

    /**
     * Performs foo operation.<br />
     * Whatever is calculated.
     * @param index Index of a desired element
     * @throws HorribleException When something horrible happens during computation
     */
    public void foo(int index) {
        try {
            myClass[index].doComputation();
        } catch (MyComputationException e) {
            System.out.println("Something horrible happened during computation");
            throw new HorribleException(e);
        }
    }
}

现在我已经记录下了HorribleException异常,但很明显foo方法也可能会抛出未检查的java.lang.ArrayIndexOutOfBoundsException异常。随着代码变得越来越复杂,想到方法可能抛出的所有未检查异常越来越困难。我的IDE和其他工具都没有什么帮助。既然我不知道任何工具可以做到这一点...

你如何处理这种情况?


4
我认为这段文字的意思是,你需要明确记录代码抛出的所有未经检查的异常——包括你正在记录的方法以及如果它调用了任何其他方法并引发了未经检查的异常。 - Abhinav Sarkar
而是要使用throw new HorribleException("index=" + index, e)。堆栈跟踪中包含的信息越多,就越好。 - Thorbjørn Ravn Andersen
这只是一个虚假的例子。我相信真正的重点是另外一个 - 考虑HorribleException已经记录,但ArrayIndexOutOfBoundsException没有记录。考虑各种异常,而不仅仅是虚拟的foo方法。 - Xorty
5个回答

16

仅记录那些明确由您自己抛出或从另一个方法传递的内容。其余部分应视为错误,需要通过良好的单元测试和编写稳健的代码进行修复。

在这种特定情况下,我会将 ArrayIndexOutOfBoundsException 视为您代码中的错误,并相应地修复代码,使其永远不会抛出该异常。例如,添加一个检查以确保数组索引在范围内,并根据情况进行处理,可以抛出异常(您应该记录此异常),也可以采取替代路径。


1
我不明白为什么要检查索引。毕竟,“ArrayIndexOutOfBoundsException”已经足够清晰易懂了。如果你手动检查索引,那么很可能会抛出“ArrayIndexOutofBoundsException”,因此我认为手动检查索引没有意义。然而,我认为这个问题值得记录。 - Vivien Barousse
7
@Vivien: 我会重新抛出一个新的IllegalArgumentException("Unknown index"),因为对调用者来说,在“幕后”使用了数组并不明显。 - BalusC
3
如果你在处理数组,ArrayIndexOutOfBoundsException是完全合理的异常。但 foo 的调用者并不是在处理数组,而是在处理 foo 方法本身。foo 在内部使用数组是实现细节。因此,如果 foo 要抛出异常,它应该抛出更适合的异常,比如 IllegalArgumentException。但这只有在这种情况下对于 foo 抛出异常是真正恰当的情况下才是这样。异常是为了处理异常情况而设计的。我们不知道关于foo更多信息,无法确定无效参数是否真的是异常情况。 - T.J. Crowder
1
@Vivien:通常的做法是只记录从调用方法显式抛出或传播的异常。在Java SE和EE API中,你也可以看到这种实践几乎无处不在。像这样显式地抛出NPE也并不罕见。它使代码自我记录,并更符合javadoc。但我会在消息中包含更多细节,比如参数名称。 - BalusC
@BalusC:我基本上同意。特别是核心Java API文档参考。但是,尊重所有人,Vivien说的和我发布的Bloch的引语一样。这很奇怪。就像使用Bloch的习语似乎很合理,但几乎无法实现。这就是为什么我也更喜欢TDD和经过强烈测试的代码。如果我没有明确抛出未经检查的异常,我个人不会记录它们。 - Xorty
显示剩余2条评论

2
随着代码越来越复杂,想到方法可能抛出的所有未经检查的异常就越难。
你看到的是一个问题,我则认为这是保持代码简单的一个很好的理由 ;-)
在你的示例中,我建议记录 ArrayIndexOutOfBoundsException。这是因为当有人给出一个错误的参数时,可能会发生这种情况,因此应该写下来:“如果给出一个无效的数组索引,你将得到一个 ArrayIndexOutOfBoundsException。例如,String#charAt() 方法记录了如果索引无效,则可能抛出 IndexOutOfBoundException
一般来说,你不应该记录可能出现的所有异常。你无法预测所有异常,而且很可能会遗漏其中一个。所以,记录那些明显的异常,也就是你想到的异常。尽可能列出最多的异常,但不要花太多时间在这上面。毕竟,如果你遗漏了应该记录的异常,以后还可以改进你的文档。

1
你有检查过charAt()的源码吗?它会检查范围并明确地抛出StringIndexOutOfBoundsException异常。如果你按照相同的方法处理OP的情况,你应该抛出一个FooIndexOutOfBoundsException异常。 - BalusC
@BalusC:好吧,那可能不是最好的例子。请查看您答案下的评论。 - Vivien Barousse

1

只记录你自己抛出的内容。

在这种情况下,我会进行索引边界检查并抛出自己的异常: throw IllegalArgumentException("索引必须小于" +myClass.length + ",但实际为:" + index) 然后在JavaDoc中记录IllegalArgumentException


当然,这就是问题所在。很难想到所有可能的异常情况 - 就像这个例子一样。现在很明显,但当你有一个类似于500行方法的项目,并且你没有使用"throw" - 意味着你没有使用throw子句... - Xorty
@Xorty:如果你有很多500行的方法,并且如果你发现“难以想到所有可能的异常”,那么这些都是你的方法可能过于复杂的明显迹象,你可能需要进行一些重构。请参见http://c2.com/cgi/wiki?TallerThanMe和http://c2.com/cgi/wiki?LongMethodSmell。 - Grodriguez
@Grodriguez 我希望这个世界更美好,有更多喜欢单元测试和文档编写的程序员...但通常你需要在已经开始的项目中实现某些模块,这就是痛苦所在。 - Xorty

0

你发的那句话,只是要记住如果想成为理想的程序员。编程不是考虑“会出什么问题”,而是思考如何以最佳方式编写代码并实现它。如果这是个个人项目,只需写出方法的功能即可。

然而,有三种可能的解决方案:

  • 不记录该方法。
  • 思考一分钟你的代码做了什么,并找出最常见的未经检查的异常。将它们添加到Java-doc中。如果遇到新的异常,请找出问题所在并将其添加为可能的异常。
  • 不关心可能的异常,只记录方法体中抛出的异常(例如:if (obj == null) { throw new NullPointerException(); })。

0

我们有一个已编写的Checkstyle扩展程序在我们的测试服务器上运行。在您的样本中,它将测试HorribleException是否已记录。

通过代码审查可以检测到ArrayIndexOutOfBoundsException。在您的示例代码中,我们的目标要求抛出InvalidArgumentException而不是ArrayIndexOutOfBoundsException。其文档可以在测试服务器上找到。

在FindBugs中,ArrayIndexOutOfBoundsException可能会被视为警告信息。但我不确定。


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