软件调试修炼之道之——让软件学会自己寻找缺陷

有很多书或文章都在探讨如何编写好软件,但很少有讨论如何编写容易调试的软件,幸好,如果遵从创建良好软件的一般原则,即分离问题、避免复制、隐藏信息,并创建结构良好、易于理解和修改的软件,那么就能编写出容易调试的软件。良好的设计与调试并无冲突

代码的每一块都建立在一个无数假设的平台上——某些条件必须是正确的才能让运行结果符合预期,往往缺陷的出现是因为某些假设是不成立的或者是错误的。避免做出这些假设是不可能的也是没意义的,我们不仅可以验证它们,也可以通过断言来自动验证。

断言长什么样?在Java里有两种格式,一种简单的格式:assert <condition>; 还有一种格式包含一条信息,若断言失败则显示信息:assert <condition>: <message>; 无论使用哪种格式,无论何时被执行,断言都要计算条件,如果条件为真,则什么也不做,如果条件为假则抛出AssertionError的异常,此时程序立即退出。

进行单元测试的时候是否需要断言

断言和单元测试时解决相关但不同问题的方法,单元测试不能检测没有在测试用例中被引用的缺陷,而任何时候都可以使用断言来检测缺陷,无论是在测试中还是其他什么场合。

断言使得程序出现错误时,不需要我们主动去寻找缺陷,而是让程序自己通知出错并且告诉我们这个缺陷的相关信息。

有没有一些普遍的规律帮助确定我们该断言哪一类的问题呢

先决条件:对于方法来说,先决条件是那些在方法被调用之前就必须满足的条件,它保证方法可以按预期运行。

后置条件:对于方法来说,后置条件是那些方法被调用之后所要保持的条件。

不变量:对象的不变量是那些只要在方法被调用之前它的先决条件被满足就始终为真的数据。

实现一个类时只要总按照上面三条编写断言,软件就会自动检测大量可能存在的各类缺陷。

既然断言的益处这么多,为何我们在有时候还要选择关闭断言功能呢?因为效率和健壮性。

断言的求值花费时间,对于程序的功能也没有任何贡献,甚至有可能它会影响到程序的性能。而断言的失败会让软件随意退出并弹出一个简短且没帮助的信息,这回导致程序的健壮性。

软件在产品阶段应该是健壮的,但调试的时候应该是脆弱的

想象一下我们编写了一个函数,它包含一个字符串类型的参数,如果这个字符串全部是大写就返回真,否则返回假,以下是Java中一种可能的实现方式:

public static boolean allUpper(String s)
{
    CharacterIterator i = new StringCharacterIterator(s);
    for(char c = i.first(); c != CharacterIterator.DONE; c = i.Next())
        if(Character.isLowerCase(c))
            return false;
    
    return true;
}

这是一个十分合理的函数——但是由于某种原因我们将null作为参数传递给这个函数,我们的软件将会崩溃。为避免这种情况发生,一些开发者可能会在代码开头添加一些代码:

if(s == null)
    return false;

现在程序不会崩溃了,但是当程序调用函数的时候传进一个空字符串是什么意思呢?有可能任何这样做的代码都包含缺陷,但现在我们将它掩盖过去了。

断言给我们提供了一个非常简单的解决方法,无论在哪里写防错性代码,都要确保使用断言保护这段代码:

assert s != null: "Null string passed to allUpper";
if (s == null)
    return false;

这样就可以得到健壮性的产品软件和脆弱的开发/调试软件。

断言是一个缺陷检测机制,而不是一个错误处理机制。下面这些例子几乎可以肯定是不应该用断言来处理的:

试图打开一个文件却发现它不存在
通过网络连接检测和处理无效的数据
当写入文件时空间已用完
网络错误

调试子系统与代替测试之间不同的是规模和范围,代替测试技术是一个短生命周期的对象,仅仅用于单一测试;调试子系统通常用来完全替代其对应的产品子系统,实现了所有接口且在广泛的用例中正确的运行。

终端用户的需求与开发者的需求是非常不一样的,因此需要解决用户界面问题,确保用户界面层尽可能的小,仅仅是照顾显示信息的细节和征求用户的输入就可以了,不应包含任何业务逻辑。

调试内存分配器可以帮助检测和解决很多常见的问题:

1. 通过跟踪内存的分配和释放,可以检测内存泄露
2. 在分配的内存前后放置保护器,可以检测缓冲区溢出和内存损坏
3. 通过用已知模式填充内存区,可以检测哪个实例使用的内存没有被初始化
4. 通过使用已知的模式填充已释放的内存并让其常驻,可以检测内存后又被写入的实例

在修改第三方代码的同时,可以选择让自己的代码不同于调试版本,在诊断中内置控制(禁用功能,提供其他实现)会非常有用。

不要等待资源泄露表现出来——主动且尽早检测它们。

posted @ 2013-02-25 17:04  Ribbon  阅读(373)  评论(0编辑  收藏  举报