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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5551668/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 21:47:05  来源:igfitidea点击:

What are the best practices for catching and re-throwing exceptions?

phpexception

提问by Rahul Prasad

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 directlyplease suggest the use of exception chaining, I am not able to understand a real world scenario where we use exception chaining.

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

回答by Jon

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

一个相关的场景是您在正确的位置为失败的操作执行一些清理,但不是决定如何在顶层处理失败。在早期的 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 has introduced the finallykeyword, 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:

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

$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 Componentto 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 Componentwould say that "in the case of an initialization failure, ComponentInitExceptionwill be thrown". This allows consumers of Componentto catch exceptions of the expected type while also allowing debugging code to access all the (implementation-dependent) details.

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

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 whydid 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 UserProfileobject 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.

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

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").

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

回答by ircmaxell

Well, it's all about maintaining the abstraction. So I'd suggest using exception chaining to throw directly. As far as why, let me explain the concept of leaky abstractions

嗯,这都是关于保持抽象。所以我建议使用异常链接直接抛出。至于为什么,让我解释一下泄漏抽象的概念

Let's say you're building a model. The model is supposed to abstract away all of the data persistence and validation from the rest of the application. So now what happens when you get a database error? If you rethrow the DatabaseQueryException, you're leaking the abstraction. To understand why, think about the abstraction for a second. You don't care howthe model stores the data, just that it does. Likewise you don't care exactly what went wrong in the underlying systems of the model, just that you know that something went wrong, and approximately what went wrong.

假设您正在构建模型。该模型应该从应用程序的其余部分中抽象出所有数据持久性和验证。那么现在当您遇到数据库错误时会发生什么?如果您重新抛出DatabaseQueryException,则会泄漏抽象。要了解原因,请考虑一下抽象。您不关心模型如何存储数据,只要它存储即可。同样,您并不关心模型的底层系统究竟出了什么问题,只需要知道出了什么问题,以及大概出了什么问题。

So by rethrowing the DatabaseQueryException, you're leaking the abstraction and requiring the calling code to understand the semantics of what's going on under the model. Instead, create a generic ModelStorageException, and wrap the caught DatabaseQueryExceptioninside of that. That way, your calling code can still try to deal with the error semantically, but it doesn't matter the underlying technology of the Model since you're only exposing errors from that abstraction layer. Even better, since you wrapped the exception, if it bubbles all the way up and needs to be logged, you can trace to the root exception thrown (walk the chain) so you still have all the debugging information that you need!

因此,通过重新抛出 DatabaseQueryException,您将泄漏抽象并要求调用代码理解模型下发生的事情的语义。相反,创建一个 generic ModelStorageException,并将捕获的包裹在DatabaseQueryException里面。这样,您的调用代码仍然可以尝试在语义上处理错误,但模型的底层技术无关紧要,因为您只是从该抽象层公开错误。更好的是,由于您包装了异常,如果它一直冒泡并需要记录,您可以跟踪抛出的根异常(遍历链),因此您仍然拥有所需的所有调试信息!

Don't simply catch and rethrow the same exception unless you need to do some post-processing. But a block like } catch (Exception $e) { throw $e; }is pointless. But you can re-wrap the exceptions for some significant abstraction gain.

不要简单地捕获并重新抛出相同的异常,除非您需要进行一些后处理。但是像块一样} catch (Exception $e) { throw $e; }是没有意义的。但是您可以重新包装异常以获得一些重要的抽象收益。

回答by Clement Herreman

IMHO, catching an Exception to just rethrow it is useless. In this case, just don't catch it, and let earlier called methods handle it (aka methods that are 'upper' in the call stack).

恕我直言,捕捉异常只是重新抛出它是无用的。在这种情况下,不要捕捉它,让之前调用的方法处理它(也就是调用堆栈中“上层”的方法)

If you rethrow it, chaining the caught exception into the new one you'll throw is definitely a good practise, as it will keep the informations that the caught exception contains. However, rethrowing it is only usefull if you add some information or handle somethingto the caught exception, may it be some context, values, logging, freeing resources, whatever.

如果您重新抛出它,将捕获的异常链接到您将抛出的新异常中绝对是一个好习惯,因为它会保留捕获的异常包含的信息。然而,只有当您添加一些信息或处理捕获的异常时,重新抛出它才有用,可能是一些上下文、值、日志记录、释放资源等等。

A way to add some information is to extend the Exceptionclass, to have exceptions like NullParameterException, DatabaseException, etc. More over, this allow the developper to only catch some exceptions that he can handle. For example, one can catch only DatabaseExceptionand try to solve what caused the Exception, like reconnecting to the databse.

添加一些信息的方式是扩展Exception类,有像例外NullParameterExceptionDatabaseException等更多了,这让developper只赶上一些例外,他可以处理。例如,只能捕获DatabaseException并尝试解决导致 的原因Exception,例如重新连接到数据库。

回答by HMagdy

You have to take a look on Exception Best Practices in PHP 5.3

你必须看看PHP 5.3 中的异常最佳实践

Exception handling in PHP is not a new feature by any stretch. In the following link, you'll see two new features in PHP 5.3 based around exceptions. The first is nested exceptions and the second is a new set of exception types offered by the SPL extension (which is now a core extension of the PHP runtime). Both of these new features have found their way into the book of best best practices and deserve to be examined in detail.

PHP 中的异常处理无论如何都不是一个新特性。在以下链接中,您将看到 PHP 5.3 中基于异常的两个新功能。第一个是嵌套异常,第二个是 SPL 扩展(现在是 PHP 运行时的核心扩展)提供的一组新异常类型。这两个新功能都已收录在最佳实践书中,值得详细研究。

http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3

http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3

回答by ólafur Waage

You usually think of it this way.

你通常是这样想的。

A class might throw many types of exceptions that will not match. So you create an exception class for that class or type of class and throw that.

一个类可能会抛出许多类型的不匹配的异常。所以你为那个类或类的类型创建一个异常类并抛出它。

So the code that uses the class only has to catch one type of exception.

所以使用该类的代码只需要捕获一种类型的异常。