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

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

先说个题外话: 在公司做了俩件事, 是我觉得很有意义的, 第一就是成立了一个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没有返回类型提示):

  1. void dummy() {  

但是后来你慢慢修改了这个函数, 给了它更多的功能, 此时这个函数可能会失败了. 而你现在根本无法为这个函数, 加入错误返回码了.

也许有人说PHP没有返回值类型限制一说, 但是想想PHP的构造函数, 构造函数是没有返回值的, 当发生错误的时候, 如果你不使用异常, 我想你只能选择die, 或者使用2中的方法来错误继续执行了.

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

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

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

  1. <?php  
  2.   if (!call1()) {  
  3.       die();  
  4.   }  
  5.    if (call2() != SUCCESS) {  
  6.      die();  
  7.   }  
  8.    if (call3() < 0) {  
  9.       $msg = error_get_last();  
  10.       die($msg["message"]);  
  11.   } 

异常机制

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

  1. <?php  
  2. try {  
  3.    call1();  
  4.    call2();  
  5.    call3();  
  6. } catch (Exception $e) {  
  7.    die($e->getMessage());  

更方便的, 如果你的代码只是中间层, 你的调用方会负责处理错误的话, 你甚至可以简单的写作:

  1. <?php  
  2. function myFunc() {  
  3.    call1();  
  4.    call2();  
  5.    call3();  

而一个异常对象, 可以包含更丰富的错误信息, 比如错误信息, 错误码, 错误的行数, 文件, 甚至出错上下文, 等等, 避免的”1.错误信息不丰富”的不足.

我们也可以为一个返回void类型的函数增加异常, 而不改变他的函数签名, 也就不会有上面说的”2.加入错误状态码可能需要改变函数签名”. 对于PHP来说, 如果我们新加入的错误没有被捕捉, 也不用担心, 会明显的出错的. 也就不会发生上面所说的”3. 错误状态码可能会被忽略”的情况.

然而, 也有一些反对使用异常的声音:

1. 性能

正如文章开头提问中的: “它的性能如何?”, 异常机制确实要比返回状态码的方式昂贵一些, 对于C++来说, 在异常发生的时候, 还要发生堆栈解退。

性能和方便, 往往是一个矛盾体, 我只能说, 你需要权衡, 如果你写的是一个小的模块, 并且它的生命期可能很短, 也不需要什么特殊的设计模式, 那我觉得你可以不用异常.

而如果你在为一个庞大的软件做开发, 我想你更应该看重的, 应该是, 它的可扩展性, 可维护性.

2. 太多可能的Uncaught Exception

如果, 你调用了一个可能发生异常的函数, 但是却没有捕获这个异常, okey, Fatal Error了, 所以让我们的代码看起来:

  1. <?php  
  2. try {  
  3. } catch () {  
  4. }....  
  5.  try {  
  6. } catch () {  
  7. }....  
  8. try {  
  9. } catch () {  

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

结论

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

原文链接:http://www.laruence.com/2012/02/02/2515.html

责任编辑:张伟 来源: 风雪之隅
相关推荐

2023-03-29 15:01:43

微服务开发

2010-11-09 13:58:03

SQL Server锁

2020-06-17 10:35:16

机器学习AI人工智能

2021-01-30 19:59:37

性能项目开源

2015-10-20 15:59:57

注释代码程序

2015-10-26 09:38:52

避免注释代码

2022-09-27 15:06:07

微服务架构开发

2017-06-28 15:06:51

PythonLambda函数

2022-05-19 10:27:34

机器学习人工智能

2023-04-24 14:32:54

2020-01-05 23:28:51

MQ消息进程

2017-04-05 21:43:08

MQ互联网架构

2024-09-02 08:53:44

2020-04-14 12:53:01

Nuxt.jsVue.js前端

2015-07-08 15:55:01

NSStringcopystrong

2017-05-15 09:55:07

2020-05-12 11:25:50

MySQLES数据库

2022-06-27 16:55:30

5G6G

2013-09-29 17:13:59

PowerShell工作流

2013-11-28 16:03:24

点赞
收藏

51CTO技术栈公众号