小编典典

捕获和重新抛出异常的最佳实践是什么?

all

捕获的异常应该直接重新抛出,还是应该包裹在新的异常周围?

也就是说,我应该这样做:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw $e;
}

或这个:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw new Exception("Exception Message", 1, $e);
}

如果您的回答是 直接抛出, 请建议使用 异常链 ,我无法理解我们使用异常链的真实场景。


阅读 43

收藏
2022-08-16

共1个答案

小编典典

除非您打算做一些有意义的事情,否则您 不应该捕获异常。

“有意义的事情”可能是其中之一:

处理异常

最明显有意义的操作是处理异常,例如通过显示错误消息并中止操作:

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    echo "Error while connecting to database!";
    die;
}

记录或部分清理

有时您不知道如何正确处理特定上下文中的异常;也许您缺乏有关“大局”的信息,但您确实希望将故障记录到尽可能接近故障发生的位置。在这种情况下,您可能需要捕获、记录并重新抛出:

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    logException($e); // does something
    throw $e;
}

一个相关的场景是您在正确的位置对失败的操作执行一些清理,但没有决定如何在顶层处理失败。在早期的 PHP 版本中,这将被实现为

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
catch (Exception $e) {
    $connect->disconnect(); // we don't want to keep the connection open anymore
    throw $e; // but we also don't know how to respond to the failure
}

PHP 5.5
引入了finally关键字,因此对于清理场景,现在有另一种方法来解决这个问题。如果无论发生什么(即错误和成功)都需要运行清理代码,现在可以在透明地允许任何抛出的异常传播的同时执行此操作:

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
finally {
    $connect->disconnect(); // no matter what
}

错误抽象(带有异常链接)

第三种情况是您希望将许多可能的故障逻辑分组到一个更大的范围内。逻辑分组示例:

class ComponentInitException extends Exception {
    // public constructors etc as in Exception
}

class Component {
    public function __construct() {
        try {
            $connect = new CONNECT($db, $user, $password, $driver, $host);
        }
        catch (Exception $e) {
            throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
        }
    }
}

在这种情况下,您不希望
的用户Component知道它是使用数据库连接实现的(也许您希望保持选项处于打开状态并在将来使用基于文件的存储)。因此,您的规范Component会说“在初始化失败的情况下,ComponentInitException将被抛出”。这允许消费者Component捕获预期类型的​​异常,
同时还允许调试代码访问所有(依赖于实现的)细节

提供更丰富的上下文(带有异常链接)

最后,在某些情况下,您可能希望为异常提供更多上下文。在这种情况下,将异常包装在另一个中是有意义的,该异常包含有关您在错误发生时尝试执行的操作的更多信息。例如:

class FileOperation {
    public static function copyFiles() {
        try {
            $copier = new FileCopier(); // the constructor may throw

            // this may throw if the files do no not exist
            $copier->ensureSourceFilesExist();

            // this may throw if the directory cannot be created
            $copier->createTargetDirectory();

            // this may throw if copying a file fails
            $copier->performCopy();
        }
        catch (Exception $e) {
            throw new Exception("Could not perform copy operation.", 0, $e);
        }
    }
}

这个案例与上面的类似(这个例子可能不是最好的例子),但它说明了提供更多上下文的意义:如果抛出异常,它告诉我们文件复制失败。但 为什么
失败了?此信息在包装的异常中提供(如果示例复杂得多,则可能有不止一个级别)。

如果您考虑一个场景,例如创建一个UserProfile对象会导致文件被复制,因为用户配置文件存储在文件中并且它支持事务语义,那么这样做的价值就可以说明:您可以“撤消”更改,因为它们只在一个配置文件的副本,直到您提交为止。

在这种情况下,如果你这样做了

try {
    $profile = UserProfile::getInstance();
}

并因此捕获“无法创建目标目录”异常错误,您有权感到困惑。将此“核心”异常包装在提供上下文的其他异常层中将使错误更容易处理(“创建配置文件复制失败”->“文件复制操作失败”->“无法创建目标目录”)。

2022-08-16