- 本文地址: https://www.laruence.com/2012/02/02/2515.html
- 转载请注明出处
先说个题外话: 在公司做了俩件事, 是我觉得很有意义的, 第一就是成立了一个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.
一次去腾讯面试,在那的朋友说线上环境是不用try{}catch{}的。这样不安全。我也就纳闷了。那try{}catch{}到底该不该用啊?
一次去腾讯面试,在那的朋友有线上环境是不用try{}catch{}的。这样不安全。我也就纳闷了。那try{}catch{}到底该不该用啊?
[…] 本文地址: http://www.laruence.com/2012/02/02/2515.html […]
异常非常实用,我自己也做过框架,也是最顶层加入了try,这样在设计程序功能的时候,只需要按一切正常的逻辑去写代码,任何时候出错,会跳到异常处理的代码块去,很好的解决了代码流程控制。 我曾经写了一个php的confirm函数,在服务端抛出让客户端确认的函数, 还写过 php代码级别的signal,当数据异常的时候,只需要触发一个信号,就可以到数据异常处理的分支。 其实这都是利用了Exception实现的,以前写的函数要写很多返回值,现在只写正常的返回值。
[…] 我们什么时候应该使用异常? 错误码 […]
[…] 3 个回答 回答: 基本不用try …..catch ,出错的话php会告诉你哪里错了。顶多if else判断下。 回答: 这个还真没太用过,平时写的东西一般用不上参考鸟哥的这篇文章,lz看完应该会有所收获的http://www.laruence.com/2012/02/02/2515.html 回答: 我一般就是包含文件前先检测,如果不存在就抛出异常,美化一下报错的界面哈哈。或者你想统一一下出错后的处理方式会用的着。当然,在一些复杂点的系统里,这还是很有用的,比如可以扩展异常来查出trace 函数的调用步骤等等。不过一般来说这的确是绣花枕头。 本条目发布于 2013 年 4 月 25 日。属于 php 分类,被贴了 php 标签。作者是 admin。 […]
原来是这样.
[…] 我们什么时候应该使用异常? […]
[…] 我们什么时候应该使用异常? […]
很高兴拜读到鸟哥的精彩文章,
呵,关于异常的处理,很多时候因为考虑到了不同的需求,所以采用了不同的处理方式,有两种,一种就是Fatal Error类的错误,还有一种就是普通的用户性异常处理,我们现在是这样处理的
如在网站上出现了一些Fatal Error的错误时,一般是通过特殊的方式捕获到(如通过register_shutdown_function来注册一个CLASS来捕获),接下来就是需要考虑到两方面的问题,第一是,用户在使用网站时遇到的致命性错误时,采用什么提示方式使体验更好,第二就是,技术人员遇到此类错误时,一般都需要通知技术人员去修改好,所以还得把该错误记录下来,使技术人员能动态跟踪到
第二种错误就是,用户性的操作异常了,如用户输入了一些非法字符,或者说用户做了一些非法操作后的信息提示,需要给予用户相关的提示,以便他能更好的接下来操作,在我们系统上肯定有很多类似的提示,而为了达到一个统一的管理,所以便做了一个统一的异常处理,即把所有的异常处理提示保存一个通用的文件中,而在遇到此类异常时,只是纯粹的throw new,然后再做一个统一的处理。
[…] 本文地址: http://www.laruence.com/2012/02/02/2515.html […]
[…] Laruence 原文地址: http://www.laruence.com/2012/02/02/2515.html […]
异常本质上是程序执行流程的跳转,它最大的好处是可以将处理正常情况的代码和处理错误的分开,使代码可读性提高。
异常也是处理错误的一种手法,对于大部分可以处理的错误,个人认为无需使用异常,使用错误码或者状态码就可以了。对于不可以恢复和处理的错误,比如说数据库连接失败,内存分配失败等,或者是底层的第三方库抛出了异常。这些情况下才使用异常。
异常因为要栈回溯,是有一定的开销,但我认为谨慎使用异常的主要原因是,过多的异常处理会带来程序流程的混乱,比如一个大的try块了发生了异常,你比较难定位到底是哪条语句抛出的。
http://tmkook.com/blog/archives/php-exception
呵呵,扩展阅读。。
我看到很多框架包括ZF异常都是去继承Exception
有什么好处?
我比较喜欢全部都使用系统的Exception
大部分的時候我都是用異常來處理,不過我的工作是寫中間的元件,有時候必須吃掉異常來讓調用的一方繼續執行,可是當這種情況發生時,我並不確知錯誤的訊息調用方需不需要,不知道碰到這種情況有沒有甚麼模式可以參考?
hey,鸟哥.
请问你的hi群是只对公司内部同事交流么?
我可以加入么
[…] 本文地址: http://www.laruence.com/2012/02/02/2515.html […]
@Rhythm 好注意, 我试着问问Rasmus. 另外这个配色方案好像叫做ir_black..
能否发起,成立一个中国PHP的邮件组。
请问,这个vim的配色方案叫什么名字?
@pdfdog 这是vim, 不过好像zend ide也有类似的配色方案, 只是我不知道 🙂
支持下laruence,希望能加入邮件组
博主你的zend主题叫什么啊,好顺眼,麻烦给偶百度下啦~
博主你平时用的zend是上面的颜色主题吗??
我一直有个困惑,通常情况下某个函数或方法只返回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去处理所有异常
希望能加入邮件组!
万维网黑客联盟 求友情链接
支持下,两边都做分析了,哈哈。奢望一下哦,希望能加入您的php邮件组。
求hi群号
前几天遇到一个问题,类似下面的操作
stream1.close();
stream2.close();
stream3.close();
…
这几行代码都有可能抛出异常,但是抛出异常后也得保证后面的代码继续执行,所以就不能在外面搞一个try catch,所以现在是每一行都加一个tray catch,但是感觉比较ugly,有啥好的建议不?
求hi群号,惠兄!
@xiaokaizi class的属性声明必须使用常数, 见:http://www.php.net/manual/en/language.oop5.properties.php
这个常数, 同时对值, 和, 名有效. 🙂
class xx{
public $box[2] = ‘fds’;
public function echso() {
// …
}
} 这样定义类为什么报语法错误?
在zend_language_parser.c里,变量名是不允许带[]的,估计在词法分析那一步把$box[2]分割成了一个变量名,fds是值,到语法分析这步就出错了。
如果是这样原因。问题是为什么要在类的成员变量中这样分割,而普通变量缺不呢?
哈哈,现在用hi的人貌似不是很多啊
请问,作为一个production,异常处理的使用是为了在产品发生错误的时候能更好的提示用户,或者通知开发者,是这样理解吗?
能不能加入邮件列表和HI群
顶。
这个问题是我问的。
多谢,看完这文章明白多了。
前排留名啊。学习。HI群有点麻烦,能不能让我们也加入邮件列表啊。