探讨程序设计语言中的异常处理机制的由来

本文为作者原创内容,未经许可,禁止转载。如您发现侵权行为,请联系我们


    这篇文章主要讲讲程序的异常处理机制,结合多种程序设计语言。
    我们知道,程序在执行时也可能出错,出错时候如何处理很重要。而在程序中错误的处理方法大致分为两种:使用返回值和使用异常(异常处理机制)。除了C语言之外多数的语言都支持异常处理机制。
    其实,在日常的编程中,我很少用到异常的抛出,而大多数的情况下,都是使用返回值进行标注,根据返回值再做具体的程序处理,可是我会在一些框架的底层看到大量的使用异常抛出。所以,查询了一下资料,对程序中的异常处理好好的了解一下。
    为什么会有异常处理机制?
    因为程序会出错,比如将文件写入磁盘中,磁盘已满导致的写入错误,或者是参数的传入错误,以及数据库的写入错误等等,这些错误中包含我们能预见的错误,同时也包含了我们不能预见的错误。就我自身而言,最喜欢的做法就是对每一个可能出现的错误写入返回值,在调用处进行检测,判断其返回的结果然后在调用处进行错误的处理。比如说,函数中写入数据库时,用一个变量进行标注,判断这个变量是否是我们期待的结果,如果是表示写入成功,否则表示我们的数据插入失败,将返回预先规定的值,如FALSE,我们根据返回者再进行判断。还有一种方法就是预先定义全局变量用于接受错误信息。这两种都是遵循出错时写入信息然后做检查的思路。我这样做了很久,没觉得有什么问题,但是后来我发现,这种做法有很大的弊端,如:

  1. 会遗漏错误。比如说,我们调用一个函数的时候,这个函数有很多的错误返回信息,而我们在调用的时候,可能会对错误的返回信息漏掉检查。一个函数的错误返回信息有很多种,而程序员在调用函数时,漏掉某个错误返回信息的检测也是很有可能的,而如果都进行检测,这无疑增加了代码的冗余。
  2. 错误的处理导致代码的可读性下降。上面讲到对每个错误返回进行验证大大增加了代码的冗余,同时也降低了代码的可读性。比如下面的这段代码:
<?php

function test($error)
{

if ($error == 1) {
return 'error1';
}
if ($error == 2) {
return 'error2';
}
if ($error == 3) {
return 'error3';
}
//.....诸如此类的很多逻辑错误返回值
//全部验证之后进行下面的业务逻辑
return 'success';
}


$param = rand(1,3);
$result = test($param);
//这里根据业务逻辑进行返回值的验证
if ($result == 'error1') {
echo '出现错误1';
}elseif ($result == 'error2'){
echo '出现错误2';
}elseif ($result == 'error3'){
echo '出现错误3';
}
//上面就是对每一个错误进行验证
die;
也就是说在代码实现中引入了很多的错误处理代码,使得程序变得难懂。
如何解决这样的问题,我们来看看这种程序语言中都会面临的问题在对应的语言中是被如何处理的。

C语言中,为了集中进行错误处理,可以使用goto语句将错误处理跳转到同一处。正如C语言中的这种思想:集中处理,逐渐演化成我们现在程序中的异常处理机制。
int main(){
if(error1) goto ERROR;
if(error2) goto ERROR;
return;
}
ERROR:
/*这里进行失败的处理*/
/*这里进行失败的处理*/
从上面的代码形式来看,实现了针对出错时的代码处理进行分离的效果。

而更加之后的程序设计语言大都采用了如下的异常抛出格式:
try{ 
//可能出现的错误语句
}catch(){
//捕获错误之后的操作
}finally{
//最终要执行的操作
}
    将可能出错的代码括起来的语句结构(我们将可能会出错的代码括起来防止程序员会漏掉对异常抛出的捕获,如果异常抛出而没有进行捕获,不同的程序设计语言都会报错,从而影响程序的正常执行)。其中使用throw进行抛出。

    为什么要引入finally?

    finally是微软公司引入的,原因是采用这种结构化的异常处理可以提高代码的可靠性。比如,程序在程序员意料之外结束时,也可以正确地释放锁定的内存和文件等资源。再具体的说,如果在函数的入口处执行了lock(锁定某个资源),调用函数之后随后就要执行unlock进行资源的释放,如果函数的出口只有一个,只要在出口前执行unlock即可。如果有多处return,就需要在每一个return前面执行unlock,此时无遗漏的执行8unlock就变得十分困难了。
    使用finally解决这种情况,finally代码块在try代码块执行结束后一定会被执行到,不管try代码块中是否发生异常。90年左右微软公司开始使用finally,95年发布的Java语言也引入了finally。现在PHP,Python和ruby语言也支持同样的语句结构。

    那现在我们就要思考一个问题,什么时候抛出异常?

    比如根据具体的可预见的业务返回值就不算是异常,是业务的一种返回处理,不能算是异常。比如说,从数据库中读取某个字段,根据该字段判断是否要进行接下来的操作,如果不能进行,返回到页面提示用户出现错误,但这本身就是代码的一个合法的流程,不是异常。

    常见的异常有:函数调用时参数不足、数组越界等。
函数调用时,参数不足时不同的语言处理这种情况的态度也是不一样的,Python会在调用之后立即抛出异常,JavaScript会把缺失的参数当做未定义的特殊值继续执行。PHP会提示一个警告级别的错误,然后继续执行。

    通过上面的例子我们知道,不同的程序语言设计针对不同的情况,也不能关于什么是异常达成统一意见。我认为,在程序中尽早的发现错误有利于我们排除BUG,如果按照JavaScript的方式返回未定义,接着执行程序,会带来更多不可预知的错误。发生错误时应该停止操作立即报告,这一设计思想被称为错误优先(fail first),在实际应用中,简单的停止程序可能不太妥当,但是至少在学习和开发阶段需要这样处理。

    本文属于个人原创内容,转载需注明出处链接。