解惑PHP异常
目录
php的异常机制:
现在的php书籍中没有讲清楚,或者不敢讲的地方,怕言多有所失。以下我们将详细说明PHP异常机制。
怎么样给php异常下定义
首先要说的是,php里的异常和其它语言里的异常概念上是不大一样的。php里的异常实际上是“可以预见的不同于正常状况的非错误的代码”。异常不是错误,只是和你预想的逻辑不符的一种情况,出乎正常逻辑外的异常报错;异常->意外的错误,如边界等严格上的错误;
异常机制让你把更多的精力放在了业务逻辑上,而不必在外部业务逻辑上到处if-else来判断各种返回值,来确定可能出什么问题,再处理问题;
你在写代码的时候,只管按正常业务流程写,捕获后,你可以不处理,选择丢弃,也可以做后续工作(如回滚、如异常日志)来补救数据;
异常就是让机器来代替人对业务流程进行分拣,减少重复无意义劳动,并且使我们专注于正常的业务处理;
然而php的异常机制是不完善的。在php的大部分异常里面还是少不了if-else来抛出异常,绝大部分异常php不能自动抛出,而要人工抛出。
(1)对程序的悲观预测
如果一个程序员对自己的代码带有“悲观情绪”,这里并不是指该程序员代码质量不高。他认为自己的代码无法一一处理各种可预见的不可预见的情况,那该程序员就会进行异常处理。假设一个场景,程序员悲观地认为自己的这段代码在高并发条件下可能产生死锁,那么他就会悲观地抛出异常,然后在死锁时进行捕获,对异常进行细致的处理。 注意:这里的代码指的是外部业务逻辑代码,不是内部模块代码
(2)程序和业务的的需要
如果程序员希望业务代码中不会充斥大堆的打印,调试等处理,通常他们会使用异常机制;或者业务上需要定义一些自己的异常,这个时候就需要自定义一个异常, 来对现实世界中各种各样的业务进行补充。比如上班迟到,这种情况,我就认为是一个异常,要收集起来,到月底集中处理,扣你工资;如果程序员希望有预见性地处理可能发生的会影响正常业务的代码,那么它需要异常。在这里,强调了异常是业务处理中必不可少的环节,不能对异常视而不见。异常机制认为,数据一致很重要,在数据一致性可能被破坏时,就需要异常机制来进行预先补救。
举个例子,比如有个上传文件的业务需求,要把上传的文件保存在一个目录里,并在数据库里插入这个文件的记录,那么这两步就是互相关联密不可分的一个集成的业务,缺一不可。文件保存失败,而插入记录成功就会导致无法下载文件;而文件保存成功数据库写入失败,则会导致没有记录的文件成为死文件,永远得不到下载。
那么我们假设文件保存成功后没有提示,但是保存失败会自动抛出异常,访问数据库也一样,插入成功没有提示,失败则自动抛出异常,我们就可以把这两个有可能抛出异常的代码段包在一个try语句里,然后在catch捕捉错误,在catch代码段里删除没有被记录到数据库的文件或者删除没有文件的记录,以保证业务数据的一致性。
因此,从业务这个角度讲,异常偏重于保护业务数据一致性,并且强调了对异常业务的处理。如果我们的代码中,只是象征性的try-catch,最后打印一个报错,over。这样的异常,不如不用,没有体现了异常的思想。所以,合理的代码应该如下:
try{
//可能出错的代码段
if(文件上传不成功) throw(上传异常);
if(插入数据库不成功) throw(数据库操作异常);
}catch(异常){
必须的补救措施,如删除文件,删除数据库插入记录,这个处理很细致
}
也可以如下:
function 上传{
if(文件上传不成功) throw(上传异常);
if(插入数据库不成功) throw(数据库操作异常);
}
//其他代码...
try{
上传;
其他;
}catch(上传异常){
必须的补救措施,如删除文件,删除数据库插入记录
}catch(其它异常){
记录log
}
捕获异常方式
上面的两种捕获异常的方式,前一种是在异常发生时,立刻捕获; 后一种是分散抛异常,集中捕获。 那到底应该是哪一种呢?
如果我们的业务很重要,那么异常越早处理越好,以保证程序在意外情况下能保持业务处理的一致性。比如一个操作有多个前提步骤,突然最后一个步骤异常了,那么其他前提操作都要消除掉才行,保证数据一致性。并且在这种核心业务下,有大量的代码来做善后工作,进行数据补救,这是一种比较悲观的异常。
如果我们的异常不是那么重要,并且在单一入口,MVC风格的应用中,为了保持代码流程的统一,则常常采用后一种异常处理方式,这种异常处理更多强调了业务流程的走向,对善后工作并不是很关心。这是一种乐观的异常。
那么异常的意义何在?
异常就是无法控制的运行时错误,会导致出错时中断正常逻辑运行,该异常代码后面的逻辑都不能继续运行。那么try/catch的好处就是可以把异常造成的逻辑中断破坏降到最小范围内,并且经过补救处理措施后不影响业务逻辑的完整性,乱抛异常和只抛不捕获,或捕获而不补救,会导致数据混乱。
这就是异常处理的一个重要作用,就是在我们精确控制运行时流程的时候,在程序中断的时候,有预见的用try缩小可能出错的影响范围,再及时捕获异常的发生并作出相应的补救,以使逻辑流程仍然能回到正常轨道上来。
怎样看php的异常?
我们已经看到了php中的异常机制是很鸡肋的,绝大多数情况下无法自动抛异常,必须用if-else来先进行判断,再手工抛出异常。这种处理方式看起来,比较像是多此一举。手动抛异常的意义就不是很大了,因为你手动抛异常也就意味着你在代码里已经充分预期到错误的出现了,也就算不得真正的“异常”了,而是意料之中的了。还是陷入了纷繁复杂的业务逻辑判断和处理中。java和C++语言做的比较好的就是定义了一堆内置的常见的异常,并且对异常进行了层次上的分类,不需要程序员判断各种异常情况后手工抛出,编译器会代我们进行判断业务是否发生错误,自动抛出异常。作为程序员,则只需要关心异常的捕获和随后补救,而不是像php中关注到底会发生哪些异常啊,用if-else来逐一判断,逐一抛异常。
php的异常机制很不完美,很多情况下和if-else相比没有明显的优势,这也是php的异常没有普及的原因。当然了,使用了异常也能一定程度上降低耦合性。
程序上怎么完成异常处理?
到处分散的判断可能存在的异常并抛出异常; 在业务逻辑层集中式的捕捉异常,并对异常分类进行补救处理,如数据的补救等;
数据不合法,类型不合法,变量不在指定数组中等这些属于异常否?若这些是异常也不能是在封装层去处理,应该是提前到业务逻辑处理前验证判断等;
不过,为了遵循“每个 throw 必须对应一个 catch”的原则,可以设置一个顶层的异常处理器来处理漏掉的错误。 有时,当异常被抛出时,您也许希望以不同于标准的方式对它进行处理。可以在一个 “catch” 代码块中再次抛出异常。
顶层的异常处理器?
set_exception_handler(handlerFunction)
set_exception_handler(array('class','method'))
用于设置顶级异常捕捉,也就是将捕捉没有被try-catch捕捉到的异常;
异常抛出,异常捕获代码
自定义异常及捕捉多个异常(优先顺序)
class MyException extends Exception{
private $my = '';
public function newGetMessage($msg){
echo $msg;
}
}
function logic(){
if(不合法){
throw new MyException('my exception message.');
}
if(其他异常){
throw new Exception('other exception message.');
}
}
try{
logic();
}catch(MyException $me){
$me->newGetMessage();
}catch(Exception $e){
$e->getMessage();
}
@tsingchan