是否应该直接重新抛出捕获的异常,或者它们是否应该包含新的异常?
那就是我应该这样做:
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);
}
如果您的答案是直接抛出,请建议使用异常链接,我无法理解我们使用异常链接的真实场景。
答案 0 :(得分:255)
除非您打算做一些有意义的事情,否则您不应该捕获异常。
“有意义的东西”可能就是其中之一:
最明显的有意义的行动是处理异常,例如:通过显示错误消息并中止操作:
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();
}
因此捕获了“无法创建目标目录”异常错误,您将有权被混淆。在提供上下文的其他异常层中包含此“核心”异常将使错误更容易处理(“创建配置文件复制失败” - >“文件复制操作失败” - >“无法创建目标目录” )。
答案 1 :(得分:29)
嗯,这都是关于维护抽象的。所以我建议使用异常链接直接抛出。至于原因,让我解释一下leaky abstractions
的概念假设您正在构建模型。该模型应该从应用程序的其余部分抽象出所有数据持久性和验证。那么现在当你遇到数据库错误时会发生什么?如果你重新抛出DatabaseQueryException
,你就会泄露抽象。要理解为什么,请考虑一下抽象。你不关心模型存储数据的 ,就像它一样。同样,你并不关心模型底层系统出了什么问题,只是你知道出了什么问题,大概出了什么问题。
因此,通过重新抛出DatabaseQueryException,您正在泄漏抽象并要求调用代码理解模型下正在发生的事情的语义。相反,创建一个通用的ModelStorageException
,并将捕获的DatabaseQueryException
包装在其中。这样,您的调用代码仍然可以尝试在语义上处理错误,但是模型的基础技术并不重要,因为您只是暴露了该抽象层的错误。更好的是,因为你包装了异常,如果它一直冒出来并且需要记录,你可以追踪抛出的根异常(走链),这样你仍然可以获得所需的所有调试信息!
除非您需要进行一些后期处理,否则不要简单地捕获并重新抛出相同的异常。但像} catch (Exception $e) { throw $e; }
这样的街区毫无意义。但是你可以重新包装异常以获得一些重要的抽象收益。
答案 2 :(得分:8)
恕我直言,抓住一个例外来重新抛出它是无用的。在这种情况下,只是不要捕获它,并让之前调用的方法处理它(也称为调用堆栈中'upper'的方法)。
如果你重新抛出它,将捕获的异常链接到你将抛出的新异常绝对是一个好习惯,因为它将保留捕获的异常包含的信息。但是,重新抛出它只有在为捕获的异常添加一些信息或处理时才有用,可能是某些上下文,值,记录,释放资源等等。
添加一些信息的方法是扩展Exception
类,以获得NullParameterException
,DatabaseException
等异常。此外,这允许开发者只捕获一些例外他可以处理。例如,只能捕获DatabaseException
并尝试解决导致Exception
的原因,例如重新连接到数据库。
答案 3 :(得分:1)
你通常会这样想。
一个类可能抛出许多不匹配的异常类型。因此,您为该类或类的类创建一个异常类并抛出它。
因此,使用该类的代码只能捕获一种类型的异常。
答案 4 :(得分:1)
您必须查看Exception Best Practices in PHP 5.3
PHP中的异常处理不是任何延伸的新功能。在以下链接中,您将看到PHP 5.3中基于异常的两个新功能。第一个是嵌套异常,第二个是SPL扩展提供的一组新的异常类型(现在是PHP运行时的核心扩展)。这两个新功能都已进入最佳最佳实践,并值得详细研究。
http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3
答案 5 :(得分:1)
我们有可能产生异常的 try/catch - 因为
在您的应用中做出合乎逻辑的决策 - 如果您根据异常类型做出合乎逻辑的决策,您可能需要更改异常的类型。但是抛出的异常类型大部分时间都足够具体。所以这更像是一个不对异常做任何事情的理由,只是重新抛出它。
向用户展示不同的东西 - 在这种情况下,只需按原样重新抛出并决定在最高级别做什么 - 在控制器中。您需要更改消息 - 记录真实的技术消息并向用户显示友好的消息。
DB 事务 - 它可以属于上述 2 种类型中的任何一种 - 如果您做出一些合乎逻辑的决定或者您只需要告诉用户一些事情。
所以,除非你有很好的理由,否则应该只在一个地方处理异常(否则会变得混乱)——而且那个地方必须是最顶层——在控制器中。
所有其他地方都应该被视为中介,你应该只是冒泡例外 - 除非你有很好的理由不这样做。