是否抛出异常泄漏了抽象?


12

我有一个接口方法,在文档中指出它将抛出特定类型的异常。该方法的实现使用引发异常的东西。捕获内部异常,并抛出接口协定声明的异常。这是一个可以更好解释的代码示例。它是用PHP编写的,但是遵循起来非常简单。

// in the interface

/**
 * @return This method returns a doohickey to use when you need to foo
 * @throws DoohickeyDisasterException
 */
public function getThatDoohickey();

// in the implementation

public function getThatDoohickey() {

    try {
        $SomethingInTheClass->doSomethingThatThrowsAnException();
    } catch (Exception $Exc) {
        throw new DoohickeyDisasterException('Message about doohickey failure');
    }

    // other code may return the doohickey

}

我正在使用此方法来尝试防止抽象泄漏。

我的问题是:将引发的内部异常作为先前的异常传递会泄漏抽象吗?如果不是,仅重用上一个异常的消息是否合适?如果它将泄漏抽象,您是否可以提供有关为什么会这样的指导?

为了澄清,我的问题将涉及更改为以下代码行

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);

异常应该与原因有关不是与谁抛出异常有关。如果您的代码可以有一个file_not_found,则应该抛出一个file_not_found_exception。异常应该不会是图书馆具体。
Mooing Duck

Answers:


11

我的问题是:将引发的内部异常作为先前的异常传递会泄漏抽象吗?如果不是,仅重用上一个异常的消息是否合适?

答案是:“取决于”。

具体来说,它取决于异常及其含义。从本质上讲,重新抛出意味着将其添加到界面中。如果您编写了自己的代码,会这样做吗?还是只是为了避免为异常重新命名?

如果您的异常在被调用的代码中包含追溯到根本原因的跟踪,则可能会泄漏数据到抽象之外—但是,通常,我认为这是可以接受的,因为异常要么由调用方处理(并且没有人关心它的位置)来自或显示给用户(并且您想知道根本原因,以便进行调试。)

但是,通常,仅允许异常通过是一种非常合理的实现选择,而不是抽象问题。只是问“如果我调用的代码不是我会抛出这个吗?”


8

严格来说,如果“内部”异常揭示了有关实现的内部工作的任何内容,那么您就泄漏。因此,从技术上讲,您应该始终捕获所有内容并抛出自己的异常类型。

但。

不少重要的参数可以进行反对这一点。最为显着地:

  • 例外是例外的事情 ; 它们已经在许多方面违反了“正常操作”规则,因此也可能违反封装和接口级抽象。
  • 只要您不将内部程序信息泄漏到UI障碍之外(这就是信息公开),拥有有意义的堆栈跟踪指向错误信息的好处通常会超过OOP的正确性。
  • 用适当的外部异常类型包装每个内部异常会导致大量无用的样板代码,从而破坏了异常的全部目的(即,实现错误处理而不会用错误处理样板污染常规代码流)。

就是说,捕获和包装异常通常有道理的,只要将异常添加到日志中或防止内部信息泄露给最终用户即可。错误处理仍然是一种艺术形式,很难将其归结为一些通用规则。一些异常应该冒泡,而另一些则不应该,决定还取决于上下文。如果要编写UI,则不希望任何内容未经过滤就传递给用户。但是,如果您要编码实现某种网络协议的库,则冒泡某些异常可能是正确的事情(毕竟,如果网络中断,您几乎无能为力地增强信息或恢复并继续操作;翻译NetworkDownExceptionFoobarNetworkDownException无意义的文字)。


6

首先,您在这里所做的不是抛出异常。重新抛出实际上是抛出相同的异常,如下所示:

try {
    $SomethingInTheClass->doSomethingThatThrowsAnException();
} catch (Exception $Exc) {
    throw $Exc;
}

显然,这是没有意义的。重新抛出用于需要释放一些资源或记录异常或您可能想要做的其他事情而又不阻止异常使堆栈沸腾的情况。

您已经在做的是用DoohickeyDisasterException 替换所有异常,无论是什么原因。您可能会或可能不会这样做。不过,我会建议任何你这样做的时候,你也应该做你的建议,并通过原始异常作为内部异常,以防万一它是在堆栈跟踪有用。

但这与泄漏抽象无关,这是另一个问题。

为了回答重新引发的异常(或者实际上是未捕获的异常)是否可能是泄漏抽象的问题,我想说这取决于您的调用代码。如果该代码需要了解抽象框架的知识,则它是一个泄漏的抽象,因为如果您用另一个框架替换该框架,则必须在抽象之外更改代码。

例如,如果DoohickeyDisasterException是框架中的类,并且您的调用代码如下所示,则您有一个泄漏的抽象:

try {
    $wrapper->getThatDoohickey();
} catch (DoohickeyDisasterException $Exc) {
    echo "The Doohickey is all wrong!";
    echo $Exc->getMessage();
    gotoAHappyPlaceAndSingHappySongs();
}

如果用另一个使用不同的异常名称的第三方框架替换第三方框架,则必须找到所有这些引用并进行更改。最好创建自己的Exception类并将框架异常包装在其中。

另一方面,如果DoohickeyDisasterException是您自己制作的,那么您不会泄漏抽象,那么您应该更加关注我的第一点。


2

我尝试遵循的规则是:如果引起它,则将其包装。同样,如果由于运行时异常而要创建错误,则应该是DoHickeyException或MyApplicationException。

这意味着如果错误的原因是代码之外的原因,并且您不希望它失败,则请优雅地失败并重新抛出它。同样,如果您的代码有问题,请将其包装在(可能)自定义异常中。

例如,如果期望文件在磁盘上,或者期望服务可用,则请妥善处理该文件并重新引发错误,以便代码使用者可以找出为什么该资源不可用。如果您为某种类型创建了太多数据,那么包装和抛出是您的错误。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.