欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

PHP中的异常

程序员文章站 2022-05-02 14:12:49
...

PHP5引入了 异常 ,这是一个看着简单,实际复杂的功能,本文结合PHP的具体情况讨论一二。 为什么要有 异常 ? 在PHP4的时代,那时候还没有 异常 ,编码时如果要处理一些特殊情况,一般是通过返回错误码(比如类似1,2,3之类的整数)的方式来处理,类似于she

  PHP5引入了异常,这是一个看着简单,实际复杂的功能,本文结合PHP的具体情况讨论一二。

  为什么要有异常

  在PHP4的时代,那时候还没有异常,编码时如果要处理一些特殊情况,一般是通过返回错误码(比如类似1,2,3之类的整数)的方式来处理,类似于shell中的处理方式,随着项目尺度的增大,这种方法带来的问题会越来越明显,可以通过下面演示代码来论证这一点:

  先看看错误码的调用方式:

01$foo= foo();
02if ($foo== FOO_ERROR_CODE) {
03// foo error
04 }
05$bar= bar();
06if ($bar== BAR_ERROR_CODE) {
07// bar error
08 }

  再看看异常的调用方式:

01try {
02$foo= foo();
03$bar= bar();
04 } catch (FooException $e) {
05// foo exception
06 } catch (BarException $e) {
07// bar exception
08 }

  我们可以看到,错误码缺乏描述性,而且代码容易倾向if/else之类的拉面风格。另外,如上面代码所示,蓝色代表代码的正常流程,红色代表错误处理,可以清楚看到,采用错误码的调用方式,代码的正常流程和错误处理是混杂在一起的;采用异常的调用方式,代码的正常流程和错误处理是分离的,所以说采用异常的调用方式后,代码的可读性会大大优于采用错误码的调用方式。

  什么时候使用异常

  程序出现非预期结果的时候就应该使用异常,这听起来有些模糊,举例子来说一下:

function find_person_by_id($id)

  假设我们想通过主键查找某人,此时如果没有找到(比如说数据库一致性出现问题)就应该抛出异常,因为从逻辑上讲,既然你通过主键检索,那么这个人应该是必然存在的,如果找不到,就属于非预期结果。

function search_person_by_name($name)

  假设我们想通过名字搜索某人,此时如果没有找到就不应该抛出异常,而应该返回空,或者类似NullPerson之类的对象,此时找不到是可以预期到的结果,既然是搜索,就可能找到,也可能找不到,所以不应该抛出异常

  仔细体会方法名字中的find何search字样,你应该能体会到我说的意思,当然,这样的原则多少有些完美化倾向,实际使用的时候大家自己推敲。

  异常的坏味道?

  PHP作为脚本语言,在使用自定义异常前必须先导入这个异常的定义文件,这多少让人有点纠结,设想一段代码可能会抛出十种(或者更多)类型的异常,那么甭管最终运行结果会怎么样,每次请求前你都得预先包含这些异常的定义文件,这个问题似乎没有太好的解决方法,不过你可以尽可能使用PHP内置的SPL异常类型,因为他们是不需要导入的,缺省就存在。

  另外,在一个分层结构的程序中,异常同样也应该是分层的,比如说程序分为表现层,应用层,持久层,那么异常也应该对应分为表现层异常,应用层异常,持久层异常。这样区分的意义在于不会在当前层抛出一个它很难理解的异常,设想我们在表现层抛出了一个数据库无法连接(Too many connections之类的)的异常,这是多么尴尬的一件事情。以这种情况为例,最佳情况是持久层的异常应该全部在它的上一层,也就是应用层捕捉处理,即便在应用层不能立刻处理,也应该转换成一个表现层可以理解的异常在抛出,比如说可以抛出异常说:系统正忙,请稍后再试。顺着这个方向继续思考,那么每个层次的异常都应该有一个基类,比如说持久层应该有一个统一PersistenceException基类,所有的持久层异常都从这个基类继承,这样做的意义在于,当我们在应用层捕捉异常的时候,可以多一个catch(PersistenceException $e)的保险,避免有持久层异常没有得到处理而泄漏到表现层。

  异常的坏味道还远不止上面提到的这些,有时候,try/catch本身就是坏味道!设想我们有一个Person模型,里面有一个前面说过的find_person_by_id方法,可能会抛出异常,此外,还有很多其他方法也都会抛出异常,那么当在若干Action中调用这个Person模型的时候,就会不断的try,不断的catch,周而复始,try/catch代码到处都是!发现问题了么?没错,问题就是重复!这个问题不好解决,当然,如果你的系统架构比较好的话,还是有办法的,比如可以通过使用装饰器模式来规避这个问题,关于装饰器模式的理论基础可以参考我以前写的Web框架审美观,通过使用装饰器模式,我们可以把每个模型的异常捕捉过程单独拿出来,作为一个装饰器存在,当有Action需要使用这个模型的时候,就可以有针对性的使用这个装饰器。

  从一个理想状态来看,假设你使用MVC框架的话,try/catch的过程应该属于框架的工作职责(具体行为可以通过配置来改变),你的Action代码里不应该存在try/catch代码,这样既可以消除重复代码,而且还最大限度的保证了Action代码本身的可读性。