担降到最小。方法。第一个想法是借助那些出于语言的要求而必须存在的语句,来附加尽可能多的“文档”信息。因此,标签、声明语句、符号名称均可以作为工具,用来向读者表达尽可能多的意思。第二个方法是尽可能地使用空格和一致的格式提高程序的可读性,表现从属和嵌套关系。第三,以段落注释的形式,向程序中插入必要的记叙性文字。大多数文档一般都包括足够多的逐行注释,特别是那些满足公司呆板的“良好文档”规范的程序,通常就包含了很多注释。即使是这些程序,在段落注释方面也常常是不够的,而段落注释能提供总体把握和真正加深读者对整件事情的理解。因为文档是通过程序结构、命名和格式来实现的,所有这些必须在书写代码时完成。不过,这也只是应该完成的时间。另外,由于自文档化的方法减少了很多附加工作,使这件工作遇到的障碍会更少。一些技巧。图15.3 是一段自文档化的PL/I 程序3。圆圈中的数字不是程序的组成部分,而是用来帮助我们进行讨论。- 98 ------------------------ Page 111-----------------------图15.3:一段子文档化程序1. 为每次运算使用单独的任务名称。维护一份日志,记录程序运行的目的、时间和结- 99 ------------------------ Page 112-----------------------果。如果名称由一个助记符(这里是QLT)和数字后缀 (4)组成,那么后缀可以作为运算编号,把列表和日志联系在一起。这种技术要求为每次运算准备新的任务卡,不过这项工作可以采用“重复进行公共信息的批处理”来完成。2. 使用包含版本号和能帮助记忆的程序名称。即,假设程序将会有很多版本。例子中使用的是1967年的最低一位数字。3. 在过程(PROCEDURE)的注释中,包含记叙性的描述文字。4. 尽可能为基本算法提供参考引用,通常它会指向更完备的处理方法。这样,既节省了空间,同时还允许那些有经验的读者能非常自信地略过这一段内容。5. 显示和算法书籍中的传统算法的关系。a) 更改 b) 定制细化 c) 重新表达6. 声明所有的变量。采用助记符,并使用注释把DECLARE 转化成完整的说明。注意,声明已经包含了名称和结构性描述,需要增加的仅仅是对目的的解释。通过这种方式,可以避免在不同的处理中重复名称和结构性的描述。7. 用标签标记出初始化的位置。8. 对程序语句进行分组和标记,以显示与设计文档中语句单元的一致性。9. 利用缩进表现结构和分组。10. 在程序列表中,手工添加逻辑箭头。它们对调试和变更非常有帮助。它们还可以补充在页面右边的空白处(注释区域),成为机器可读文字的一部分。11. 使用行注释来解释任何不很清楚的事情。如果采用了上述技术,那么注释的长度和数量都将小于传统惯例。12. 把多条语句放置在一行,或者把一条语句拆放在若干行,以吻合逻辑思维,表示和其他算法描述一致。为什么不?这种方法的缺点在什么地方?很多曾经遇到的困难,已经随着技术的进步逐渐解决了。最强烈的反对来自必须存储的源代码规模的增加。随着编程技术越来越向在线源代码- 100 ------------------------ Page 113-----------------------存储的方向发展,这成为了一个主要的考虑因素。我发现自己编写的APL 程序注释比PL/I程序要少,这是因为APL 程序保存在磁盘上,而PL/I 则以卡片的形式存储。然而,与此同时文本编辑的访问和修改,也在朝在线存储的方向前进。就像前面讨论过的,程序和文字的混合使用减少了需要存储的字符总数。对于文档化程序需要更多输入击键的争论,也有类似的答案。采用打字方式,每份草稿、每个字符需要至少一次击键。而自文档化程序的字符总数更少,每个字符需要的击键次数也更少,并且电子草稿不需要重复打印。那么流程图和结构图的情况又如何呢?如果仅仅使用最高级别的结构图,那么另外使用一份文档的方法可能更安全一些,因为结构通常不会频繁变化。它理所当然也可以作为注释合并到文档中。这显然是一种聪明的作法。以上讨论的用于文档和软件汇编的方法到底有多大的应用范围呢?我认为“自文档化”方法的基本思想可以得到大规模的应用。““自文档化”方法”对空间和格式要求更为严格,这一点的应用可能会受限;而命名和结构化声明显然可以利用起来,在这方面,宏可以起到很大的帮助;另外,段落注释的广泛使用在任何语言中都是一个很棒的实践。自文档化方法激发了高级语言的使用,特别是用于在线系统的高级语言——无论是对批处理还是交互式,它都表现出最强的功效和应用的理由。如同我曾经提到的,上述语言和系统强有力地帮助了编程人员。因为是机器为人服务,而不是人为机器服务。因此从各个方面而言,无论是从经济上还是从以人为本的角度来说,它们的应用都是非常合情合理的。- 101 ------------------------ Page 114-----------------------没有银弹-软件工程中的根本和次要问题(No Silver Bullet – Essence and Accident inSoftware Engineering)没有任何技术或管理上的进展,能够独立地许诺十年内使生产率、可靠性或简洁性获得数量级上的进步。There is no single development, in either technology or management technique, which by itselfpromises even one order-of-magnitude improvement within a decade in productivity, in reliability,in simplicity.摘要1所有软件活动包括根本任务——打造由抽象软件实体构成的复杂概念结构,次要任务——使用编程语言表达这些抽象实体,在空间和时间限制内将它们映射成机器语言。软件生产率在近年内取得的巨大进步来自对后天障碍的突破,例如硬件的限制、笨拙的编程语言、机器时间的缺乏等等。这些障碍使次要任务实施起来异常艰难,相对必要任务而言,软件工程师在次要任务上花费了多少时间和精力?除非它占了所有工作的 9/10,否则即使全部次要任务的时间缩减到零,也不会给生产率带来数量级上的提高。因此,现在是关注软件任务中的必要活动的时候了,也就是那些和构造异常复杂的抽象概念结构有关的部分。我建议:仔细地进行市场调研,避免开发已上市的产品。在获取和制订软件需求时,将快速原型开发作为迭代计划的一部分。有机地更新软件,随着系统的运行、使用和测试,逐渐添加越来越多的功能。不断挑选和培养杰出的概念设计人员。- 102 ------------------------ Page 115-----------------------介绍在所有恐怖民间传说的妖怪中,最可怕的是人狼,因为它们可以完全出乎意料地从熟悉的面孔变成可怕的怪物。为了对付人狼,我们在寻找可以消灭它们的银弹。大家熟悉的软件项目具有一些人狼的特性(至少在非技术经理看来),常常看似简单明了的东西,却有可能变成一个落后进度、超出预算、存在大量缺陷的怪物。因此,我们听到了近乎绝望的寻求银弹的呼唤,寻求一种可以使软件成本像计算机硬件成本一样降低的尚方宝剑。但是,我们看看近十年来的情况,没有银弹的踪迹。没有任何技术或管理上的进展,能够独立地许诺在生产率、可靠性或简洁性上取得数量级的提高。本章中,我们试图通过分析软件问题的本质和很多候选银弹的特征,来探索其原因。不过,怀疑论者并不是悲观主义者。尽管我们没有看见令人惊异的突破,并认为这种银弹实际上是与软件的内在特性相悖,不过还是出现了一些令人振奋的革新。这些方法的规范化、持续地开拓、发展和传播确实是可以在将来使生产率产生数量级上的提高。虽然没有通天大道,但是路就在脚下。解决管理灾难的第一步是将大块的“巨无霸理论”替换成“微生物理论”,它的每一步——希望的诞生,本身就是对一蹴而就型解决方案的冲击。它告诉工作者进步是逐步取得的,伴随着辛勤的劳动,对规范化过程应进行持续不懈的努力。由此,诞生了现在的软件工程。是否一定那么困难呢?——根本困难不仅仅是在目力所及的范围内,没有发现银弹,而且软件的特性本身也导致了不大可能有任何的发明创新——能够像计算机硬件工业中的微电子器件、晶体管、大规模集成一样——提高软件的生产率、可靠性和简洁程度。我们甚至不能期望每两年有一倍的增长。首先,我们必须看到这样的畸形并不是由于软件发展得太慢,而是因为计算机硬件发展得太快。从人类文明开始,没有任何其他产业技术的性价比,能在30 年之内取得6 个数量级的提高,也没有任何一个产业可以在性能提高或者降低成本方面取得如此的进步。这些- 103 ------------------------ Page 116-----------------------进步来自计算机制造产业的转变,从装配工业转变成流水线工业。其次,让我们通过观察预期的软件技术产业发展速度,来了解中间的困难。效仿亚里士多德,我将它们分成根本的——软件特性中固有的困难,次要的——出现在目前生产上的,但并非那些与生俱来的困难。我们在下一章中讨论次要问题。首先,来关注内在、必要的问题。一个相互牵制关联的概念结构,是软件实体必不可少的部分,它包括:数据集合、数据条目之间的关系、算法、功能调用等等。这些要素本身是抽象的,体现在相同的概念构架中,可以存在不同的表现形式。尽管如此,它仍然是内容丰富和高度精确的。我认为软件开发中困难的部分是规格化、设计和测试这些概念上的结构,而不是对概念进行表达和对实现逼真程度进行验证。当然,我们还是会犯一些语法错误,但是和绝大多数系统中的概念错误相比,它们是微不足道的。如果这是事实,那么软件开发总是非常困难的。天生就没有银弹。让我们来考虑现代软件系统中这些无法规避的内在特性:复杂度、一致性、可变性和不可见性。复杂度。规模上,软件实体可能比任何由人类创造的其他实体要复杂,因为没有任何两个软件部分是相同的(至少是在语句的级别)。如果有相同的情况,我们会把它们合并成供调用的子函数。在这个方面,软件系统与计算机、建筑或者汽车大不相同,后者往往存在着大量重复的部分。数字计算机本身就比人类建造的大多数东西复杂。计算机拥有大量的状态,这使得构思、描述和测试都非常困难。软件系统的状态又比计算机系统状态多若干个数量级。同样,软件实体的扩展也不仅仅是相同元素重复添加,而必须是不同元素实体的添加。大多数情况下,这些元素以非线性递增的方式交互,因此整个软件的复杂度以更大的非线性级数增长。软件的复杂度是必要属性,不是次要因素。因此,抽掉复杂度的软件实体描述常常也去掉了一些本质属性。数学和物理学在过去三个世纪取得了巨大的进步,数学家和物理学家们建立模型以简化复杂的现象,从模型中抽取出各种特性,并通过试验来验证这些特性。这- 104 ------------------------ Page 117-----------------------些方法之所以可行——是因为模型中忽略的复杂度不是被研究现象的必要属性。当复杂度是本质特性时,这些方法就行不通了。上述软件特有的复杂度问题造成了很多经典的软件产品开发问题。由于复杂度,团队成员之间的沟通非常困难,导致了产品瑕疵、成本超支和进度延迟;由于复杂度,列举和理解所有可能的状态十分困难,影响了产品的可靠性;由于函数的复杂度,函数调用变得困难,导致程序难以使用;由于结构性复杂度,程序难以在不产生副作用的情况下用新函数扩充;由于结构性复杂度,造成很多安全机制状态上的不可见性。复杂度不仅仅导致技术上的困难,还引发了很多管理上的问题。它使全面理解问题变得困难,从而妨碍了概念上的完整性;它使所有离散出口难以寻找和控制;它引起了大量学习和理解上的负担,使开发慢慢演变成了一场灾难。一致性。并不是只有软件工程师才面对复杂问题。物理学家甚至在非常“基础”的级别上,面对异常复杂的事物。不过,物理学家坚信必定存在着某种通用原理,或者在夸克中,或者在统一场论中。爱因斯坦曾不断地重申自然界一定存在着简化的解释,因为上帝不是专横武断或反复无常的。软件工程师却无法从类似的信念中获得安慰,他必须控制的很多复杂度是随心所欲、毫无规则可言的,来自若干必须遵循的人为惯例和系统。它们随接口的不同而改变,随时间的推移而变化,而且,这些变化不是必需的,仅仅由于它们是不同的人——而非上帝——设计的结果。某些情况下,因为是开发最新的软件,所以它必须遵循各种接口。另一些情况下,软件的开发目标就是兼容性。在上述的所有情况中,很多复杂性来自保持与其他接口的一致,对软件的任何再设计,都无法简化这些复杂特性。可变性。软件实体经常会遭受到持续的变更压力。当然,建筑、汽车、计算机也是如此。不过,工业制造的产品在出厂之后不会经常地发生修改,它们会被后续模型所取代,或者必要更改会被整合到具有相同基本设计的后续产品系列。汽车的更改十分罕见,计算机的现场调整时有发生。然而,它们和软件的现场修改比起来,都要少很多。其中部分的原因是因为系统中的软件包含了很多功能,而功能是最容易感受变更压力的部分。另外的原因是因为软件可以很容易地进行修改——它是纯粹思维活动的产物,可以- 105 ------------------------ Page 118-----------------------无限扩展。日常生活中,建筑有可能发生变化,但众所周知,建筑修改的成本很高,从而打消了那些想提出修改的人的念头。所有成功的软件都会发生变更。现实工作中,经常发生两种情况。当人们发现软件很有用时,会在原有应用范围的边界,或者在超越边界的情况下使用软件。功能扩展的压力主要来自那些喜欢基本功能,又对软件提出了很多新用法的用户们。其次,软件一定是在某种计算机硬件平台上开发,成功软件的生命期通常比当初的计算机硬件平台要长。即使不是更换计算机,则有可能是换新型号的磁盘、显示器或者打印机。软件必须与各种新生事物保持一致。简言之,软件产品扎根于文化的母体中,如各种应用、用户、自然及社会规律、计算机硬件等等。后者持续不断地变化着,这些变化无情地强迫着软件随之变化。不可见性。软件是不可见的和无法可视化的。例如,几何抽象是强大的工具。建筑平面图能帮助建筑师和客户一起评估空间布局、进出的运输流量和各个角度的视觉效果。这样,矛盾变得突出,忽略的地方变得明显。同样,机械制图、化学分子模型尽管是抽象模型,但都起了相同的作用。总之,都可以通过几何抽象来捕获物理存在的几何特性。软件的客观存在不具有空间的形体特征。因此,没有已有的表达方式,就像陆地海洋有地图、硅片有膜片图、计算机有电路图一样。当我们试图用图形来描述软件结构时,我们发现它不仅仅包含一个,而是很多相互关联、重叠在一起的图形。这些图形可能描绘控制流程、数据流、依赖关系、时间序列、名字空间的相互关系等等。它们通常不是有较少层次的扁平结构。实际上,在上述结构上建立概念控制的一种方法是强制将关联分割,直到可以层2次化一个或多个图形 。除去软件结构上的限制和简化方面的进展,软件仍然保持着无法可视化的固有特性,从而剥夺了一些具有强大功能的概念工具的构造思路。这种缺憾不仅限制了个人的设计过程,也严重地阻碍了相互之间的交流。以往解决次要困难的一些突破如果回顾一下软件领域中取得的最富有成效的三次进步,我们会发现每一次都是解决