捕获和重新抛出异常的最佳做法是什么? [英] What are the best practices for catching and re-throwing exceptions?

查看:98
本文介绍了捕获和重新抛出异常的最佳做法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

应该捕获的异常直接被重新抛出,还是应该被包装在一个新的例外?



那就是我应该这么做:

  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);
}

如果您的答案是直接投掷,请建议使用异常链接,我无法理解我们使用异常链接的真实世界场景。

解决方案

除非你打算做某些有意义的事情,否则你不应该捕捉异常。



有意义的东西可能是其中之一:



处理异常



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

  try {
$ connect = new CONNECT($ db,$ user,$ password,$ driver,$ host);
}
catch(异常$ e){
echo连接数据库时出错!;

}



记录或部分清理



有时您不知道如何正确处理特定上下文中的异常;也许你缺少关于大图的信息,但是您确实希望将失败记录为尽可能接近发生点。在这种情况下,您可能想要捕获,记录和重新抛出:

  try {
$ connect =新的CONNECT($ db,$ user,$ password,$ driver,$ host);
}
catch(异常$ e){
logException($ e); //做某事
throw $ e;
}

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

  $ connect = new CONNECT($ db,$ user,$ password,$ driver ,$ host); 
try {
$ connect-> insertSomeRecord();
}
catch(异常$ e){
$ connect-> disconnect(); //我们不想让连接断开
throw $ e; //但是我们也不知道如何回应失败
}

PHP 5.5已经引入了 finally 关键字,所以在清理方案中,现在有另一种方法可以解决这个问题。如果清除代码需要运行,无论发生了什么(即错误和成功),现在可以这样做,同时透明地允许任何抛出的异常传播:

  $ connect = new CONNECT($ db,$ user,$ password,$ driver,$ host); 
try {
$ connect-> insertSomeRecord();
}
finally {
$ connect-> disconnect(); //无论什么
}



错误抽象(异常链接)



第三种情况是您希望在更大的伞下逻辑分组许多可能的故障。一个逻辑分组的例子:

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


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

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



提供更丰富的上下文(有例外链接)



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

  class FileOperation {
public static function copyFiles(){
try {
$ copier = new FileCopier(); //构造函数可以抛出

//如果文件不存在,这可能会抛出
$ copier-> ensureSourceFilesExist();

//如果无法创建目录,可能会抛出
$ copier-> createTargetDirectory();

//如果复制文件失败,可能会抛出
$ copier-> performCopy();
}
catch(Exception $ e){
throw new Exception(无法执行复制操作,0,$ e);
}
}
}

这种情况类似于上面(这个例子可能不是最好的例子),但它说明了提供更多上下文的观点:如果抛出异常,它会告诉我们文件复制失败。但是为什么失败了?这个信息是在包装的例外中提供的(如果这个例子比较复杂,可能会有多个级别)。



你想到一个场景,例如创建一个 UserProfile 对象会导致文件复制,因为用户配置文件存储在文件中,并且它支持事务语义:您可以撤消更改,因为它们仅在副本上执行的个人资料,直到您提交。



在这种情况下,如果您

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

,结果发现目标目录无法创建异常错误你会有一个困惑的权利。在提供上下文的其他异常的层中将这个核心异常包装起来会使错误更容易处理(创建配置文件复制失败 - >文件复制操作失败 - >无法创建目标目录)。


Should caught exceptions be re-thrown directly, or should they be wrapped around a new exception?

That is, should I do this:

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

or this:

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

If your answer is to throw directly please suggest the use of exception chaining, I am not able to understand a real world scenario where we use exception chaining.

解决方案

You should not be catching the exception unless you intend to do something meaningful.

"Something meaningful" might be one of these:

Handling the exception

The most obvious meaningful action is to handle the exception, e.g. by displaying an error message and aborting the operation:

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

Logging or partial cleanup

Sometimes you do not know how to properly handle an exception inside a specific context; perhaps you lack information about the "big picture", but you do want to log the failure as close to the point where it happened as possible. In this case, you may want to catch, log, and re-throw:

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

A related scenario is where you are in the right place to perform some cleanup for the failed operation, but not to decide how the failure should be handled at the top level. In earlier PHP versions this would be implemented as

$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 has introduced the finally keyword, so for cleanup scenarios there is now another way to approach this. If the cleanup code needs to run no matter what happened (i.e. both on error and on success) it's now possible to do this while transparently allowing any thrown exceptions to propagate:

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

Error abstraction (with exception chaining)

A third case is where you want to logically group many possible failures under a bigger umbrella. An example for logical grouping:

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);
        }
    }
}

In this case, you do not want the users of Component to know that it is implemented using a database connection (maybe you want to keep your options open and use file-based storage in the future). So your specification for Component would say that "in the case of an initialization failure, ComponentInitException will be thrown". This allows consumers of Component to catch exceptions of the expected type while also allowing debugging code to access all the (implementation-dependent) details.

Providing richer context (with exception chaining)

Finally, there are cases where you may want to provide more context for the exception. In this case it makes sense to wrap the exception in another one which holds more information about what you were trying to do when the error occurred. For example:

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);
        }
    }
}

This case is similar to the above (and the example probably not the best one could come up with), but it illustrates the point of providing more context: if an exception is thrown, it tells us that the file copy failed. But why did it fail? This information is provided in the wrapped exceptions (of which there could be more than one level if the example were much more complicated).

The value of doing this is illustrated if you think about a scenario where e.g. creating a UserProfile object causes files to be copied because the user profile is stored in files and it supports transaction semantics: you can "undo" changes because they are only performed on a copy of the profile until you commit.

In this case, if you did

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

and as a result caught a "Target directory could not be created" exception error, you would have a right to be confused. Wrapping this "core" exception in layers of other exceptions that provide context will make the error much easier to deal with ("Creating profile copy failed" -> "File copy operation failed" -> "Target directory could not be created").

这篇关于捕获和重新抛出异常的最佳做法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆