抛出异常越早越好的做法是否可取?

4
今天我遇到了以下情况:(“伪代码”)
class MyClass {

    public void workOnArray(Object[] data) {
        for (Object item : data) {
            workOnItem(item);
        }
    }

    public void workOnItem(Object item) {
        if (item == null) throw new NullPointerException();
    }
}

现在,如果调用者使用包含null项的数组调用workOnArray,由于workOnItem,调用者将会收到NullPointerException。但是我可以在workOnArray中插入一个额外的检查,换句话说,问题可以更早地被发现。(请注意,这只是一个简单的例子,在实际应用程序中,这可能不太明显)。
对于正面方面,我认为额外的检查可以提供更高级别的诊断信息。而且尽早失败总是一件好事。
对于反面方面,我会问自己:“如果我这样做,难道我不应该验证传递到编程语言核心API的每个参数并抛出完全相同的异常吗?”
有没有什么经验法则,可以让我们在何时尽早抛出异常,何时让被调用者抛出异常?

1
提前失败并不总是一件好事。有时候,积累错误并在处理大量数据的批处理后向用户呈现报告会更好,特别是在这种情况下。 - Fred Foo
1
死程序不会说谎。换句话说,一个早期出错的程序不会意外地覆盖重要信息。此外,请考虑断言和前置条件。http://download.oracle.com/javase/1.4.2/docs/guide/lang/assert.html - Dave Jarvis
1
@larsman:每个概括都是错误的,即使是这个。 :) - Daniel Rikowski
2个回答

8

在处理类似循环处理项的情况下,有一件事情肯定会让我想要先预验证整个项数组;如果在抛出异常之前处理某些项会对其产生不良影响,从而导致任何剩余项未被处理。

除非有某种事务机制包装代码,否则通常在开始处理它们之前,我都希望确保集合中的项是有效的。


2
如果您正在更改数据并且没有可用的事务,请先检查(如果只是计算而没有副作用,则不必太担心)。在循环情况下,我也总是在考虑(而从未执行)不仅抛出一个说“此元素有问题”的异常,而是收集所有错误并在异常中说“元素一、五、七有问题”。 - Thilo
@Thilo 我也赞同你的观点,这虽然增加了一些复杂性,但它可能是一个非常棒的选项,比如继续处理其余元素,然后返回异常的集合(可能是自定义异常中的集合成员)或者一个异常,其中包含所有错误信息,并且只允许修复“坏”项目并再次尝试。 - Andrew Barber
1
+1我同意预验证是一个优点,即使它涉及到复杂结构的检查。 @DR:尝试用模拟测试的方式来思考这个问题:如果我想要模拟workOnItem()并且仅检查workOnArray()的功能,那么行为将取决于什么位置包含null元素。很复杂。如果验证与业务逻辑分离,则测试更简单。 - dma_k

2
在这个例子中,workOnItem方法是关心item是否为空的方法。workOnArray方法不关心items是否为空,因此我认为不应该验证任何items是否为空。workOnItem方法关心,所以应该执行检查。
我还会考虑从workOnItem抛出一个更合适的异常类型。NullPointerException(或在C#中是NullReferenceException)通常表示方法操作中的一些意外缺陷。在C#中,我更倾向于抛出一个ArgumentNullException,其中包括空参数的名称。这更清楚地表明workOnItem无法继续,因为它无法处理接收到的空参数。

1
workOnArray方法不关心项目是否为空。如果第三个项目为空,前两个项目已经被处理,如果没有事务,可能会留下一些不一致的痕迹。另一方面,无论您事先进行多少错误检查,这种情况都可能发生。 - Thilo
@Thilo - 当然正确。这就是抽象解决方案失效的地方,真正的业务需求开始决定什么是必要的。某些应用程序可能需要处理批处理中的所有项目或根本不需要处理任何项目,而其他应用程序可能会从部分成功中受益。最终答案取决于应用程序的业务规则。 - John Bledsoe

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