第四章 贵族专制、民主政治和系统设计

概念的完整性

在系统设计中,概念完整性应该是最重要的考虑因素。也就是说,为了反映一系列连贯的设计思路,宁可省略一些不规则的特性和改进,也不提倡独立和无法整合的系统,哪怕它们其实包含着许多很好的设计。

获得概念的完整性

  • 只有当这些功能说明节约下来的时间,比花费在学习、记忆和搜索手册上的时间要多时,易用性才会得到提高。

  • 对于给定级别的功能,能用最简洁和直接的方式来指明事情的系统是最好的。

  • 易用性实际上需要设计的一致性和概念上的完整性。

贵族专制统治和民主政治

  • 概念的完整性要求设计必须由一个人,或者非常少数互有默契的人员来实现。而进度压力却要求很多人员来开发系统。
    解决方法:
  1. 仔细地对设计方法和具体实现进行分工
  2. 前一章节所提到的新的组件编程开发团队的方法
  • 对于非常大型的项目,将设计方法、体系结构方面的工作与具体实现相分离是获得概念完整性的强有力方法。

  • 系统的体系结构指的是完整和详细的用户接口说明。必须仔细地将体系结构同实现区分开来。系统体系结构陈述的是发生了什么,而实现描述的是如何实现。

  • 架构师的工作产物的生命周期比实现人员的产物要长,并且架构师一直处在解决用户问题,实现用户利益的核心地位。若要得到系统概念上的完整性,必须有人控制这些概念,这是一种无需任何歉意的贵族专制统治。

在等待时,实现人员应该做什么

  • Blaauw指出,整个创造性活动包括了三个独立的阶段:体系结构、设计实现和物理实现。在实际情况中,它们往往可以同时开始和并发地进行。

第五章 画蛇添足

结构师的交互准则和机制

结构师应该:

  • 牢记是开发人员承担创造性和发明性的实现责任,所以结构师只能建议,而不能支配。
  • 时刻准备着为所指定的说明建议一种实现的方法,同样准备接受其他任何能达到目标的方法。
  • 对上述建议保持低调和不公开。
  • 准备放弃坚持所作的改进建议。

自律——开发第二个系统所带来的的后果

  • 一种普遍倾向是过分地设计第二个系统,向系统添加很多修饰功能和想法,它们曾在第一个系统中被小心谨慎地放在次要位置。

  • 项目经理如何避免开发第二个系统所引起的后果,从而避免画蛇添足?他必须坚持至少拥有两个系统以上开发经验结构师的决定。同时,保持对特殊诱惑的警觉,他可以不断提出正确的问题,确保原则上的概念和目标在详细设计中得到完整的体现。

第六章 贯彻执行

文档化的规则说明——手册

  • 规格说明修改的阶段化是很重要的,在进度表上应该有带日期的版本信息。
  • 手册不仅要描述包括所有界面在内的用户可见的一切,还要避免描述用户看不见的事物。
  • 手册描述的精确比生动更加重要。
  • 思路可能来源于多个人的想法,但如果想保持文字和产品之间的一致性,则必须由一个或两个人来完成将其结论转换成书面规格说明的工作。

形式化定义

  • 形式化定义是精确的,倾向完整。差异越明显,填补得越快。但是形式化定义的缺点是不易理解的。
  • 将来的规格说明应同时包括形式化和记叙性定义两种方式。
  • 如果同时具有两种方式,则必须以一种作为标准,另一种作为辅助描述,并照此明确地进行划分。
  • 形式化定义仅仅用于外部功能,说明它们是什么。
  • 在规定系统外部功能的同时,几乎所有的形式化定义均会用来描述和体现硬件系统或软件系统的某个设计实现。语法和规则的表达可以不需要具体的设计实现,但是特定的语义和意义通常会通过一段实现该功能的程序来定义。

会议和大会

  • 周例会:每周半天的会议,由所有的结构师,硬件和软件实现人员代表以及市场计划人员参与,由首席系统结构师主持。

  • 年度大会:典型的年度大会会持续两周。出席人员包括体系结构小组、编程人员、实现人员的结构代表、编程经理、市场和实现人员。

电话日志

  • 对于存有疑问的实现人员,应鼓励他们打电话询问相应的结构师,而不是一边自行猜测一边工作。
  • 一种有用的机制是由结构师保存电话日志。日志中,他记录了每一个问题和相应的回答。每周对若干结构师的日志进行合并,重新整理,并分发给用户和实现人员。

产品测试

  • 独立的产品测试机构/小组,规则规格说明检查机器和程序。
  • 在最后的分析中,用户是独立的监督人员,产品测试小组则是顾客的代理人,专门寻找缺陷。

第七章 为什么巴比伦塔会失败

巴比伦塔的管理教训

好的先决条件:清晰的目标、人力、材料、足够的时间、足够的技术、交流、组织。

大型编程项目中的交流

团队之间如何进行相互之间的交流沟通:

  • 非正式途径:电话沟通
  • 会议
  • 工作手册:在项目的开始阶段,准备正式的项目工作手册

项目工作手册

  • 项目工作手册是对项目必须产出的一些文档进行组织的一种结构。
  • 项目所有的文档都必须是该结构的一部分。这包括目的、外部规格说明、接口说明、技术标准、内部说明和管理备忘录。
  • 选择采用可以直接访问的文件。在文件中,记录修订日期记录和标记变更标识条。每个用户可以从一个显示终端来查阅。每日维护的变更小结以“后进先出”的方式保存,在一个固定的地方提供访问。

大型编程项目的组织架构

  • 团队组织的目标是为了减少必要的交流和协作量。
  • 减少交流的方法是人力划分和限定职责范围。
  • 传统的树状组织结构反映了权力的结构原理,不允许双重领导。
  • 组织中的交流是网状,而不是树状结构。因此所有的特殊组织机制都是为了进行调制,以克服树状组织结构中交流缺乏的困难。
  • 每个子项目具有两个领导角色:产品负责人、技术主管(或结构师)。
  • 两种角色的任意组合都可以是非常有效的:
    • 产品负责人和技术主管是同一个人
    • 产品负责人作为总指挥,技术主管充当其左右手
    • 技术主管作为总指挥,产品负责人充当其左右手

第八章 胸有成竹

  • 仅通过对编码部分时间的估计,然后乘以其他部分的相对系数,是无法得出对整项工作的精确估计的。
  • 构建独立小型程序的数据不适用于编程系统项目。

第九章 削足适履

作为成本的程序空间

  • 规模是软件系统产品用户成本中一个很大的组成部分,开发人员必须设置规模的目标,控制规模,考虑减小规模的方法。规模本身不是坏事,单不必要的规模是不可取的。

规模控制

  • 对于项目经理而言,规模控制即是技术工作的一部分,也是管理工作的一部分。
  • 仅对核心程序设定规模目标是不够的,必须把所有方面的规模都编入预算。
  • 应该制定总体规模的预算。和制定规模预算一样,应该制定后台存储访问的预算。
  • 在指明模块有多大的同时,确切定义模块的功能。
  • 为了满足模板,每个人都在局部优化自己的程序,很少会有人停下来,考虑一下对客户的整体影响。在整个实现的过程期间,系统结构师必须保持持续的警觉,确保连贯的系统完整性。
  • 培养开发人员从系统整体出发、面向用户的态度是软件编程管理人员最重要的技能。

空间技能

  • 空间预算的多少不喝控制并不能使程序规模减小,为实现这一目标,它还需要一些创造性和技能。
  1. 用功能换尺寸。设计人员必须决定用户可选项目的粗细程度。
  2. 空间与时间的折中。对于给定的功能,空间越多,速度越快。
  3. 编程需要技术积累,需要开发很多公共单元构件。每个项目需要自己的标准组件库。

数据的表现形式的编程的根本

  • 战略上突破常来自数据或表的重新表达,这是程序的核心所在。

第十章 提纲挈领

为什么要有正式的文档

  • 书面记录决策是必要的。只有记录下来,分歧才会明朗,矛盾才会突出。
  • 文档能够作为同其他人的沟通渠道。
  • 项目经理的文档可以作为数据基础和检查列表。通过周期性的回顾,他能清楚项目所处的状态,以及哪些需要重点进行更改和调整。

第十一章 未雨绸缪

唯一不变的就是变化本身

  • 一旦认识到试验性的系统必须被构建和丢弃,具有变更思想的重新设计将不可避免,那么,面对整个变化现象就是非常有用的。
  • 第一步是接受这样的事实:变化是与生俱来的,不是不合时宜和令人生厌的异常情况。
  • 目标上的一些变化无可避免,事先为它们做准备总比假设它们不会出现要好得多。不但目标上的变化不可避免,而且设计策略和技术上的变化也不可避免。抛弃原型概念本身就是对事实的接受——随着学习的过程更改设计。

为变更设计系统

  • 细致的模块化、可扩展的函数、精确完整的模块间接口设计、完备的文档
  • 调用队列、表驱动等技术
  • 高级语言和自文档技术
  • 每个产品都应该有数字版本号,每个版本都应该有自己的日程表和冻结日期,在此之后的变更属于下一个版本的范畴。

为变更计划组织架构

  • 程序员不愿意为设计书写文档,不仅仅是因为惰性,更多的是源于设计人员的踌躇——要为自己尝试性的设计决策进行辩解。
  • 只要管理人员和技术人才的天赋允许,老板必须对他们的能力培养基于极大的关注,使管理人员和技术人才具有互换性。特别是希望在技术和管理角色之间自由地分配人手的时候。

前进一步,后退一步

  • 事物在最初总是最好的。
  • 系统软件开发是减少混乱度(减少熵)的过程,所以它本身是处于亚稳态的。软件维护是提高混乱度(增加熵)的过程,即使是最熟练的软件维护工作,也只是放缓了系统退化到非稳态的进程。

第十二章 干将莫邪

  • 每个骨干人员都仔细保管自己工作生涯中收集的一套工具集,这些工具称为个人技能的直观证明。
  • 项目的关键问题是沟通,个性化的工具会妨碍而非促进沟通。
  • 开发和维护公共的通用编程工具的效率更高。
  • 建议为每个团队配备一名工具管理人员,这个角色管理所有通用工具,能指导他的客户和老板如何使用工具。同时,他还能编制老板需要的专业工具。
  • 项目经理需要考虑的工具:计算及设施、操作系统、语言、实用程序、调试辅助程序、测试用例生成工具、处理文档的字处理系统。

目标机器

  • 机器支持可以有效地划分成目标机器和辅助机器。
    • 目标机器:软件所服务的对象,程序必须在该机器上进行最后测试。
    • 辅助机器:在开发系统中提供服务的机器。

辅助机器和数据服务

  • 仿真装置:如果目标机器是新产品,则需要一个目标机器的逻辑仿真装置,作为辅助的调试平台
  • 编译器和汇编平台
  • 程序库和管理
  • 系统集成子库
  • 编程工具
  • 文档系统
  • 性能仿真装置

高级语言和交互式编程

  • 高级语言:生产率、调试速度
  • 交互式编程:多个级别上数据和程序的共享和保护,可延伸的库管理,以及用于终端用户之间协作的设施。

第十三章 整体部分

剔除bug的设计

  • 细致的功能定义、仔细的规格说明、规范化的功能描述说明以及这些方法的实施,大大减少了系统中必须查找的bug数量。
  • 测试规格说明:在编写任何代码之前,规格说明必须提交给外部测试小组,以详细地检查说明的完整性和明确性。
  • 自上而下的设计:体系结构设计、设计实现和物理编码实现。

    1. 通过比较粗略的任务定义和大概的解决方案得到主要结果。
    2. 对该定义和方案进行细致的检查,以判断结果和期望之间的差距。
    3. 将上述步骤的解决方案在更细的步骤中进行分解,每一项任务定义的精化变成了解决方案中算法的精化,还可能伴随着数据表达方式的精化。
  • 关键的地方和构建无bug程序的核心,是把系统的结构作为控制结构来考虑,而不是独立的分支语句。

构件单元测试

  • 本机调试
  • 内存转储
  • 快照
  • 交互式调试
  • 测试用例

系统集成调试

  • 使用经过调试的构件单元
  • 文档化的bug:构件单元所有缺陷已被发现,但还没有完全被修复,此时已经做好了系统调试的准备。在系统测试期间,这些缺陷可以被忽略。
  • 搭建充分的测试平台
    • 伪构件:由接口和伪数据或一些小的测试用例组成。该构件只是读入输入数据,校验数据格式,输出格式良好、但没有实际意义的有序数据以供使用。
    • 微缩文件:创建一个仅包含典型记录,但涵盖全部描述的小型文件。
    • 辅助程序:用来测试数据的发生器、特殊的打印输出和交叉引用表分析等。
  • 控制变更:
    • 有专人负责,控制和负责各个构建的纳元的变更或者版本之间的替换。
    • 必须存在系统的受控拷贝:一个是供构件单元测试使用的最终锁定版本,一个是测试版本的拷贝,用来记性缺陷的修复;以及一个开发库,其他人员可以在该拷贝上进行各自的程序开发工作,如修复和扩展自己的模块和子系统等。
    • 记录变更和差异:在一个日志中记录所有的变更,而在源代码中显著标记快速补丁和正式修改之间的区别。
  • 一次添加一个构件:乐观主义和惰性常常诱使我们破坏这个规则。注意,在添加每一个新构件后,都要用它们来测试子系统,原先的测试用例也要在新的现有系统上重新运行,对系统进行回归测试。
  • 阶段化、定期变更:小而频繁的变更阶段很容易变得不稳定。

第十四章 祸起萧墙

项目进度经常以一种难以察觉,但是残酷无情的方式慢慢落后。

里程碑还是沉重的负担

  • 根据严格的进度表来控制大型项目,第一步是制定进度表。进度表上的每一件事被称为里程碑。
  • 里程碑必须是具体的、特定的、可度量的事件,能够清晰定义,而不是模糊说明。

其他的部分反正会落后

  • PERT技术、关键路径技术:显示谁需要什么样的东西,谁位于关键路径上,在哪里发生滞后会影响最终的完成日期。

地毯的下面

  • 老板需要两种信息:需要采取行动计划方面的问题,用来进行分析的状态数据。
  • 减少角色的冲突:状态检查会议、问题-行动会议
  • 猛地拉开地毯:拥有了解状态真相的评审机制。

第十五章 另外一面

需要什么样的文档

  • 使用程序
    1. 目的:主要的功能?开发程序的原因?
    2. 环境:程序运行的机器、硬件配置、操作系统
    3. 范围:输入、输出的有效范围
    4. 实现功能和使用的算法
    5. “输入——输出”格式
    6. 操作指令
    7. 选项
    8. 运行时间
    9. 精度和校验
  • 验证程序
    1. 针对大多数常规数据对程序主要功能进行测试的用例
    2. 数量相对较少的合法数据测试用例
    3. 数量相对较少的非法数据测试用例
  • 修改程序
    1. 流程图或子系统的结构图
    2. 对所用算法的完整描述
    3. 对所有文件规划的解释
    4. 数据流处理的概要描述
    5. 初始设计中对已预见修改的讨论、特性等

流程图

  • 事实上,很多程序甚至不需要流程图,很少有程序需要一页纸以上的流程图
  • 流程图显示了程序的流程判断结构,一页纸的流程图称为表达程序结构、阶段或步骤的一种非常基本的图示。

自文档化的程序

  • 每个数据项包含两个文件都需要的所有信息,采用指定的键值来区别,并把它们组合到一个文件中。
  • 把文档整合到源程序,这种程序被称为自文档化。
  • 方法:
    1. 借助那些处于语言要求而必须存在的语句,来附加尽可能多的文档信息。如标签、声明语句和符号名称均可以作为工具,用来向读者表示尽可能多的意思。
    2. 尽可能地使用空格和一致的格式提高程序的可读性,表现从属和嵌套关系。
    3. 以段落注释的形式,向程序中插入必要的记叙性文字。
  • 技巧:
    1. 为每次运行使用单独的任务名称。维护一份日志,记录程序运行的目的、时间和结果。
    2. 使用包含版本号和能帮助记忆的程序名称。
    3. 在过程的注释中,包含记叙性的描述文字。
    4. 尽可能地为基本算法提供参考引用。
    5. 对程序语句进行分组和标记,以显示与算法描述文档中语句单元的一致性。
    6. 利用缩进表现结构和分组。
      • 自文档化方法的基本思想可以得到大规模的应用。

第十六章 没有银弹

  • 软件工程中的根本和次要问题
  • 在未来的十年内,无论是在技术还是管理方法上,都看不出有任何突破性的进步,能够保证在十年内大幅度地提高软件的生产率、可靠性和简洁性。

摘要

  • 软件活动包括:
    • 根本任务:打造构成抽象软件实体的复杂概念结构
    • 次要任务:使用编程语言表达这些抽象实体,在空间和时间限制下将它们映射成机器语言。
  • 关注软件任务中的必要活动,建议:
    • 仔细进行市场调研,避免开发已上市的产品
    • 在获取和制定软件需求时,将快速原型开发作为迭代计划的一部分
    • 有机地更新软件,随着系统的运行、使用和测试,逐渐添加越来越多的功能
    • 不断挑选和培养接触新生代的概念设计人员

根本困难

  • 这样的畸形并不是由于软件发展得太慢,而是因为计算机硬件发展得太快。
  • 作者认为,软件开发中困难的部分是规格说明、设计和测试这些概念上的结构,而不是对概念进行表达和对实现逼真程度进行验证。
  • 软件系统中无法规避的内在特性:复杂度、一致性、可变性和不可见性。
  • 复杂度【根本属性】
    • 没有任何两个软件部分是相同的,如果有相同的情况,会把它们合并成供调用的子函数。
    • 软件实体的扩展不仅仅是相同元素重复添加,而必须是不同元素实体的添加。
    • 复杂度导致沟通困难、难以列举程序所有困难的状态、难以调用函数、难以在不产生副作用的情况下用新函数扩充、造成安全机制状态上的不可见性,以及管理方面的一些问题。
  • 一致性
  • 可变性
  • 不可见性

以往解决次要困难的一些突破

  • 高级语言:抽象程序
  • 分时
  • 统一编程环境

银弹的希望

  • Ada和其他高级编程语言
  • 面向对象编程
  • 人工智能
  • 专家系统:包含归纳推论引擎和规则基础的程序,接收输入数据和假设条件,通过从基础规则推导逻辑结果,提出结论和建议,向用户展示前因后果,并解释最终的结果。推论引擎除了处理推理逻辑以外,通常还包括复杂逻辑或者概率数据和规则。
  • “自动”编程
  • 图形化编程
  • 程序验证
  • 环境和工具
  • 工作站

针对概念上根本问题的颇具前途的方法

  • 购买和自行开发
  • 需求精炼和快速原型
  • 增量开发
  • 卓越的设计人员