捕捉和重新抛出exception的最佳实践是什么?
应该将捕获的exception直接重新抛出,还是应该围绕一个新的exception?
也就是说,我应该这样做:
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); }
如果你的答案是直接抛出请build议使用exception链接 ,我不能理解我们使用exception链接的真实世界的情况。
除非你打算做一些有意义的事情,否则你不应该抓住例外。
“有意义的东西”可能是其中之一:
处理exception
最明显的有意义的操作是处理exception,例如通过显示错误消息并中止操作:
try { $connect = new CONNECT($db, $user, $password, $driver, $host); } catch (Exception $e) { echo "Error while connecting to database!"; die; }
logging或部分清理
有时你不知道如何正确处理特定上下文中的exception; 也许你缺乏关于“大局”的信息,但是你想把失败logging到尽可能接近的地步。 在这种情况下,您可能需要捕获,logging并重新抛出:
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 }
PHP5.5已经引入了finally
关键字,所以对于清理场景,现在有另一种方法来处理这个问题。 如果清理代码无论发生什么事情都需要运行(即错误和成功),现在可以做到这一点,同时透明地允许任何抛出的exception传播:
$connect = new CONNECT($db, $user, $password, $driver, $host); try { $connect->insertSomeRecord(); } finally { $connect->disconnect(); // no matter what }
错误抽象(带有exception链接)
第三种情况是,你想在一个更大的伞下将许多可能的故障在逻辑上分组。 逻辑分组的示例:
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
消费者捕获预期types的exception, 同时允许debugging代码访问所有(实现相关的)细节 。
提供更丰富的上下文(exception链接)
最后,在某些情况下,您可能希望为exception提供更多的上下文。 在这种情况下,将exception封装在另一个exception中是有意义的,这个exception持有关于错误发生时你正在做什么的更多信息。 例如:
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); } } }
这种情况与上面类似(这个例子可能不是最好的例子),但是它提供了更多的上下文:如果抛出exception,它告诉我们文件复制失败。 但为什么失败? 这个信息是在包装的例外中提供的(如果例子更复杂的话,可能有多个级别)。
如果考虑创buildUserProfile
对象导致文件被复制,因为用户configuration文件存储在文件中,并且它支持事务语义,则可以说明这样做的价值:您可以“撤消”更改,因为它们仅在直到您提交configuration文件的副本。
在这种情况下,如果你做到了
try { $profile = UserProfile::getInstance(); }
并因此抓到了“目录无法创build”的exception错误,您有权将其弄糊涂。 将这个“核心”exception包装在提供上下文的其他exception层中会使得处理错误更容易(“创buildconfiguration文件复制失败” – >“文件复制操作失败” – >“无法创build目标目录”)。
那么,所有关于保持抽象。 所以我build议使用exception链直接抛出。 至于为什么,让我解释抽象漏洞的概念
假设你正在build立一个模型。 该模型应该从应用程序的其余部分抽象出所有的数据持久性和validation。 那么现在发生数据库错误时会发生什么? 如果您重新抛出DatabaseQueryException
,那么您正在泄漏抽象。 要理解为什么,请考虑一下抽象。 你不关心模型如何存储数据,只是它的确如此。 同样,你也不关心模型的底层系统出了什么问题,只是知道出了什么问题,大概出了什么问题。
因此,通过重新抛出DatabaseQueryException,您正在泄漏抽象,并要求调用代码了解模型下正在发生的事情的语义。 相反,创build一个通用的ModelStorageException
,并将捕获到的DatabaseQueryException
封装在其中。 这样,你的调用代码仍然可以尝试在语义上处理这个错误,但是这个模型的底层技术并不重要,因为你只是从那个抽象层暴露错误。 更妙的是,由于你封装了exception,如果它一路冒出来,需要被logging下来,你可以跟踪抛出的根exception(遍历链),所以你仍然有所有你需要的debugging信息!
除非需要做一些后处理,否则不要简单地捕捉和重新抛出相同的exception。 但像块} catch (Exception $e) { throw $e; }
} catch (Exception $e) { throw $e; }
毫无意义。 但是你可以重新包装这些例外,以获得一些重要的抽象。
恕我直言,捕获一个例外,只是重新抛出它是没用的 。 在这种情况下,只是不要捕捉它,让先前调用的方法处理它(也就是调用堆栈中'上'的方法) 。
如果你重新抛出它,把捕获到的exception链接到你将抛出的新exception绝对是一个好习惯,因为它将保留被捕获的exception包含的信息。 然而,重新抛出它只是有用的,如果你添加一些信息或处理捕获exception的东西 ,可能是一些上下文,值,日志logging,释放资源,不pipe。
添加一些信息的一种方式是扩展Exception
类,使其具有NullParameterException
, DatabaseException
等exception。此外,这允许开发人员仅捕获一些他可以处理的exception。 例如,只能捕获DatabaseException
并尝试解决导致Exception
,如重新连接到数据库。
你通常这样想。
一个类可能抛出许多types的不匹配的exception。 所以你为这个类或类的类创build一个exception类并抛出它。
所以使用该类的代码只能捕获一种types的exception。
您必须查看一下PHP 5.3中的Exception Best Practices
PHP中的exception处理并不是一个新function。 在下面的链接中,您将看到基于exception的PHP 5.3中的两个新function。 第一个是嵌套exception,第二个是由SPL扩展(现在是PHP运行时的核心扩展)提供的一组新的exceptiontypes。 这两个新function都已经find了最佳最佳实践的书籍,值得仔细检查。
http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3