防范bug的定义。系统各个组成部分的开发者都会做出一些假设,而这些假设之间的不匹配,是大多数致命和难以察觉的bug的主要来源。第4、5、6章所讨论的获取概念完整性的途径,就是直接面对这些问题。简言之,产品的概念完整性在使它易于使用的同时,也使开发更容易进行以及bug更不容易产生。
上述方法所意味的详尽体系结构设计正是出于这个目的。Bell实验室安全监控系统项目的V.A.Vyssotsky提出,“关键的工作是产品定义。许许多多的失败完全源于那些产品未精确定义的地方。1”细致的功能定义、详细的规格说明、规范化的功能描述说明以及这些方法的实施,大大减少了系统中必须查找的bug数量。
测试规格说明。在编写任何代码之前,规格说明必须提交给测试小组,以详细地检查说明的完整性和明确性。如同Vyssotsky所述,开发人员自己不会完成这项工作:“他们不会告诉你他们不懂。相反,他们乐于自己摸索出解决问题和澄清疑惑的办法。”
自顶向下的设计。在1971年的一篇论文中,Niklaus Wirth把一种被很多最优秀的编程人员所使用的设计流程2形式化。尽管他的理念是为了程序设计,同样也完全适用于复杂系统的软件开发设计。他将程序开发划分成体系结构设计、设计实现和物理编码实现,每个步骤可以使用自顶向下的方法很好地实现。
简言之,Wirth的流程将设计看成一系列精化步骤。开始是勾画出能得到主要结果的,但比较粗略的任务定义和大概的解决方案。然后,对该定义和方案进行细致的检查,以判断结果与期望之间的差距。同时,将上述步骤的解决方案,在更细的步骤中进行分解,每一项任务定义的精化变成了解决方案中算法的精化,后者还可能伴随着数据表达方式的精化。
在这个过程中,当识别出解决方案或者数据的模块时,对这些模块的进一步细化可以和其他的工作独立,而模块的大小程度决定了程序的适用性和可变化的程度。
Wirth主张在每个步骤中,尽可能使用级别较高的表达方法来表现概念和隐藏细节,除非有必要进行进一步的细化。
好的自顶向下设计从几个方面避免了bug。首先,清晰的结构和表达方式更容易对需求和模块功能进行精确的描述。其次,模块分割和模块独立性避免了系统级的bug。另外,细节的隐藏使结构上的缺陷更加容易识别。第四,设计在每个精化步骤的层次上是可以测试的,所以测试可以尽早开始,并且每个步骤的重点可以放在合适的级别上。
当遇到一些意想不到的问题时,按部就班的流程并不意味着步骤不能反过来,直到推翻顶层设计,重新开始整个过程。实际上,这种情况经常发生。至少,它让我们更加清楚在什么时候和为什么抛弃了某个臃肿的设计,并重新开始。一些糟糕的系统往往就是试图挽救一个基础很差的设计,而对它添加了很多表面装饰般的补丁。自顶向下的方法减少了这样的企图。
我确信在十年内,自顶向下进行设计将会是最重要的新型形式化软件开发方法。
结构化编程。另外一系列减少bug数量的新方法很大程度上来自Dijkstra3。Bohm和Jacopini的为其提供了理论证明4。
基本上,该方法所设计程序的控制结构,仅包含语句形式的循环结构,例如DO WHILE,以及IF...THEN...ELSE的条件判断结构,而具体的条件部分在IF...THEN...ELSE后的花括号中描述。Bohm和Jacopini展示了这些结构在理论上是可以证明的。而Dijkstra认为另外一种方法,即通过GO TO不加限制的分支跳转,会产生导致自身逻辑错误的结构。
这种方法的基本理念非常优秀,但仍有人提出了一些反面的意见。一些附加的控制结构非常有效,例如,在多个条件下的多路分支(CASE、SWITCH语句),异常跳转等(GO TO ABNORMAL END)。此外,关于完全避免GO TO语句的说法显得有些教条主义,而且似乎有些吹毛求疵。
关键的地方和构建无bug程序的核心,是把系统的结构作为控制结构来考虑,而不是独立的跳转语句。这种思考方法是我们在程序设计发展史上向前迈出的一大步。