msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

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

先说个题外话: 在公司做了俩件事, 是我觉得很有意义的, 第一就是成立了一个PHP邮件组, 第二就是成立了一个Hi群. 目前俩者都有超过500 phpers在里面. 我一直认为, 构建一个交流平台, 让同学们能顺畅, 简单的沟通, 是营造积极的技术学习氛围的基础和前提. 让每个人的问题不会成为别人的问题, 则是最直接的利益. (后记: 不少人都问邮件组地址, 实在不好意思, 这个邮件组是公司内部的邮件组, Hi也是公司内部的. 谢谢)

昨天, 有同事在邮件组提了个问题:

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中的方法来错误继续执行了.

另外, 在一个良好的软件系统中, 返回类型其实也是约定俗成的, 当所有的使用的函数的地方, 都没有检查返回值的时候, 你还是无法为这个函数加入错误返回码.

3. 错误状态码可能会被忽略

当你的一个函数, 出错了, 返回了错误状态码, 而调用方并没有检测这个返回值, 会发生什么情况呢? -_#. 令一方面, 处处检测返回状态码, 会造成代码非常的,,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, 所有的异常错误逻辑都加到这个里面, 你也可以很方面的把你自己的异常加进去.

结论

经常有人批评我是俩面派, 呵呵, 但是在大家了解了上面的利弊以后, 是否也会和我一样认为: 这个事情没有定论呢? 一切从实际出发. :)

说了这么多, 算是抛砖引玉, 欢迎补充, 交流: here or here.


分享到:



Related Posts:

Tags: , , , ,

35 Responses to “我们什么时候应该使用异常?”

  1. bluebird |

    异常非常实用,我自己也做过框架,也是最顶层加入了try,这样在设计程序功能的时候,只需要按一切正常的逻辑去写代码,任何时候出错,会跳到异常处理的代码块去,很好的解决了代码流程控制。 我曾经写了一个php的confirm函数,在服务端抛出让客户端确认的函数, 还写过 php代码级别的signal,当数据异常的时候,只需要触发一个信号,就可以到数据异常处理的分支。 其实这都是利用了Exception实现的,以前写的函数要写很多返回值,现在只写正常的返回值。

  2. Daniel-Journey Weekly Dose –2012/2/5 | Daniel Hu的技术博客 |

    [...] 我们什么时候应该使用异常? 错误码 [...]

  3. 请假php大家日常都用不用错误处理啊 - php - 开发者问答 |

    [...] 3 个回答 回答: 基本不用try …..catch ,出错的话php会告诉你哪里错了。顶多if else判断下。 回答: 这个还真没太用过,平时写的东西一般用不上参考鸟哥的这篇文章,lz看完应该会有所收获的http://www.laruence.com/2012/02/02/2515.html 回答: 我一般就是包含文件前先检测,如果不存在就抛出异常,美化一下报错的界面哈哈。或者你想统一一下出错后的处理方式会用的着。当然,在一些复杂点的系统里,这还是很有用的,比如可以扩展异常来查出trace 函数的调用步骤等等。不过一般来说这的确是绣花枕头。 本条目发布于 2013 年 4 月 25 日。属于 php 分类,被贴了 php 标签。作者是 admin。 [...]

  4. ghostry |

    原来是这样.

  5. PHP的新特性finally | 奇言妙事-文学奇谈小小说阅读xlinblog.sinaapp.com |

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

  6. 奇言妙事-文学奇谈小小说阅读xlinblog.sinaapp.com » Blog Archive » 请手动释放你的资源(Please release resources manually) |

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

  7. 黄叶 |

    很高兴拜读到鸟哥的精彩文章,

    呵,关于异常的处理,很多时候因为考虑到了不同的需求,所以采用了不同的处理方式,有两种,一种就是Fatal Error类的错误,还有一种就是普通的用户性异常处理,我们现在是这样处理的

    如在网站上出现了一些Fatal Error的错误时,一般是通过特殊的方式捕获到(如通过register_shutdown_function来注册一个CLASS来捕获),接下来就是需要考虑到两方面的问题,第一是,用户在使用网站时遇到的致命性错误时,采用什么提示方式使体验更好,第二就是,技术人员遇到此类错误时,一般都需要通知技术人员去修改好,所以还得把该错误记录下来,使技术人员能动态跟踪到

    第二种错误就是,用户性的操作异常了,如用户输入了一些非法字符,或者说用户做了一些非法操作后的信息提示,需要给予用户相关的提示,以便他能更好的接下来操作,在我们系统上肯定有很多类似的提示,而为了达到一个统一的管理,所以便做了一个统一的异常处理,即把所有的异常处理提示保存一个通用的文件中,而在遇到此类异常时,只是纯粹的throw new,然后再做一个统一的处理。

  8. 编码者说 » Blog Archive » 我们什么时候应该使用异常? - 为web开发者而建 |

    [...] 本文地址: http://www.laruence.com/2012/02/02/2515.html [...]

  9. PHP教程:我们什么时候应该使用异常? | PHP爱好者 |

    [...] Laruence 原文地址: http://www.laruence.com/2012/02/02/2515.html [...]

  10. ramones_kkk |

    异常本质上是程序执行流程的跳转,它最大的好处是可以将处理正常情况的代码和处理错误的分开,使代码可读性提高。
    异常也是处理错误的一种手法,对于大部分可以处理的错误,个人认为无需使用异常,使用错误码或者状态码就可以了。对于不可以恢复和处理的错误,比如说数据库连接失败,内存分配失败等,或者是底层的第三方库抛出了异常。这些情况下才使用异常。
    异常因为要栈回溯,是有一定的开销,但我认为谨慎使用异常的主要原因是,过多的异常处理会带来程序流程的混乱,比如一个大的try块了发生了异常,你比较难定位到底是哪条语句抛出的。

  11. tmkook |

    http://tmkook.com/blog/archives/php-exception
    呵呵,扩展阅读。。

    我看到很多框架包括ZF异常都是去继承Exception
    有什么好处?

    我比较喜欢全部都使用系统的Exception

  12. tokimeki |

    大部分的時候我都是用異常來處理,不過我的工作是寫中間的元件,有時候必須吃掉異常來讓調用的一方繼續執行,可是當這種情況發生時,我並不確知錯誤的訊息調用方需不需要,不知道碰到這種情況有沒有甚麼模式可以參考?

  13. owen |

    hey,鸟哥.

    请问你的hi群是只对公司内部同事交流么?

    我可以加入么

  14. lidashuang » 我们什么时候应该使用异常? |

    [...] 本文地址: http://www.laruence.com/2012/02/02/2515.html [...]

  15. laruence |

    @Rhythm 好注意, 我试着问问Rasmus. 另外这个配色方案好像叫做ir_black..

  16. Rhythm |

    能否发起,成立一个中国PHP的邮件组。

  17. Rhythm |

    请问,这个vim的配色方案叫什么名字?

  18. 雪候鸟 |

    @pdfdog 这是vim, 不过好像zend ide也有类似的配色方案, 只是我不知道 :)

  19. dulidong |

    支持下laruence,希望能加入邮件组

  20. pdfdog |

    博主你的zend主题叫什么啊,好顺眼,麻烦给偶百度下啦~

  21. Peter |

    博主你平时用的zend是上面的颜色主题吗??

  22. xiatian |

    我一直有个困惑,通常情况下某个函数或方法只返回true和false,在特定条件下需要抛出一个异常(当然也就没有返回值了),当抛出异常后,原先调用这个函数或者方法的流程自然就没有继续执行了,程序通常会由set_exception_handler定义的处理方式去处理异常了,而此前调用的流程就被阻断,而我又不想对它单独定义try/catch.
    演示伪代码:
    function is_ok(xxx){
    if (not xxx){throw new Exception(‘xxx is not ok’)}
    return true OR false;
    }


    if (is_ok())
    {
    :)
    }
    else
    {
    :(
    }

    //抛出异常后上面的就不会执行了,有没有不在这里try/catch的解决办法,我想总是用set_exception_handler去处理所有异常

  23. likemeng |

    希望能加入邮件组!

  24. 万维网黑客联盟 |

    万维网黑客联盟 求友情链接

  25. Iterse's blog |

    支持下,两边都做分析了,哈哈。奢望一下哦,希望能加入您的php邮件组。

  26. phper |

    求hi群号

  27. blankyao |

    前几天遇到一个问题,类似下面的操作
    stream1.close();
    stream2.close();
    stream3.close();

    这几行代码都有可能抛出异常,但是抛出异常后也得保证后面的代码继续执行,所以就不能在外面搞一个try catch,所以现在是每一行都加一个tray catch,但是感觉比较ugly,有啥好的建议不?

  28. oboodo |

    求hi群号,惠兄!

  29. 雪候鸟 |

    @xiaokaizi class的属性声明必须使用常数, 见:http://www.php.net/manual/en/language.oop5.properties.php

    这个常数, 同时对值, 和, 名有效. :)

  30. xiaokaizi |

    class xx{
    public $box[2] = ‘fds’;

    public function echso() {
    // …
    }
    } 这样定义类为什么报语法错误?

    在zend_language_parser.c里,变量名是不允许带[]的,估计在词法分析那一步把$box[2]分割成了一个变量名,fds是值,到语法分析这步就出错了。
    如果是这样原因。问题是为什么要在类的成员变量中这样分割,而普通变量缺不呢?

  31. 岭南六少 |

    哈哈,现在用hi的人貌似不是很多啊

  32. benson |

    请问,作为一个production,异常处理的使用是为了在产品发生错误的时候能更好的提示用户,或者通知开发者,是这样理解吗?

  33. lin |

    能不能加入邮件列表和HI群

  34. darasion |

    顶。
    这个问题是我问的。

    多谢,看完这文章明白多了。

  35. treesky |

    前排留名啊。学习。HI群有点麻烦,能不能让我们也加入邮件列表啊。

Leave a Reply

*