有关PHP异常和错误处理机制的思考(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luyaran/article/details/84527216

我们从一门语言的层面上来看的话,这个语言通常具有很多的错误处理的一个模式,但是这些个错误处理模式,往往就是建立在约定俗成的基础上,也可以说,这些错误都是可以预知的。

但是在大型的一个项目或者说系统里,如果我们每次调用一个功能模块的时候,都去逐一检测我们这个模块中肯能存在的错误,很明显的就会看到,代码会变得冗余和复杂,到处是try...catch,或者if...else等等判断机制,并且还会很严重的影响代码的可读性。

而且我们要知道,人为的因素是不可靠的,写程序的人可能并不会把这些问题当成一回事,但是最后,可能就会导致系统故障。

基于上述情况,这些年逐渐形成了异常和错误处理机制,强迫消除这些问题,把问题提交给能解决它的环境,同时,把“描述正常过程中做什么事情的代码”和“出了问题怎么办的代码”进行了一个分离。

这个异常的这种思想,可以追溯到20世纪60年代,在C++、Java中,发扬光大,而我们的PHP呢,就很明智的借鉴了这两种语言的异常处理机制。

在PHP中,异常是指程序运行中,出现了不符合预期以及与正常的流程不同的情况。一种不正常的情况就是指按着正常逻辑不会出错,但是嘞,它偏偏就是出现了错误,这可以看做是逻辑和业务流程的一种中断,而不是语法错误。

而在PHP中,错误就是属于自身的情况,是一种非语法或是环境导致的,让编译器无法通过检查,甚至无法运行的情况。

在现有的各种语言里,异常(exception)和错误(error)的概念是不一样的,你在PHP里,遇到任何一个自身错误都会触发一个错误,而不是抛出异常(还有一些情况会同时抛出异常和错误),PHP一旦遇到非正常代码,都会触发错误,而不是抛出异常,在这个意义上,如果我们想使用异常来处理不可预料的错误,是办不到的。例举一个说法哈,比我我们想在文件不存在且数据库连接打不开的情况下触发异常,这是不可能的,因为PHP会把它当成一个错误抛出,而不是作为一个异常来自动捕获。

来看一个经典的除零问题的示例代码感受下:

$a = null;
try {
    $a = 5/0;
    echo $a.PHP_EOL;
} catch (exception $e) {
    $e->getMessage();
    $a = -1;
}

echo $a;

上述代码运行的话,会出现两种结果,一,空白页面,二,warning警告(也是一种错误,只不过级别较低),由此我们可以看到,PHP里,对于这种“除以零”这种异常情况,它会认为是一个错误,而不会认为是一个异常,更不会使得程序进入异常处理流程,所以,最终的结果值,也不会是我们预想中-1。在PHP里,只有我们主动throw之后,我们才可以来捕获异常(一般情况下是这样的,但是有一些异常,PHP也是可以自动捕获的)。

我们可以这样认为,PHP无法自动捕获有意义的异常,它会把所有不正常的情况都视作错误,我们如果想要捕获这个异常,就必须得使用if...else这个结构,并且要保证代码是正常的,然后判断我们的除数为0,则手动抛出异常,然后再捕获异常。

PHP内建的异常类主要有pdoexception、reflection exception,然后,我们来确定一个观念,就是PHP只有我们自己手动抛出异常之后,才可以尝试捕获异常,或者是我们有内建的异常机制时,PHP会先触发错误,完事之后会捕获异常。

来看下PHP里异常的使用代码案例:

class emailException extends exception
{
    
    
}

class passwordException extends exception
{
    
    function __toString()
    {
       return "Exception ".$this->getCode()." :".$this->getMessage()." in File ".$this->getFile()." on line ".$this->getLine();
    }
}

function reg($reginfo=null)
{
    if(empty($reginfo) || !isset($reginfo)) {
        throw new Exception("undfined data");
    }

    if (empty($reginfo['email'])) {
        throw new emailException("the email content is empty");
    }

    if ($reginfo['pwd'] != $reginfo['repwd']) {
        throw new passwordException("twice password is not same");
    }

    echo "successfully";
}

try {
    reg(array('email' => "[email protected]", "pwd" => "201314", "repwd" => "2013141992"));
} catch (emailException $email) {
    echo $mail->getMessage();
} catch (passwordException $password) {
    echo $password;
    echo PHP_EOL."make";
} catch (Exception $e) {
    echo $e->getTraceAsString();
    echo PHP_EOL."other";
}

接下来,咱们就来分析下上述代码哈。

首先就是emailException和passwordException这两个咱们自定义的异常类,一个只是继承,另外一个则做出了咱们自己想要的异常信息的一个输出格式,我们可以根据自己的需求来定制自己需要的异常提醒模式,完事就可以根据不同的业务,来自己手动抛出异常信息,来快速确定错误位置。

完事就是下面这个reg检测方法,它主要就是用来检测传入的一个数组,以及里面的参数是否正确,如果没有数据的话,就直接把异常发给exception这个超类,抛出异常信息,如果email的信息不存在,则把异常发给emailException这个类,手动抛出异常,结束进程,如果前后两个密码不正确的话,则把异常发给passwordException,手动抛出异常,结束进程。

到这里呢,我们就完成了异常的分发,但是还不够,我们还需要对异常进行分拣,并且做出相应的处理,那就是最后一段代码的事情了。

我们可以自己手动尝试变换不同的条件,来检测我们最后的那个异常的分拣流程,不过要注意的是,exception作为超类,应该放在最后捕获异常,不然的话,这个exception超类捕获异常后,线程直接就停止了,后面的捕获异常类,也就没有用处了,还有就是这个exception超类,不能针对性的提示错误信息和处理异常。

最后我们来看下经常会用到异常处理机制的几个场景。

1、对程序的悲观预测

如果一个程序员对自己的代码有“悲观情绪”,咱不是说他的代码质量不高,而是说,他认为他的代码,不能很好的处理各种可预见的,不可预见的情况,那么,这个人就会进行异常处理。

假设哈,程序员悲观的认为自己的这段代码在高并发的情况下,会产生死锁,那么他就会悲观的抛出异常,然后在死锁时,进行捕获异常,之后,对该异常,进行细致的处理。

2、程序的需要和对业务的关注

如果程序员不希望业务代码中充斥着大量的打印、调试等处理,通常会使用异常处理机制,或者在业务上定义一些自己的异常,这个时候呢,就需要我们自定义一个异常,完事要把异常收集起来,到月底集中进行处理,还有就是希望我们的程序可以预见性的处理一些可能发生的或者会影响我们的正常业务代码。

在上述情况下,异常是业务处理中,必不可少的一个环节,还有就是,异常认为,数据一致很重要,我们在数据一致性可能被破坏的时候,就需要我们的异常机制进行事后的补救工作。

假如,我们要做个上传文件的功能,需要把文件保存在一个目录里,完事在数据库中插入这个文件的记录,那么,这两步操作就可以看作是密不可分的集成业务,如果文件上传失败,但是记录保存成功,这就会导致最后别人无法下载这个文件。

那么,我们可以这样来思考,如果我们对文件上传成功或者数据库插入成功不做提示,但是失败的话,就抛出异常的话,那么我们就可以把这个上传和插入的代码,放入我们的try...catch的代码块里,然后用catch模块来捕获异常,完事可以对失败的步骤进行处理,以此,就可以来保证数据的一致性了。

因此呢,从业务的层面来看异常的话,它主要就是保持业务数据的一致性,并且主要是对异常事务的一个违规处理。来看一个合理的try...catch代码模块:

还可以是如下格式:

上述的两种捕获异常的方式呢,第一种是在异常发生后,立即捕获,最后一种就是分散抛异常,然后集中捕获。至于我们选择使用哪一种呢,就要看自己实际的需求了。

如果业务很重要,那么自然是越早处理异常越好,可以保证程序在意外的情况下,可以持续处理业务,保证数据的一致性,举个例子啊,比如一个操作,有多个前提步骤,前面走得好好的,最后一个突然就异常了,那么其它的正常操作在这时候就需要全部移除掉了,这样才能保证数据的一致性,并且在这种核心的业务下,有大量的代码来做善后的一个工作,进行数据补救,这是一种比较悲观而又非常重要的异常,我们应该把它消灭在局部,避免异常的扩散。

但是如果异常不是那么重要,并且在单一入口、MVC风格的应用中,为了保持代码风格的统一,我们常常会采用最后一种异常处理的一个方式,这种方式更多的是强调一个业务流程的走向,而对善后的工作,则不是那么关心,这种异常通常是次要异常,我们将其集中处理,主要是为了使得流程更加专一。

异常处理机制实际上可以把每一件事情当做事务来考虑,我们还可以把一场看作是内建的一个恢复机制,就是说如果某部分代码执行失败,异常将恢复到某个已知的稳定的点上,这个点就是程序的上下文环境,而try块里的代码就保存catch所要知道的程序上下文的信息,因此如果很看重异常,就应当使用第一种异常处理和捕获的方式。

3、语言级别的健壮性要求

提到这个健壮性啊,我们知道,PHP跟别的语言相比起来,是有点弱,例如、、、、、Java,提到这个我就上火啊,但是咱这是技术性文章,不应该带有个人的想法,咱就直接说下这个java强在哪里吧。

Java,支持多线程,Java认为多线程被中断的情况,是无法预料和避免的,所以,Java使用者必须要正是这种情况,要么你抛出异常,不管它,要么你就捕获异常进行处理,反正不管怎样,你必须意识到,异常可能发生,这类异常是强制性的,还有很多非强制性的,这个就看程序员自己了,总之java的对异常的分类和约束,保证了程序的健壮性。

我们很清楚,异常就是无法控制的程序运行时的错误,会中断正常的逻辑运行,并且,这个异常后面的代码,也是不能执行,那么在这里这个try...catch代码块的好处就显而易见了。

它可以把异常造成的逻辑中断的破坏性,降低到最小的范围,还可以经过补救处理后,不影响业务逻辑的完整性,完事,乱抛异常,只抛异常,不捕获,或者捕获而不补救,都会导致数据混乱,这就是一场的一个重要的作用,它可以精确地控制运行时的流程,在程序中断的时候,有预见的用try来缩小可能出错的影响范围,及时的捕获异常,并且做出相应的处理,使得逻辑可以回到正常的轨道上。

我们明知道PHP异常机制是不足的,那我们有没有什么办法可以很好地处理呢???

答案就是,结合PHP的错误处理机制来主动抛出异常。

我们使用异常能在一定程度上降低程序的耦合性,但千万不能滥用,滥用的后果,就是代码多处被挂起,流程变得更加复杂,并且难以理解,但是可以肯定的是,异常处理机制,在PHP中,有着很大的价值,越复杂的应用,我们越要考虑合理的来使用异常处理机制。

顺道提一嘴,sql中也定义了一大堆exception,并且这些exception之间还存在着层级关系,不过,它们只是一个空壳,没有什么方法,需要我们自己手动填充使用 ,实际上,就是起到一个命名参考的作用。

好啦,本次记录就到这里了,关于错误处理,咱们下篇文章再继续。

如果感觉不错的话,请多多点赞支持哦。。。

猜你喜欢

转载自blog.csdn.net/luyaran/article/details/84527216