php

我们什么时候应该使用异常?

dafenqi
2023-08-04 / 0 评论 / 8 阅读 / 正在检测是否收录...

我们什么时候应该使用异常?

PHP应该什么时候使用 Exception ? 它的性能如何?

这个问题也算是一个久经争论的经典问题了. 我谈谈我的个人看法.
异常与之对应的错误码(或者状态码), 到底各自有什么优点, 缺点, 我们应该怎么使用呢?

错误码
首先来说, 异常机制是在错误码机制之后才出现的, 那么根据进化论, 异常自然是避免了错误码机制的一些不足. 这些不足包括.

  1. 错误信息不丰富
    函数, 只能有一个返回值(当然, Lua可以返回多个, 但其实也相当于在PHP中返回一个数组), 我们见过最多的函数说明就是: 成功时候返回*, 错误的时候返回FALSE, 然而一个函数出错我原因可能有多种, 出错的种类更有多种. 一个简单的FALSE, 并不能把具体的错误信息告诉调用者.
    于是, 我们也就见过一些, 这样的函数说明: 如果返回值大于0, 则表示成功的状态码, 如果返回值小于0, 则表示出错的状态码.
    然而, 这个要求函数是返回整形(或者数字), 对于一些其他函数, 我们并不能通过0, >0, <0来判别, 并且, 即使通过这样的方式, 我们还需要用返回的错误码和一些预定义宏(或者调用类似strerror())来获取具体的, 可读的错误信息. 于是, 就有一些函数使用全局的错误码, 和错误信息, 来保存具体的错误信息, 这个时候我们就看到这样的函数描述: 成功返回*, 出错的时候返回FALSE, 错误代码保存在全局变量$errno中(至少大多数Linux库函数是这样描述的, 呵呵). Okey, 这样的方式确实可以工作, 但是, 是不是觉得, 很丑陋呢?
  2. 加入错误状态码可能需要改变函数签名
    假设, 你编写了一个函数, 这个函数很简单, 很简单, 你认为他绝对不会出错, 于是你申明为(用C语言为例, PHP没有返回类型提示):

void dummy() {
}
但是后来你慢慢修改了这个函数, 给了它更多的功能, 此时这个函数可能会失败了. 而你现在根本无法为这个函数, 加入错误返回码了.
也许有人说PHP没有返回值类型限制一说, 但是想想PHP的构造函数, 构造函数是没有返回值的, 当发生错误的时候, 如果你不使用异常, 我想你只能选择die, 或者使用2中的方法来错误继续执行了.
另外, 在一个良好的软件系统中, 返回类型其实也是约定俗成的, 当所有的使用的函数的地方, 都没有检查返回值的时候, 你还是无法为这个函数加入错误返回码.

  1. 错误状态码可能会被忽略
    当你的一个函数, 出错了, 返回了错误状态码, 而调用方并没有检测这个返回值, 会发生什么情况呢? -_#. 令一方面, 处处检测返回状态码, 会造成代码非常的,,ugly:

<?php
if (!call1()) {

  die();

}
if (call2() != SUCCESS) {

 die();

}
if (call3() < 0) {

  $msg = error_get_last();
  die($msg["message"]);

}
异常机制
那么现在我们来看看异常机制, 如果我们采用异常机制, 上面的代码可以写作:

<?php
try {
call1();
call2();
call3();
} catch (Exception $e) {
die($e->getMessage());
}
更方便的, 如果你的代码只是中间层, 你的调用方会负责处理错误的话, 你甚至可以简单的写作:

<?php
function myFunc() {
call1();
call2();
call3();
}
而一个异常对象, 可以包含更丰富的错误信息, 比如错误信息, 错误码, 错误的行数, 文件, 甚至出错上下文, 等等, 避免的"1.错误信息不丰富"的不足.
我们也可以为一个返回void类型的函数增加异常, 而不改变他的函数签名, 也就不会有上面说的"2.加入错误状态码可能需要改变函数签名". 对于PHP来说, 如果我们新加入的错误没有被捕捉, 也不用担心, 会明显的出错的. 也就不会发生上面所说的"3. 错误状态码可能会被忽略"的情况.
然而, 也有一些反对使用异常的声音:

  1. 性能
    正如文章开头提问中的: "它的性能如何?", 异常机制确实要比返回状态码的方式昂贵一些, 对于C++来说, 在异常发生的时候, 还要发生堆栈解退(对于PHP来说, 没有这个逻辑, 具体的大家可以参看我之间写的一篇文章: 深入理解PHP原理之异常机制).
    性能和方便, 往往是一个矛盾体, 我只能说, 你需要权衡, 如果你写的是一个小的模块, 并且它的生命期可能很短, 也不需要什么特殊的设计模式, 那我觉得你可以不用异常.
    而如果你在为一个庞大的软件做开发, 我想你更应该看重的, 应该是, 它的可扩展性, 可维护性.
  2. 太多可能的Uncaught Exception
    如果, 你调用了一个可能发生异常的函数, 但是却没有捕获这个异常, okey, Fatal Error了, 所以让我们的代码看起来:

<?php
try {
} catch () {
}
....
try {
} catch () {
}
....
try {
} catch () {
}
然而, 这个是可以经过良好设计避免的, 比如我在设计Yaf的时候, 就提供了全局异常处理, 也就是类似于, 你在最最顶层, 加上了一个try catch, 所有的异常错误逻辑都加到这个里面, 你也可以很方面的把你自己的异常加进去.

结论

0

评论 (0)

取消