作为例子,我们来编写一个求解一元二次方程的程序。利用初等代数知识,我们知道一 元二次方程 ax2+bx+c=0 的两个根是:

    据此很容易写出下面这个程序:

    【程序 3.5】eg3_5.py

    本程序先由用户输入一元二次方程的三个系数,然后利用公式算出两个根,并显示结果。 这个版本看上去很直接了当,似乎符合预期的功能,但实际上这个版本很有问题。下面我们 来运行这个程序:

    1. Enter the coefficients (a, b, c): 1,2,3
    2. Traceback (most recent call last):
    3. File "<pyshell#0>", line 1, in <module> import eg3_x
    4. File "eg3_x.py", line 3, in <module> discRoot = math.sqrt(b * b - 4 * a * c)

    为了增强程序 3.5 的健壮性,可以用 if 语句来检查判别式的值,以便区别处理方程有实 数根和无实数根的两种情形,避免在无实数根的情况下崩溃。改进版本如下:

    【程序 3.6】eg3_6.py

    从程序中可见,仅当判别式 discrim 大于等于 0 时,才去调用 math.sqrt 函数求其平方根, 这样 sqrt 不会出错,从而避免了程序崩溃;当 discrim 为负数时,并不调用 sqrt,而是向用户 显示一些信息,告诉用户发生了什么,程序同样能正常结束。

    下面分别测试程序 3.6 对两种情形的判别式的执行效果:

    1. Enter the coefficients (a, b, c): 1,2,3
    2. The equation has no real roots!
    3. >>> reload(eg3_6)①
    4. Enter the coefficients (a, b, c): 1,3,2

    从结果可见程序 3.6 确实达到了预期的目的,健壮性得到了增强。

    很多时候要执行的语句实际上是函数调用②,被调用的函数可能是我们自己写的,也可 能是标准函数库里定义的。函数作为一个具有相对独立性的程序块,一般都有自己的错误检 测代码,并根据执行是否正常而返回不同的“错误码”给调用者。这样,函数的调用者可以 无条件地调用函数,然后根据函数返回的错误码来了解函数的执行情况,并基于此来决定下 一步行动。例如,假设有一个求平方根的函数 robustSqrt 在参数为负数时返回错误码-1(由 于实数的平方根总是正数,返回-1 就表明发生了异常):

    那我们就可以不必先检测判别式的正负,而是直接调用 robustSqrt,并通过它的返回值来检测 是否发生了异常。示例代码片段如下:

    1. if discRoot < 0:
    2. print "The equation has no real roots!"
    3. else:
    4. root1 = (-b + discRoot) / (2 * a)

    与程序 3.6 中的错误检测代码相比,上面这种错误检测代码更可取。理由是:函数就像 一个提供特定功能的“黑盒”,我们只需调用其功能,不需了解其内部细节,因此让函数自己 在内部进行错误检测更符合“黑盒”原则。程序 3.6 中的错误检测建立在对函数 math.sqrt 内 部执行细节(即负数导致崩溃)的了解之上,因而不符合“黑盒”原则。