15~18¶
第15章 另外一面¶
不了解,就无法真正拥有。 ——歌德
咦,赐予我朴素的评论者吧,它们不会因过于深奥而让人困惑不解。 ——克雷布
程序与沟通的本质:计算机程序是从人传递到机器的信息,程序使用严格的语法和语义来清晰传达意图。除此之外,程序还需要通过其他方式向用户解释自己的"故事",即使是开发者自用的程序也需如此。由于记忆衰退,用户(包括作者自己)会逐渐失去对程序的理解,需要重拾细节。
而对于文档的重要性:对于公共应用程序,用户在时间和空间上远离作者,文档尤为重要。软件产品中,程序向用户展示的内容和提供给机器识别的内容同样重要,很多开发者对编写"简约"文档感到困难,并尝试向新人灌输文档重要性。
作者通过学习收银机推销员的经历得到了有效的教学方法,即 品改用"马车上搬收银机"的方法(亲自示范如何完成工作)时,效果相较于讲座来进行强调的方式显著提高。
需要什么样的文档¶
该部分内容系统性地概述了软件文档应具备的要素、验证方法以及支持后续修改所需的信息,强调了全面、精确文档对软件开发和维护的重要性。
1. 文档需求与分类
不同用户的文档需求:不同用户需要不同级别的文档,比如用户类型包括:仅偶尔使用程序的用户、依赖程序的用户、需要根据环境和目的修改程序的用户。
有效文档的特点:每个用户都需要描述程序的文字说明,而大多数文档缺乏足够的总结性内容,无法满足用户需求。比如作者提到的比喻即许多文档只描述了"树木、树皮和树叶",却没有提供"森林的图案",所以编写有用的文档需要稳妥、系统地进行。
程序文档应包含的九大要素:
- 目的:主要功能是什么?开发程序的原因是什么?
- 环境:程序运行的机器、硬件配置和操作系统
- 范围:输入的有效范围和允许显示的合法输出范围
- 实现功能和使用的算法:精确描述程序的功能
- "输入-输出"格式:必须确切和完整
- 操作指令:包括控制台及输出内容中正常和异常结束的行为
- 选项:用户可用的功能选项及选择方法
- 运行时间:在指定配置下解决特定规模问题所需时间
- 精度和校验:期望结果的精确程度及精度检测方法
文档的篇幅与时机:三四页纸通常足以容纳所有必要信息,表达需简洁精确,由于包含软件基本决策,大部分文档需在程序编制前完成。
2. 验证程序文档
测试用例的重要性:除了使用方法外,程序文档应附带程序正确运行的证明(测试用例),每份发布的程序拷贝应包含小测试用例,证明安装正确,且程序修改后需进行更全面的测试。
测试用例的三种类型:
-
常规数据测试用例:针对程序主要功能的测试(主要组成部分)
-
合法边界数据测试用例:检查输入范围边界值(最大值、最小值等)
-
非法数据测试用例:验证程序能正确处理无效输入并提供适当诊断
3. 修改程序文档
修改者需要的信息:程序调整或修复需要详细信息,这些信息通常记录在注释良好的列表中,同时修改者需要清晰的系统内部结构概述。系统内部结构概述应包含:
- 流程图或系统结构图
- 所用算法的完整描述或类似算法的参考资料
- 文件规划解释
- 数据流处理概述(从存储媒介获取数据到处理全过程)
- 设计中对可能修改的讨论,包括特性、功能回调、出口位置及原作者对可能修改的建议和隐藏缺陷的观察
流程图¶
该部分内容对流程图的实用性进行了批判性分析,指出了其在软件开发实践中的真实地位,以及为何许多开发者实际工作中并不严格依赖它:
1. 流程图的实用性与局限性
流程图的过度推崇:流程图是被过度推崇的一种程序文档,因为实际上很多程序甚至不需要流程图,极少有程序需要超过一页纸的流程图。
流程图的局限性:
- 流程图仅展示程序的流程判断结构,只是程序结构的一个方面
- 单张流程图能很好地显示程序的判断流向,但当流程图被分成几张时,整体结构的概览就被破坏
流程图的基本功能:一页纸的流程图是表达程序结构、阶段或步骤的基本图示工具,这种简单流程图容易绘制。绘制要求的简化:
- 不需要遵循ANSI流程图标准
- 只需使用方框、连线、编号等基本元素让流程图可理解即可
高级语言与流程图的关系:
- Goldstine和Von Neumann引入这种方法时,将框图作为一种高级语言,把难以理解的机器语言组合成易于理解的步骤,可以完全放弃流程图,使用文字列表表达内容
- 在现代系统化的高级语言中,程序分组已经完成,每个方框包含一条语句,方框本身变成了单调重复的练习,可以省略;使用块结构可以消除GO TO跳转语句,使所有标签消失
2. 流程图的实际应用状况
实践中的矛盾:
- 流程图被吹捧的程度远大于其实际作用
- 有经验的程序员很少在编写程序前绘制详尽流程图
- 在要求流程图的组织中,流程图通常是事后补充的
- 一些公司使用工具软件从代码生成流程图,称之为"不可缺少的设计工具"
作者的观点主要有以下几点:
- 这种普遍经验并非令人尴尬和悔恨的现象,而是对技术的良好评判
- 流程图的使用有其特定价值和局限性
- 引用《使徒行传》的比喻:"为什么让他们背负我们的祖先和我们自己都不能承担的重负呢?"
- 对于新的程序员和陈旧的流程图方法,不应强制使用不实用的工具
自文档化的程序¶
该部分内容详细阐述了如何将文档融入程序本身,创建自文档化程序的方法与优势,解决了传统文档与程序分离导致的同步和维护问题:
1. 文档与程序分离的问题与解决方案
文件同步问题:数据处理基本原理表明,试图维持不同文件之间的同步关系是费力且不理想的,更合理的方法是:每个数据项包含两个文件都需要的所有信息,用指定的键值区别,并组合到一个文件中。
而现实中的矛盾:在程序文档编制实践中往往违反了这一原则,典型做法是维护一份机器可读的程序和一系列包含描述性文字与流程图的人类可读文档,结果与预期相反:不同文件的数据保存导致不良后果,程序文档质量声名狼藉,维护低劣,程序变动无法及时准确反映在文档中。
自文档化方案(Self-Documenting)带来新的思路"合并文件",将文档整合到源程序中。该方法直接推动正确维护,确保编程用户能方便、及时地获取文档资料。
2. 自文档化的理论基础
与流程图的关系:在程序中包括流程图是一种突兀但可行的做法,考虑到流程图方法的落后和高级语言的普及,将程序和文档放在一起合理。将源程序作为文档载体,强制推行了一些约束,但使文档读者能重复利用源代码,现在是设计一套程序文档的全新方法的时候
文档是我们和前人都未能成功背负的重担,文档的基本目标是尽量减小文档负担。
3. 自文档化的具体方法
利用语言特性有以下几点:
- 利用语言要求必须存在的句子添加尽可能多的"文档"信息
- 标签、声明语句和符号名称可作为工具,向读者表达更多意思
- 尽可能使用空格和一致的格式提高程序可读性,表现从属和嵌套关系
- 以段落注释形式插入必要的描述性文字
文档的及时编写:
- 由于文档通过程序结构、命名和格式实现,所有这些必须在首次书写代码时完成
- 自文档化减少了附加工作,使工作遗漏的概率更小
自文档化示例与技巧:书中展示了一段自文档化的PL/I程序,并提供了12点技巧:
- 为每次运行使用独特任务名称,维护日志记录程序运行目的、时间和结果
- 使用包含版本号和助记符的程序名称
- 在过程(PROCEDURE)注释中包含描述性文字
- 为基本算法提供参考引用,指向更完备的处理方法
- 显示与算法书籍中传统算法的关系(更改/定制细化/重新表达)
- 声明所有变量,使用注释解释DECLARE语句
- 用标签标出初始化位置
- 对程序语句进行分组和标记,显示与算法描述文档中语句单元的一致性
- 利用缩进表现结构和分组
- 在程序列表中手工添加逻辑箭头,帮助调试和变更
- 使用行注释标记不清楚的事情(采用上述技术可减少注释长度和数量)
- 将多条语句放在一行或拆分单条语句,以符合逻辑思维和算法描述
4. 自文档化的争议与优势
作者对于对自文档化的质疑与回应:
- 关于源代码存储模式的变化:随着编程技术向在线源代码存储发展,自文档化变得更有价值
- 关于输入击键增加的争论:自文档化程序字符总数更少,且电子草稿不需重复打印,击键次数实际更少
结构图的文档应用:如只使用高级别结构图,并使用文档注释到源程序中可能更安全,因结构不常变化,该方法可作为注释合并到源程序中。
自文档化的应用范围:
- 自文档化基本思想可广泛应用
- 对空间和格式要求严格可能受限,但命名和结构化声明可发挥很大帮助
- 段落注释在任何语言中都是有效实践
高级语言的推动作用:
- 高级语言激发了自文档化方法,特别是在线系统的高级语言
- 无论批处理还是交互式,高级语言都显示强大功效
- 机器应为人服务,而非人为机器服务
- 从经济和以人为本的角度看,这种方法都合情合理
以下是 LLM 补充的思考和看法
你提供的《人月神话》第15章"另外一面"的总结非常全面,涵盖了Brooks关于文档和程序交流本质的重要观点。我来补充一些思考,帮助加深对这一章的理解:
1. 关于程序沟通本质的反思
Brooks提出"计算机程序是从人传递到机器的信息"这一观点,实际上揭示了软件开发的双重挑战:既要让机器理解,也要让人理解。这种双重沟通职责在现代软件工程中变得更加关键,特别是:
- 开发者流动性增强:在当今的软件行业,开发者更频繁地更换团队和项目,代码的自解释性变得尤为重要
- 协作开发的普及:分布式团队协作已成常态,代码不再是个人孤立作品,而是集体创作
2. 文档需求的现代演变
Brooks在40多年前提出的九大文档要素在今天仍然有效,但随着敏捷开发和持续集成的兴起,文档形式发生了变化:
- 文档即代码(Documentation as Code): 将文档视为代码的一部分,使用相同的版本控制和协作工具
- 动态文档: 自动生成的API文档、测试覆盖率报告等成为代码健康状态的实时指标
- 持续集成中的测试用例: Brooks提到的三类测试用例(常规、边界、非法)现在通常被整合到自动化测试套件中
3. 流程图的历史演变与今天的地位
Brooks对流程图的批判性分析可谓先见之明。在现代软件开发中:
- 图形化表达工具的多样化: 取代了传统流程图,如UML图、ER图、系统架构图等,各自有更专注的表达目的
- 代码即设计: 随着函数式编程、声明式编程的流行,代码本身的结构和组织方式已经能更清晰地传达设计意图
- 可视化工具的自动化: 现代IDE能自动生成类图、依赖图等,减轻了手工绘图的负担
4. 自文档化的现代实践
Brooks的自文档化理念在今天的体现更加丰富:
- 代码规范与风格指南: 如Google的代码风格指南、Airbnb的JavaScript风格指南等,强调了代码可读性
- 注释工具的成熟: 如JavaDoc、JSDoc等标准化注释格式,支持自动生成API文档
- 领域驱动设计(DDD): 强调代码应该反映业务领域语言,使代码本身就能讲述业务故事
- 测试驱动开发(TDD): 测试用例本身成为代码用途和行为的文档
5. Brooks观点的持久价值
尽管技术不断演进,Brooks关于"另外一面"的核心观点依然具有深远影响:
- 开发者的双重责任: 不仅要让程序运行,还要让程序被理解
- 文档与代码同步问题: 尽管形式变化,但保持一致性的挑战仍然存在
- 以人为本的工具选择: "机器应为人服务,而非人为机器服务"的理念,在今天的低代码、AI辅助编程等领域仍然指导着技术发展
6. 对当代软件开发的启示
- 知识传递的持续挑战: 代码是知识的载体,Brooks强调的"记忆衰退"问题在大型团队和长期维护项目中更加突出
- 简洁与全面的平衡: 在信息爆炸的时代,如何提供既简洁又全面的文档仍是一门艺术
- 可维护性的经济价值: 软件维护成本通常远超初期开发,Brooks的自文档化思想有助于降低这一长期成本
Brooks在本章中不仅提出了实用的方法论,更揭示了软件开发本质上是一种交流活动的深刻见解。这种见解穿越时间,在现代软件工程实践中仍然闪耀着智慧的光芒。
第16章 没有银弹——软件工程中的根本和次要问题¶
在未来的十年内,无论是在技术上还是在管理方法上,都看不出有任何突破性的进步,能够保证在十年内大幅度地提高软件的生产率、可靠性和简洁性。
摘要¶
改部分内容概括了第16章的核心论点,即软件开发中的根本挑战在于概念构建,而非实现细节,因此不存在能解决软件开发本质复杂性的"银弹":
1. 软件活动的根本与次要任务
软件活动的基本分类:
- 根本任务:打造构成抽象软件实体的复杂概念结构
- 次要任务:使用编程语言表达这些抽象实体,在空间和时间限制下将它们映射成机器语言
软件生产率的进步与障碍:近年来软件生产率的巨大进步主要来自对人为障碍的突破,如包括:硬件的限制、笨拙的编程语言和机器时间的缺乏等障碍,这些障碍使次要任务实施异常艰难,而相对根本任务而言,软件工程师在次要任务上花费了大量时间和精力。
关于生产率的重要观点:除非次要任务约占软件工作的9/10,即使将所有次要任务的时间缩减至零,也不会带来生产率数量级的提高。
未来关注的方向:现在应关注软件任务中的必要活动,关注那些和构造异常复杂的抽象概念结构有关的部分。
2. 作者的建议
作者提出四点具体建议:
-
市场调研:仔细进行市场调研,避免开发已上市的产品
-
快速原型开发:在获取和制定软件需求时,将快速原型开发作为迭代计划的一部分
-
增量式开发:有机地更新软件,随着系统的运行、使用和测试,逐渐添加更多功能
-
人才培养:不断挑选和培养杰出新生代的概念设计人员
介绍¶
该部分内容引入了"没有银弹"的核心隐喻,解释了为什么软件工程没有简单的解决方案,同时表明作者对渐进式进步的信心。通过人狼与银弹的比喻生动地说明了软件项目管理中期望与现实的差距:
1. 银弹的隐喻与软件项目的困境
在恐怖民间传说中,人狼是最可怕的怪物,能从熟悉的面孔变成可怕的怪物。对付人狼需要特殊的银弹,这一隐喻引出软件项目中类似的挑战。
软件项目的"人狼"特性:
- 大家熟悉的软件项目具有人狼特性(至少在非技术经理看来)
- 看似简单明了的项目可能变成:进度落后、超出预算、存在大量缺陷的怪物
- 行业呼唤"银弹"——可使软件成本像计算机硬件成本一样迅速降低的简单解决方案
银弹的缺失:
- 回顾近十年的情况,没有发现真正的"银弹"
- 没有任何技术或管理上的进展能独立地在生产率、可靠性或简洁性上带来数量级的提高
- 作者会在本章尝试分析软件问题的本质和多种候选银弹的特征,探索原因
2. 作者的立场与未来展望
作者并非悲观主义者。虽然没有看到令人惊异的突破,认为这种银弹实际上与软件的内在特性相悖,但仍有令人振奋的革新出现,比如以下提到:
- 渐进式进步的可能性:这些新方法的规范化、持续地开拓、发展和传播可能在将来带来生产率的数量级提高。"虽然没有通天大道,但是路就在脚下"
- 软件工程的哲学转变:解决管理灾难的第一步是将"巨无霸理论"替换成"微生物理论",每一步渐进式的诞生对抗了"一蹴而就"型解决方案的冲击,而软件工程进步是逐步取得的,需要伴随着辛勤的劳动和对规范化过程的持续努力
根本困难¶
该部分内容详尽分析了为什么软件开发本质上困难,认为复杂性、一致性要求、可变性和不可见性是软件固有的根本特性,这些特性导致了软件开发的内在困难,使得不可能出现能彻底解决软件困境的"银弹"。
1. 软件与硬件发展的对比
软件的固有瓶颈:软件的特性本身导致不可能出现像硬件工业中的突破性发明,因为无法像电子器件、晶体管、大规模集成一样提高软件的生产率、可靠性和简洁程度,甚至无法期望软件每两年有两倍的增长。
硬件vs软件发展速度:这种差异不是因为软件发展太慢,而是计算机硬件发展太快。因为从人类文明开始,没有任何其他产业技术能在30年内取得6个数量级的提高,计算机制造业从装配工业转变为流水线工业,带来了巨大进步。
困难分类方法可借鉴亚里士多德的分类:
- 根本的(essence)——软件特性中固有的困难
- 次要的(accident)——出现在目前生产中但非与生俱来的困难
接下来章节先关注根本问题。
2. 软件的四大根本特性
I. 复杂度
软件实体的复杂性:相互牵制关联的概念结构是软件实体必不可少部分,包括数据集合、关系、算法和功能调用,而这些元素本身抽象,但体现为不同表现形式下的相同概念构造,尽管如此,软件概念结构内容丰富且高度精细。
软件复杂度的特点:
- 规模上软件比人类创造的其他实体更复杂,没有两个软件部分完全相同
- 计算机本身比人类建造的大多数东西复杂,而软件系统状态又比计算机状态多若干个数量级
- 软件扩展不仅是相同元素重复添加,而是不同元素的添加,且元素以非线性递增方式交互
复杂度的根本性:
- 软件的复杂度是根本属性,不是次要因素
- 抽掉复杂度的软件描述也去掉了本质属性
- 数学和物理学可以建立简化模型是因为复杂度不是被研究现象的根本属性
复杂度导致的问题:
- 团队沟通困难,导致产品瑕疵、成本超支和进度延迟
- 影响可靠性:难以枚举和理解所有状态
- 函数调用困难,程序难用
- 扩展难度:难以在不产生副作用情况下添加新函数
- 安全机制状态不可见性
复杂度的管理影响:
- 难以全面理解问题,影响概念完整性
- 离散出口难以寻找和控制
- 学习和理解负担重,开发变成灾难
II. 一致性
- 不只软件工程师面对复杂度问题,物理学家也面对复杂事物,但是物理学家坚信存在通用原理简化解释,软件工程师却无法得到类似安慰
- 软件的复杂度是随心所欲、毫无规则的,来自必须遵循的人为惯例和系统,这些复杂性会随接口不同而改变,随时间推移而变化
- 由于软件需要与最新技术保持一致并遵循各种接口,其复杂性难以简化
III. 可变性
- 软件实体受持续变更压力,而与建筑、汽车、计算机等不同,工业制造产品出厂后不会频繁修改,会被后续模型替代
- 软件的现场修改比其他产品更常见的原因主要有系统中的软件包含很多功能,功能最容易受变更压力,同时软件容易修改——纯粹思维活动的产物,可无限扩展,在此基础上现实生活中建筑修改成本高,而软件修改相对简单
- 成功软件必然会发生变更,因为当发现软件有用时,会扩展应用范围或超越边界使用,功能扩展压力来自喜欢基本功能的用户
- 软件与硬件平台的关系是考虑到软件生命期通常比开发所用硬件平台长,所以必须与新硬件(如磁盘、显示器、打印机)保持一致
- 软件扎根于文化母体中,必须随应用、用户、自然及社会规律、硬件等变化而变化
IV. 不可见性
- 软件是不可见和无法可视化的
- 几何抽象是强大工具,但软件不具备空间形体特征,而尝试用图形描述软件结构时,发现是相互关联、重叠在一起的图形,这些图形代表控制流程、数据流、依赖关系、时间序列和名字空间等
- 在此结构上建立概念控制的方法是强制将关联分割,直到可以层次化图形
- 软件的不可视化特性剥夺了构造创意的可能,限制个人设计过程和思路交流
以往解决次要困难的⼀些突破¶
该部分内容分析了过去软件工程中的三大突破——高级语言、分时系统和统一编程环境,强调这些进步虽然重要,但仅解决了次要困难而非根本困难。作者认为这些突破虽然提高了生产率,但都有其固有限制,无法像"银弹"一样彻底解决软件开发的本质问题。这部分为下一节讨论期望达到的目标和限制做了铺垫:
过去突破的性质:回顾软件领域最富有成效的三次进步,发现每一次都解决了软件构建上的巨大困难,而这些都是次要困难,不是本质属性,也不是主要困难。可以对每一次进步进行外推,了解它们的固有限制。
1. 高级语言的突破
高级语言是软件生产率、可靠性和简洁性上最有力的突破,大多数观察者相信开发生产率至少提高了5倍,可靠性、简洁性和理解程度也大为提高,高级语言的具体贡献比如有:
- 减轻了一些次要的软件复杂度
- 抽象程序包含概念上的要素:操作、数据类型、流程和相互通讯,而机器语言程序关注位、寄存器、条件、分支、通道、磁盘等具体细节
- 高级语言体现了抽象程序需要的要素,避免了更低级的元素
- 消除了并非程序固有的整个级别的复杂度
当然高级语言也有相应的限制:
- 高级语言为所有编程人员提供抽象程序中想到的要素
- 我们正逐渐思考数据结构、数据类型和操作的复杂度,但速度非常缓慢
- 程序开发方法越来越接近用户的复杂度,对于较少使用复杂深奥语言要素的用户,高级语言在某种程度上增加而非减少了脑力劳动负担
2. 分时系统的贡献
- 分时在提高程序员生产率和产品质量方面起到很大作用,尽管不如高级语言显著
- 分时解决了完全不同的困难,保证了及时性,维持对复杂度的总体把握。批处理编程的长周转时间会导致遗忘细节末节,同时思维中断会导致重新思考的高昂时间代价,甚至失去对复杂系统的掌握
- 长周转时间与机器语言复杂度一样,是软件开发过程的次要困难,不是本质困难。所以分时作用也是有限的:主要缩短了系统响应时间,随着响应时间接近零,超过人类识别能力(约100毫秒),获得的好处也接近零
3. 统一编程环境的突破
第一个集成开发环境——Unix和Interlisp已得到广泛应用。
- 通过提供集成库、统一文件格式、管道和过滤器,解决了共同使用程序的次要困难
- 使概念性结构理论上的互相调用、提供输入和互相使用,在现实中非常容易实现
- 每个新工具可通过标准格式在任何程序中应用,激发整个工具库的开发
- 环境开发是当今软件工程研究的主要题目
银弹的希望¶
该部分评估了各种可能被视为"银弹"的技术,包括Ada、面向对象编程、人工智能、专家系统、自动编程、图形化编程、程序验证和开发环境。作者对每种技术的潜力进行了分析,认为虽然它们都有价值和贡献,但没有一种能真正解决软件开发的根本困难,无法成为彻底改变软件生产率的"银弹":
技术评估框架:作者讨论当今可能作为潜在银弹的最先进技术进步,评估这些技术是否解决根本问题或只解决次要困难,分析它们是提供真正创新还是仅做出改进。
1. Ada和高级编程语言
- Ada是20世纪80年代备受推崇的高级语言
- Ada不仅反映语言概念的突破性进展,还强调现代设计和模块化概念
- 采用抽象数据类型、层次结构的模块化理念,使用设计来承载需求,可能过于丰富
- 词汇子集可解决学习问题,硬件进展提供更高MIPS,减少编译成本
- 作者提出的预言:Ada在后续10年会产生重大变化,但不是因特别语言特性
- Ada最大贡献在于改变编程人员培训方式,促进现代软件设计技术培训
2. 面向对象编程
软件专业学生将面向对象编程视为最有希望的新技术。
- Mark Sherman提出两个重要概念区分:
- 抽象数据类型:通过名称、适当值和操作定义,不关心隐藏存储结构
- 层次化类型(class):允许通用接口定义被后续子类型精炼
- 这些概念消除开发过程中的非本质困难,允许设计人员表达内在特性
- 允许采用较高层次表现形式表达设计
- 但这些提高仅消除设计表达上的次要困难,不能解决软件内在设计复杂度,作者对面向对象编程作为"银弹"表示怀疑
3. 人工智能的评估
- 许多人期望AI给软件生产率和质量带来数量级增长,而作者不认同这一观点
- Parnas澄清了AI术语混淆,指出两种定义:
- AI-1:使用计算机解决以前只能通过人类智慧解决的问题
- AI-2:使用启发式或基于规则的特定编程技术
- 其中AI-1定义的问题:一旦了解程序运行方式,就不再被视为人工智能,而语音识别和图像识别技术与专家系统中应用技术不同
- 软件开发困难在于决定做什么,而非如何表达
4. 专家系统技术
- AI最广泛使用的是专家系统技术,专家系统包含归纳推论引擎和规则基础程序
- 接收输入数据和假设条件,通过基础规则推导结果。对解决相同问题,明显比传统程序算法先进
- 专家系统的优势在于推论引擎技术独立于应用程序,可有多种用途;同时基于应用的可变部分以统一风格编码;而且为规则基础开发提供工具,系统化应用程序复杂度
- Feigenbaum认为系统能力来自丰富知识库而非推导机制,作者认为最重要进步是应用复杂度与程序本身分离
其中专家系统在软件开发中的应用:
- 可通过多种途径应用:建议接口规则、测试策略、bug频率记录等
- 比如虚构测试顾问系统例子,基础级别类似诊断专家系统和飞行员检查表,也可根据更多复杂问题征兆提供精确建议,随着包含更多系统结构信息,推测和推荐测试更加准确
- 专家系统的挑战在于寻找简单方法从技术说明自动产生诊断规则,同时开发能清晰表达、理解来龙去脉的自我分析专家,还有提取专家知识并转化为基础规则(工作量是知识获取的两倍)
- 专家系统最强贡献是给缺乏经验的开发人员提供优秀开发者的经验和知识
5. "自动"编程
- 近40年来一直有相关预言和文字,Parnas指出这是魔咒术语,含义不完整
- 大多数技术说明是问题解决方法而非问题本身
- 数据发生器开发技术实用且常用于排序程序,微积分方程系统可直接描述问题
- 这类应用的特性主要有问题通过较少参数迅速描述,存在已知解决方案库,给定参数条件下有清晰规则选择具体解决方案
- 难以想象这种方法能推广到更广泛软件系统
6. 图形化编程:
- 软件工程博士论文中流行的主题,推测部分来自VLSI芯片设计类比
- 流程图作为理想设计介质,提供了很多功能强大程序
- 作者认为不会出现令人信服或激动的进步,因为流程图是非常差劲的软件结构表达方法,而且现在流程图复杂(一张图有多页,多连接点),且已被证明是不必要设计工具——程序员开发后绘制而非先绘制再开发,其他的如屏幕无法同时表现软件所有正式和详细范围,同时软件难以可视化,即使用图形表达也只表达某方面
- 对VLSI设计方法的类推是误导——芯片是二维对象的层次设计
7. 程序验证
- 现代编程大量工作是测试和修复bug,作者不认为存在魔法能在系统设计和代码阶段就消除bug
- 程序验证是先进概念,对安全操作系统内核应用重要,但验证要求大量工作,最终只有少量程序能真正验证
- 验证不意味零缺陷,数学验证也可能有错误,即使完美验证也只能建立满足技术说明的程序
- 软件开发困难部分在于完整一致说明的形成
8. 环境和工具
- 对更好编程环境的投入期望回报有限,环境和工具有价值但回报有限
- 人类本能是首先解决高回报问题:层次化文件系统、统一格式获取一致编程接口
- 智能化编辑器还未广泛应用,主要消除语法错误和简单语义错误
- 集成数据库是最大成果,跟踪开发细节、提供精确信息、维持团队最新状态
9. 工作站的局限
- 随着处理能力和内存提高,预期软件领域收益有限
- 现有运算速度已满足编制和文档需求 编译还需提高,但机器运算速度提高10倍后,思考活动将成为主要活动
- 欢迎更强大工作站但不期待魔术般提高
针对概念上根本问题的颇具前途的⽅法¶
该部分内容着重探讨了解决软件开发根本问题的有希望方法,尤其强调了三个关键方向:购买而非开发、快速原型开发和增量式开发,以及培养卓越设计人员。作者认为这些方法能够真正应对软件开发的本质困难,而不仅仅解决次要问题(虽无银弹,但有希望的方向):
软件开发的现实与希望:
- 虽然没有技术上的突破能预示软件领域可以获得像硬件领域一样的进展,但现实软件领域中已有大量优秀工作和平稳进步
- 作者提出生产率公式:任务时间 = Σ(频率)i × (时间)i,如果创造性部分占据大部分时间,那么仅仅表达概念的活动对生产率影响有限, 必须考虑解决软件必要困难的活动——准确表达复杂概念结构
I. 购买而非开发
购买vs自行开发:构建软件最彻底的解决方案是"不开发任何软件"
- 现实情况越来越好,市场上提供更多高质量软件产品,个人计算机的普及创造了广阔市场,各类软件工具和环境可随时购买
- 购买现有软件比重新开发更经济,软件市场是软件工程领域最深远的开发方向
- 软件成本是开发而非复制成本
- 即使在少数用户间共享,也能大幅减少每用户成本
- 使用n个拷贝将使开发人员生产率有效提高n倍
商用软件使用的变化:关键问题是可用性——能否在自己开发中使用商用软件包,其中的变化原因不是软件包本身变化(虽更通用和定制化),也不是应用变化,而关键是计算机硬件/软件成本比率变化。
现代用户和软件使用方式:
- 电子表格和数据库系统等工具出现晚但普及快,产生无数应用,以前使用COBOL或报表生成程序的应用被这些工具取代
- 现代用户使用各种应用程序但不编写代码,而对很多组织机构,最有效软件生产率策略是:生产线配备计算机和通用软件,同时配备熟练使用这些工具的人员,分配到各岗位
II. 快速原型开发和增量式开发
需求精炼和快速原型:
- 软件系统开发最困难部分是确定要构建什么,技术需求比概念需求更困难,包含所有人机界面与硬件和软件接口,而需求工作影响比其他部分更大,需求错误后果更严重
- 软件开发人员最重要职能是不断提取和细化需求,因为即使是客户通常不知道自己需要什么,不知道哪些问题必须回答
- 复杂软件系统是活动的、变化的系统,动态部分难以想象,客户与设计人员需多次广泛交流沟通,作为系统定义的一部分
快速原型开发的重要性:现代技术中最有希望解决根本问题的是快速原型化系统,软件原型模拟系统界面,演示主要功能。其中原型不必受相同硬件速度、规模或成本限制,原型展示应用功能主线,不处理异常情况。原型目的是明确实际概念结构,使客户可测试一致性和可用性。
增量开发模式:现在软件开发基于错误假设:先描述系统,竞标开发,实际开发,最后安装。作者认为这种假设根本不正确,许多软件问题来自这种谬误,而增量开发是解决方案,即对产品和原型不断反复开发和规格化,使用"增长"而非"搭建"系统的思路。软件开发类似其他建造过程,可使用规格说明、构件装配、脚手架等隐喻,传统"搭建系统"隐喻可能超出有效期,概念结构复杂难以事先精确说明。
向自然学习的方法:转向自然界,研究生物复杂性而非人造硬件,大脑本身极其复杂,其多样性、自我保护和自我更新能力丰富,秘密在于逐步发育成长而非一次性搭建,软件系统也应如此开发。
增量式开发的实践:Harlan Mills建议所有软件系统以增量方式开发
作者在软件工程试验班推动此方法,效果显著,甚至过去几十年没有任何方法能比较。
- 系统应能运行,即使未完成任何有用功能,只能调用假子系统
- 系统逐点完善,子系统轮流开发
- 增量开发要求自上而下设计,是一种自上而下增长的软件
- 方便跟踪,容易进行原型开发
- 新增功能或模块可从已规划系统有机增长
增量开发的心理效应:这种模式对士气推动令人震惊,比如简单系统运行时,开发过程每阶段都有可运行系统,新图形软件系统第一幅图案(哪怕简单长方形)出现等场景,让大型项目可获得与小型项目相同好处。
III. 培养卓越设计人员
卓越设计人员的培养:提高软件行业核心关键是人员培养
- 通过遴选优秀而非抽劣实践获得良好设计,优秀设计可传授,程序员可学习良好实践,美国战略是颂扬优秀现代实践(新课程、文献、SEI等)
- 软件开发是创造性过程,方法学能培养创造性思维但无法孕育创造过程
- 卓越与一般差异如同萨列里与莫扎特,卓越设计者产生的成果更快、更小、更简单、更干净,代价更少,甚至能说卓越与一般差异接近一个数量级
伟大产品的特点:尽管许多实用软件系统由团队设计,激动人心的产品往往来自少数伟大设计师。作者支持技术转移和开发技能传授,但最重要的是寻找培养卓越设计人员的途径。
组织对卓越设计人员的重视:
- 没有软件机构可忽视这项挑战,公司可能缺少好管理人员,但不会比缺少好设计人员更迫切。大多数机构投入时间精力寻找培养管理人员,却未对杰出设计人员投入同等资源
- 产品技术特色最终依赖设计人员,作者建议每个软件机构应明确杰出设计人员与卓越管理人员同等重要,提供相同培养和回报,包括办公室规模、设备、差旅费和人员支持
培养杰出设计人员的策略:
- 尽早有系统识别顶级设计人员,最佳通常不是最有经验者
- 为设计人员指派职业导师,负责技术成长和职业规划
- 为每个方面制定职业计划,包括与设计大师学习、高级教育、短期课程
- 提供设计人员相互交流和激励机会
以下是 LLM 补充的思考和看法
你提供的《人月神话》第16章"没有银弹"的总结非常全面和结构清晰。这一章可能是Brooks最有影响力的观点,甚至在软件工程领域产生了广泛而持久的影响。让我补充一些思考和观点来帮助加深理解:
深入思考与现代视角
1. 根本困难的持久性与现代印证
Brooks在1986年提出的"没有银弹"观点,预测未来十年不会有突破性进展。有趣的是,不仅那十年证实了他的预测,而且此后的几十年也在很大程度上印证了他的观点。这表明软件开发的根本困难具有超越时代的持久性:
- 复杂性依然存在:尽管有容器、微服务、无服务器架构等现代技术,但系统总体复杂性不降反增
- 一致性要求更严格:现代软件需要与更多系统和标准保持一致(云服务、APIs、多平台兼容等)
- 可变性压力更大:敏捷开发、DevOps、持续部署等方法论实际上是对可变性本质特征的适应
- 不可见性仍是挑战:尽管有更好的可视化工具,软件的多维关系仍难以完全可视化
2. 关于银弹隐喻的哲学思考
Brooks的银弹隐喻不仅是技术论断,更是一种哲学观点:
- 技术乐观主义的限制:硅谷文化常常宣扬"技术可以解决一切",而Brooks提醒我们某些困难是本质的,不可避免的
- 增量进步vs颠覆式创新:Brooks不否认进步,但认为软件领域的进步更可能是渐进式的,而非突破性的
- 简单解决方案的诱惑:人们天然渴望简单解决复杂问题的方法,Brooks冷静提醒我们接受复杂性的必要性
3. 银弹候选技术在现代的发展轨迹
Brooks评估的各种"银弹候选"在随后几十年有着有趣的发展轨迹:
- Ada语言:并未成为主流,但其思想影响了后续语言设计
- 面向对象编程:确实成为主流范式,但正如Brooks预测,并未带来生产率的数量级提升
- 人工智能:经历了起伏发展,如今以全新形式(深度学习、大型语言模型)重新兴起
- 专家系统:作为特定AI方向已相对式微,但其思想以更广泛形式存在
- 自动编程:以代码生成、低代码平台等形式演化
- 图形化编程:在特定领域取得成功(如LabVIEW),但未成为通用编程主流
- 程序验证:形式化方法在关键系统中应用增加,但仍未广泛应用
- 环境和工具:IDE、Git、云开发环境等确实提高了生产率,但正如Brooks预测,是渐进式而非革命性的
4. 针对根本问题的方法在现代的演化
Brooks提出的三个应对根本困难的方向在现代软件工程中得到了深化:
购买而非开发的演化: - 开源软件的崛起:开源生态系统使"购买而非开发"演化为"重用而非开发" - 软件即服务(SaaS)模式:进一步降低了获取软件的门槛 - API经济:通过API组合服务成为软件构建的主要方式 - 组件市场:从npm到Docker Hub,预制组件市场繁荣
快速原型和增量开发的现代实践: - 敏捷方法论:可视为Brooks增量开发理念的正式化和系统化 - DevOps与持续交付:实现了真正的持续增量演进 - MVP(最小可行产品)理念:与Brooks的原型思想高度一致 - A/B测试与特性标志:使增量开发更加精细和数据驱动
培养卓越设计人员的现代挑战: - 10倍工程师现象:研究继续证实顶尖开发者与普通开发者之间的巨大生产力差距 - 开发者体验的重视:优秀组织越来越关注创造环境让卓越设计者发挥才能 - 开源贡献作为能力信号:开源社区成为识别和培养卓越设计者的新途径 - 工程师文化的塑造:技术公司越来越注重建立能吸引和留住顶尖人才的文化
5. 新兴的"银弹候选"评估
如果Brooks今天重写这一章,他可能会评估这些现代"银弹候选":
- 大型语言模型与AI辅助编程:虽然提高了生产力,但主要解决的仍是次要困难(代码生成)而非根本困难(概念设计)
- 无代码/低代码平台:降低了特定领域应用的开发门槛,但在复杂系统面前仍有局限
- 函数式编程:提供了更好的抽象和并发处理,但仍然面临概念复杂性挑战
- 形式化方法与类型系统:有助于捕获某些错误,但无法解决需求定义的根本困难
- 微服务架构:改变了系统组织方式,但可能转移而非消除了复杂性
6. "没有银弹"思想的长期影响
Brooks的这一章产生了深远影响,包括:
- 对技术炒作的健康怀疑:培养了行业对"革命性"技术宣传的批判性思考
- 软件工程的现实主义:促进了对软件开发固有复杂性的认识和尊重
- 多元化方法的重视:认识到没有单一解决方案,需要多管齐下
- 人因素的重新重视:重申了优秀人才在软件开发中的核心作用
结论:从悲观到现实主义
Brooks的"没有银弹"论点常被误解为悲观。实际上,它代表的是一种成熟的现实主义——接受软件开发的本质复杂性,同时积极寻求渐进改进。
在现代软件工程实践中,我们看到的是对Brooks思想的印证:没有任何单一技术带来了生产率的数量级提升,但通过多种方法的综合应用,包括更好的工具、实践、方法论和人才培养,软件开发能力确实得到了显著提升。
正如Brooks所言,"虽然没有通天大道,但路就在脚下"。这种平衡的视角,既认识到困难的持久性,又不放弃持续改进的努力,可能是这一章最宝贵的智慧。
第17章 再论“没有银弹”¶
生死有命,富贵在天。 ——威廉三世,奥兰治亲王
任何人若想看到一件完美无瑕的作品,他所想的那种作品过去不存在,现在和将来也不会出现。 ——亚历山大·蒲柏,《批判论文》
人狼和其他恐怖传说¶
该部分内容主要介绍了第16章"没有银弹"文章的发表背景和当时引起的反响,以及杂志编辑在文章呈现上的独特处理方式。作者通过幽默的方式描述了这篇技术文章如何被赋予了"恐怖传说"的戏剧化外衣,并暗示了该文章在读者中引起的广泛关注。
存在着银弹——就在这里¶
该部分内容主要围绕作者对"没有银弹"一文发表后的反响进行总结,既包括支持的观点,也包括反对的声音。作者表示愿意接受有实际证据支持的银弹存在的观点,同时认为多数早期被推崇的方法并未实现戏剧性的效果提升。作者对读者提供的建设性反馈表示感谢,并承诺在本章中对这些观点进行分享和讨论。
含糊的表达会导致误解¶
该部分内容主要围绕术语澄清、对批评的回应,以及对软件系统复杂性本质的深入讨论。作者强调"次要"部分与"必要"部分的区别,并认为即使完全消除次要部分的问题也无法带来生产率的数量级提升。同时作者讨论了复杂性的层次性和可改善性,并展望未来可能出现更高层次的规律性理解。
1. 术语澄清:一些作者指出作者没有将某些观点表达清楚
- 关于"次要"(accident)一词:在第16章摘要中已尽可能清晰表达"没有银弹"的主要观点
- 由于术语"accident"(偶然)和"accidental"(次要)被混淆,这些术语来自亚里士多德的古老用法,"accidental"不是指"偶然发生"或"不幸的",而是更接近于"附带的"或"从属的"
2. 对软件构建的本质与次要部分的界定:作者并不贬低软件构建中的次要部分
- 作者认同塞耶斯看待创造性活动的观点,即所有创造性活动包括:(1)概念性结构的形式规格化;(2)使用现实的介质来实现;(3)在实际使用中与用户交互
- 在软件开发中,"必要"(essence)部分是构思概念上的结构,而"次要"(accident)部分是指它的实现过程
3. 软件开发工作的现实问题:
- 对作者而言关键论点的正确与否归结为一个现实问题:
- 软件开发中哪些部分与概念性结构的精确和有序表达相关
- 哪些部分是创造那些结构的思维活动
- 可根据缺陷是概念性的(如未识别异常)还是表达上的问题(如指针错误、内存分配错误)来划分寻找和修复工作
4. 次要部分在整体工作中的比重:
- 作者认为开发的次要或表达部分现在已占整个工作的一半或更少
- 由于这部分是现实问题,可以应用测量技术来研究,其观点可以通过更科学更新的估计来纠正
- 值得注意的是还没人公开发表或私下告知作者次要部分任务占工作的9/10
5. "没有银弹"的核心论点:如果开发的次要部分少于整个工作的9/10,即使完全消除(需要奇迹),也不会给生产率带来数量级提高。因此,必须着手解决开发的根本问题
6. 关于动机因素的研究:
- Bruce Blum 引用了 Herzberg、Mausner 和 Sayderman 等人1959年的研究
- 他们发现动机因素可以提高生产率,而环境和次要因素无论多么积极,都无法提高生产率,但在产生负面影响时会降低生产率
- "没有银弹"认为许多软件开发过程已消除以下负面因素:笨拙的机器语言、漫长的批处理周转时间、糟糕的工具以及内存限制
7. 对Brad Cox"这就是银弹"的回应:
- 1990年 Brad Cox 发表论文《这就是银弹》,指出可重用和可交互的构件开发是解决软件根本困难的方法
- 作者表示赞同这一方向,但Cox在两点上误解了"没有银弹":首先,他认为软件困难来自"编程人员缺乏构建当今软件的技术",而作者认为根本困难是固有的概念复杂性,无论何时何方法都存在;其次他误读文章,认为作者认为处理软件开发中的根本困难是没有希望的,这不是作者本意
8. 关于复杂性的本质:
- 作为本质上的困难,构思软件概念结构本身就有复杂性、一致性、可变性及不可见性的特点,但实际上每种困难产生的麻烦都是可以改善的
- 复杂性是层次化的,虽是最严重的内在困难,但并非所有复杂性都不可避免
- MYSIGMA的Lars Södahl指出,系统工作中遇到的大多数复杂性是组织结构上的失误,试图为现实建模反而隐藏而非解决混乱,而Northrop的Steve Lukasik认为组织机构上的复杂性可能受到策略调整的影响
9. 对复杂性的理解与展望:
- 作者受物理学训练,倾向用简单概念描述"复杂"事物
- 无法断定所有复杂事物都易于用有序规律表达,同样不能断定它们不能被如此表达
- 昨天的复杂性是今天的规律,就像分子无序性启发了气体动力学理论和热力学三大定律
- 作者相信有一天软件的"复杂性"将以某种更高级的规律性概念来理解(类似物理学的不变式)
10. 系统复杂性分析:
- 作者认为系统复杂性是无数细节的函数,这些细节必须精确而详细地说明
- 可借助通用规则或通过逐一阐述,但不仅是统计说明,仅靠有限人员不可能产生足够一致性来用通用规律精确描述
- 很多软件结构复杂性不完全因为与外部世界保持一致,而是实现本身的数据结构、算法和互联性等内在因素
- 在高级别开发(发展)软件时,使用他人成果或重用自己程序,能避免面对整个层次的复杂性
11. 应对复杂性的方法:"没有银弹"提出了全力解决复杂性问题的方法,这种方法在现实中取得显著进展。它倡导向软件系统增加必要的复杂性:
- 层次化,通过分层的模块或对象
- 增量化,使系统可以持续运行
Harel 的分析¶
这部分内容主要围绕 David Harel 对"没有银弹"的批评进行讨论,包括对悲观主义vs乐观主义的辩论,历史视角的正确性,以及关于软件可视化和建模方法的深入讨论。作者反驳了 Harel 关于文章过于悲观的观点,但对Harel提出的图形化思考软件的方法表示赞同。作者强调"没有银弹"的核心不是否定进步,而是对各种技术的合理评估以及对软件本质复杂性的认识。
- Harel的细致批评:
- David Harel在1992年的论文"批评银弹"(Biting the Silver Bullet)中对"没有银弹"进行了最细致的分析
- Harel同时阅读了"没有银弹"和1984年Parnas的文章"战略防卫系统的软件问题",认为它们"太过悲观"
- 他在论文"走向系统开发的光明未来"中展现更明亮的一面
- Cox与Harel一样认为"没有银弹"过于悲观,提出"如果从一个新视点去观察相同的事情,你会得到更加乐观的结论"
- 乐观主义vs悲观主义:
- 作者反驳:他的妻子、同事和编辑发现他犯乐观主义错误的几率远远大于悲观主义
- 作者的从业背景是程序员,乐观主义是该行业的职业病
- "没有银弹"明确指出:"我们看看近10年来的情况,没有银弹的踪迹......怀疑论者并不是悲观主义者......虽然没有通天大道,但是路就在脚下"
- 作者认为,如果1986年的很多创新能持续开拓和发展,它们共同作用能使生产率获得数量级提高
- 从1986年到1996年已过去10年,预言得到验证,这显示作者更多是乐观而非悲观
- 对悲观论调的检视:
- 作者质疑:如果"没有银弹"确有悲观,那存在什么问题?
- 爱因斯坦关于物体速度无法超过光速的论断是否"骇俗"?哥德尔关于某些事物无法计算的结论又如何?
- "没有银弹"认为"软件的特性本身导致了不大可能有任何的银弹"
- Turski的观点:
- Turski在IFIP大会上发表论文作为回应,指出:
- 在被误导的科学探索中,最悲惨的莫过于点金石研究(将一般金属变成金子)
- 这种从理想化想象中提取的精华,是人类纯粹信仰的体现
- 人们花费大量时间精力来认可和接受无法解决的问题
- 即使被证明不存在,寻找出路和希望能一劳永逸的愿望仍然强烈
- 将圆形变方的论文被发表,抑制脱发的药液被研制和出售,提高软件生产率的方法被提出并成功推销
- 人们太倾向于遮掩自己的乐观主义,太喜欢忽视真理的声音,而去听从万灵药销售者的诱惑
- 对"白日梦"的共识:
- 作者和Turski都坚持认为这个白日梦限制了发展,浪费了精力
- Harel识别的消极主题:
- Harel认为"没有银弹"中的消极来自三个主题:
- 根本和次要问题的清晰划分
- 独立地评价每个候选银弹
- 仅仅预言了10年,而不是期望在足够长时间内出现任何重大进步
- 第一个主题是整篇文章的主要观点,作者认为上述划分对理解软件开发困难原因至关重要,也是改进方向的明确指南
- 对各种技术的评价:
- 作者认为"没有银弹"并非否定各种技术,而是公平地独立评估各技术
- 各种技术被提出时都过分夸大自身效果,因此逐一评估是公平的
- 作者不是反对这些技术,而是反对期望它们有魔术般作用的观点
- Glass、Vessey和Conger在1992年论文中提供充分证据,指出对银弹的研究仍未结束
- 关于预言时限的选择:
- 选择10年而非40年作为预言期限,相当于承认我们无法预见10年后的事情
- 作者质疑:谁能在1975年预见到20世纪80年代的微型计算机革命?
- 各种银弹都宣称能立刻取得效果,没有银弹声称"向我秘方投资,10年后你将获得成功"
- 硬件性价比每10年提升百倍,这种比较虽不完全合适,但事实如此
- 作者相信下一个40年会有稳步发展,但以40年为代价获得数量级进展,很难被认为是不可思议的进步
- Harel的思想实验:
- Harel提出一种思想实验:假设"没有银弹"发表于1952年而非1986年,但论断相同
- 他使用反证法证明将根本和次要问题分开是不恰当的
- 作者认为这种观点站不住脚,理由如下:
- "没有银弹"一开始就声明,20世纪50年代程序开发中曾占主导地位的次要困难如今不存在,消除这些困难已获数量级改善
- 将辩论推回40年前不合理,1952年甚至难以想象开发的次要问题会占据开发工作主要部分
-
对Harel历史观点的质疑:
- Harel对20世纪50年代行业状态的描述不准确:
- 当时已是构建大型复杂系统的时代
- 程序员工作模式是执行有限算法工作的常规个人程序开发(现代编程语言约100-200行代码)
- 这些任务对今人恐怖,充满错误、故障和延误
- Harel接着描述传统小型个人程序中的错误、故障如何在接下来25年获得数量级改进
- 事实上,20世纪50年代并非小型个人程序的天下
- Harel对20世纪50年代行业状态的描述不准确:
-
Harel的银弹提案:
- Harel提出自己的银弹——"香草(Vanilla)框架"建模技术
- 文中没有详细描述该方法,但提供了一些论文和参考资料
- 该建模针对软件开发的根本困难(概念性要素的设计和调试),可能具有革命性
- 作者表示希望如此,Ken Brooks报告中提到这确实是一种颇有帮助的方法学
-
关于软件不可见性的讨论:
- Harel强烈主张软件的许多概念性要素本质是折扣的,这些关系可用空间/图形方式表达
- 使用适当的可视化图形可给工程师和程序员带来可视的成效
- 这种效果不仅解决次要问题,开发人员思考和探索的质量也得到改进
- 未来成功系统开发将围绕可视化图形的表达方式
- 首先使用"合适的"实体和关系形成概念,然后表达成一系列逐步完善的模型
- 模型用若干可视化语言的组合描述,需要多种语言组合,因为系统模型有若干方面内容
- 使用可视化表达方式的建模过程有些方面不会立刻出现改观,如变量和数据结构上的算法操作可能仍采用文字描述
-
作者与Harel观点的一致性:
- 作者认为软件要素不存在于三维空间中,不存在概念性设计到图形的简单映射
- 这需要多种图形,每种图形覆盖某个特定方面,有些方面无法用图形表达
- Harel使用图形辅助思考和设计的热情彻底感染了作者
- 作者举例:当询问程序员"下个11月在哪儿?"时,如觉得问题模糊,会问"告诉我你关于时间历法的模型"
- 优秀程序员有强空间想象能力,常有时间的几何模型,无需思考就能理解第一个问题,优秀程序员往往拥有高度个性化的模型
Jones 的观点——质量带来生产率¶
该部分内容主要讨论了Capers Jones提出的质量与生产率之间的关系观点。Jones认为应该关注质量,生产率会随之提高,而不是直接追求生产率。这一观点得到了作者的认同,但同时也指出了过度追求完美质量可能导致生产率下降的风险。此外,这部分内容还回顾了系统化软件开发方法和软件工程原理应用的历史背景和初衷,强调它们最初的目标是提高质量而非提高效率。
1. Jones的洞察力观点:"没有银弹"与当时很多文章一样,关注生产率——单位输入对应的软件产出,而Jones提出不同观点:"不。关注质量,生产率自然会随之提高"。
2. 质量管理与后期修复的关系:Jones认为,很多代价高昂的后续项目投入了大量时间和精力来寻找和修复规格说明、设计和实现上的错误,他所提供的数据显示了缺乏系统化质量控制和项目灾难之间的密切关系。
3. 质量与生产率的平衡:Boehm指出如果一味追求完美质量,生产率就会像IBM的航天飞机软件一样再次下降,所以需要在质量与生产率之间找到平衡点。
4. 系统化软件开发方法的目标:Coqui也提出类似主张系统化软件开发方法的发展是为了解决质量问题(特别是避免大型灾难),而不是出于生产率方面的考虑。
5. 工程原理应用的历史背景:20世纪70年代,在软件生产上应用工程原理的目标是提高软件产品的质量、可测试性、稳定性以及可预见性-—而不是软件产品的开发效率。
6. 软件工程驱动力:在软件生产上应用工程原理的驱动力是其担心无法控制的"艺术家们"可能导致巨大灾难,这些艺术家们往往对异常复杂系统的开发承担责任。
那么生产率的情况如何¶
该部分内容主要讨论了软件生产率的测量难度与实际情况,以及作者对"没有银弹"中预测的回顾。作者特别强调了大众市场软件包(现成软件)的重要性,认为这可能是软件工程领域最有意义的发展方向。作者承认自己低估了软件包的客户化程度及其重要性,并指出最终用户(而非开发人员)往往是不愿使用软件包的一方,因为担心失去特性功能,因此易于定制化成为软件包的重要卖点。
1. 生产率的测量与差异:生产率数据非常难以定义、测量和寻找。
2. 塑料薄膜包装的成品软件——购买而非开发:从学科角度看,不管与内部还是外部客户软件开发相比,大众市场软件都几乎是一个崭新的领域,软件包的销量达到百万或仅仅几千时,关键的支配性问题变成了质量、时机、产品性能和支持成本,而不再是对于客户系统异常关键的开发成本。
3. 创造性活动的强大工具:提高信息管理系统(MIS)编程人员生产率最戏剧化的方法是到计算机商店购买理应由他们开发的商业成品。与复杂大型产品工具相比,它们更像电锯、电钻和砂磨机,将它们组合成兼容互补的集合能带来巨大灵活性。其中某些工具会经常被使用,这种工具必须注重使用方便而非专业。
4. 客户对软件包的接受度:用户会察觉到软件包更加通用和易于定制化,这种感觉也使用户更容易接受软件包,对用户而言,易于定制是一个非常大的卖点。作者认为Selin是正确的——作者低估了软件包客户化的程度和它的重要性。
面向对象编程——这颗铜质子弹可以吗¶
该部分内容主要讨论了面向对象编程(OOP)作为一个潜在的"银弹"的特性、发展状况以及推广面临的挑战。作者认为面向对象编程有许多优势,包括强制的模块化、封装、继承和抽象数据类型,但其推广缓慢的主要原因是需要大量前期投入(主要是培训成本),而收益主要体现在后期维护和扩展阶段。作者引用了多位专家的意见,指出面向对象不仅是一种编程技术,更是一种设计方法,需要正确的教授和学习才能发挥价值。尽管推广缓慢,C++已在许多机构取代了C,证明面向对象技术的价值正逐渐被认可。
1. 使用零件构建的优势:本章开始描述提醒我们,当很多零件需要装配,而且每个零件可能很复杂时,如果它们的接口设计得流畅,大量丰富的结构就能快速地组合在一起。
2. 面向对象编程的核心特征:
- 强制的模块化和清晰的接口
- 强调封装,即外界无法看到组件的内部结构
- 强调维承和伴随着的层次化类结构以及虚函数
- 强调强抽象数据类型化,确保某种特定的数据类型只能由自身的相应函数来操作
3. 面向对象技术的发展:
- "没有银弹"发表后的9年中,对面向对象技术的期望总体增长
- 增长缓慢的原因是理论不够成熟
- 程序员经历了很多错综复杂混乱的应用,他们关注的是低层次的抽象,而不是高层次的抽象。他们开发了很多像链表或集合这样的类,而不是用户接口、射线束或有限元素模型。C++中帮助程序员避免错误的强类型检查,使得从小型事物中构建大型对象变得非常困难。
4. 软件开发的客户参与:
- Coggins回归到基本的软件问题,主张一种解决软件不能满足要求的方法是通过客户的参与和协作来提高脑力劳动的规模,他赞同自上而下的设计
- 如果设计大粒度的类,关注用户经接触的概念,则在进行设计时客户能够理解设计并提出问题,并且可以帮助设计测试用例
- 小规模的封装带来的好处比较少
5. Parnas对面向对象的看法:David Parnas的论文是面向对象概念的起源之一,他有不同的观点,因为OO和各种复杂语言的联系已经很紧密,人们并没有被告知OO是一种设计的方法,并向他们讲授设计方法和原理。大家只是被告知OO是一种特殊工具,而我们可以用任何工具写出优质或低劣的代码。如果人们使用这种语言做出不好的设计,没有从中获得多少价值,而一旦获得的价值太少,它就不会流行。
6. 投资与回报的关系:
- 面向对象技术包含了很多方法学上的进步,面向对象技术的前期投入很多
- 作者认为面向对象的优势是客观存在的,并非仅仅是推测
- 面向对象在整个开发周期中得到了应用,但真正的获益只有在后续开发、扩展和维护活动中才能体现出来
7. 收益与投资的平衡:为了预期中的、但有些不确定的收益,冒着风险投入资金是投资人每天在做的事情。作者认为极度的前期投入和收益的推后是使OO技术应用迟缓的最大原因。即使如此,在很多机构,C++仍无可争议地取代了C。
重用的情况怎样¶
该部分内容主要讨论了软件重用作为解决软件开发困难的一种方法的现状、挑战和经济性考量。作者指出重用是一种理想的解决方案,但实际实施面临多种障碍,包括消费者端的心理障碍(觉得重用比自己开发更麻烦)、缺乏标准化术语和文档支持、通用化构件的高成本等。作者通过引用多位专家的观察和数据,说明重用在数学软件等特定领域较为成功,但在一般软件开发中推广仍面临挑战。大型组织比小型组织更可能进行重用研究,但真正成功的重用案例仍然有限。作者认为可重用构件的开发成本约为一次性构件的三倍,这种高投入是阻碍重用广泛应用的重要因素。
- 重用作为解决软件困难的方法:
- 解决软件构建根本困难的最佳方法是不进行任何开发
- 软件包只是实现上述目标的方法之一,另外的方法是程序重用
- 实际上,易于重用类和通过继承方便地定制是面向对象技术最吸引人的地方
- 当某人在新的做事方法上获得一些经验,新模式就不再像一开始那么简单了
- 程序员的重用行为:
- 程序员经常重用他们自己的手头工作
- Jones提到:
- 大多数有丰富经验的程序员都拥有自己的私人开发库,使用大约30%的重用代码来开发软件
- 公司级别的重用能提供70%的重用代码量,但需要特殊的开发库和管理支持
- 公司级别的重用代码也意味着需要对项目中的变更进行统计和度量,以提高重用的可信程度
- 推广重用的建议:
- W. Huang建议用责任专家的矩阵管理来组织软件工厂,培养每个人重用自己代码的日常工作习惯
- JPL的Van Snyder指出数学软件领域有着软件重用的长期传统:
- 重用的障碍不在生产者一边,而在消费者一边
- 如果软件工程师觉得寻找能满足他需要的构件进行验证比自行编写的代价更加昂贵时,重复的构件就会产生
- 注意"觉得"和重新开发的真正投入无关
- 数学软件重用成功的原因:
- 数学软件上重用成功有两个原因:
- 它很晦涩难懂,每行代码需要大量高智商的输入
- 存在丰富的标准术语,也就是用数学来描述每个构件的功能
- 因此,重新开发数学软件构件的成本很高,而查找现有构件功能的成本很低
- 数学软件界存在长期传统,如专业期刊出版和搜集算法、用适度成本提供算法、出于商业考虑开发的高质量算法等,使查找和发现满足某人需要的构件比其他领域更容易
- 这些因素结合使数学软件的重用比重新开发更有吸引力
- 其他领域的重用情况:
- 同样的原因在其他领域也可能导致相同的重用现象,如为核反应、天气模型、海洋模型开发软件的代码编制工作
- 这些领域都是在相同的课本和标准概念下逐步发展起来的
- 公司级重用研究的现状:
- 有大量关于公司级重用的研究,但美国国内的实践相对较少,有报道称在国外重用较多
- Jones报告,在他公司的客户中:
- 所有拥有5000名以上程序员的机构都进行正式的重用研究
- 拥有500名以下程序员的组织,只有不到10%着手重用研究
- 报告指出最具有重用潜质的企业中,重用性研究(而非部署)"是活跃和积极的,即使没有完全成功"
- Ed Yourdon报告一家马尼拉的软件公司有200名程序员中有50名从事供他人使用的重用模块开发
- 他认为看到的个案非常少,重用研究主要是由于奖励结构等机构上的因素而非技术因素
- 大众市场软件包的影响:
- DeMarco告诉作者,大众市场软件包提供了数据库系统等通用功能,减轻了压力,减少了处在重用模块边缘的开发
- "不管怎样,重用的模块一般是一些通用功能"
- 关于重用的专家观点:
- Parnas认为重用是一件说起来容易、做起来难的事情,它需要良好的设计和卓越的文档
- 即使看到非常罕见的优秀设计,如果没有好的支持,也不会看到能重用的构件
- Ken Brooks指出通用化的困难:"即使在第五次使用我自己的个人用户界面库时,我还是在不断地进行修改"
- 重用的经济性:
- Jones报告开放市场上仅有少量的重用代码模块,价格在常规开发成本的1%~20%之间
- DeMarco表示对整个重用现象有些气馁,认为现有理论几乎是整体缺失的
- 时间证明了要使模块能够重用,其成本非常高
- Yourdon估计这个高昂的费用:"一个良好的经验法则是,可重用的构件的工作量是'一次性'构件的两倍"
- 作者在第1章的讨论中观察到真正产品化构件所需的成本,因此对工作量比率的估计是三倍
-
重用的发展前景:
- 虽然正在看到很多重用的形式和变化,但离期望的还较远,还有很多需要学习的地方
学习大量的词汇——对软件重用的一个可预见但还没有被预言的问题¶
该部分内容主要讨论了软件重用面临的一个重要挑战:需要学习大量的"词汇"(即编程组件、类库、函数等)。作者将这个问题类比为语言学习,指出随着抽象级别的提高,所需掌握的基本元素数量也相应增加。现代类库可能包含数千个成员,每个成员又有多个参数和复杂的功能,这对开发者提出了巨大的学习挑战。
不过,作者也指出这个挑战并非不可克服,因为人类在学习自然语言时已经能够掌握数以万计的词汇及其复杂的语法和语义。因此,解决软件重用的词汇学习问题,应该借鉴人类学习语言的方法:在上下文中学习(通过完整的例子而非孤立的组件)、通过使用逐渐掌握语法和语义、按照语义分类组织知识。这些观察为改进软件重用的教学和文档方法提供了启示。
- 语言复杂性的递增:
- 思索的层次越高,所需要处理的基本思考要素也就越多
- 因此,编程语言比机器语言更加复杂,而自然语言的复杂程度更高
- 高级语言有更广泛的词汇量、更复杂的语法以及更加丰富的语义
- 软件重用中的词汇学习问题:
- 作为一个科目,我们并没有就程序重用的实际情况仔细考虑它蕴涵的意义
- 为了提高质量和生产率,我们需要通过经过调试的大型要素来构建系统
- 在编程语言中,这些函数的级别远远高于语句
- 无论采用对象类库还是程序库的方式,我们必须面对编程词汇规模彻底扩大的事实
- 对于重用,词汇学习并不是思维障碍中的一小部分
- 现实中类库的规模:
- 现在人们拥有成员超过3,000个的类库
- 很多对象需要10~20个参数和可选变量的说明
- 如果想获得所有潜在的重用,任何使用类库编程的人员必须学习其成员的语法(外部接口)和语义(详细的功能行为)
- 学习挑战的规模:
- 这项工作并不是没有希望的
- 一般人日常使用的词汇超过了10,000个,受过教育的人远多于这个数目
- 我们自然而然地学习着语法和非常微妙的语义
- 我们可以正确地区分巨大、大、辽阔、大量和庞大
- 人们不会说"庞大的沙漠"或"辽阔的大象"
- 研究合适的学习方法:
- 对软件重用问题,我们需要研究适当的学问,了解人们如何拥有语言
- 一些经验教训是显而易见的:
- 人们在上下文中学习,因此我们需要出版一些复合产品的例子,而不仅仅是零部件的库
- 人们只会记忆普通词,而语法和语义是在上下文中通过使用逐渐地学习的
- 人们根据语义的分类对词汇组合规则进行分组,而不是通过比较对象子集
子弹的本质——形势没有发生改变¶
这部分内容是整章的总结,作者重申了他在原《没有银弹》一文中的核心观点:软件开发的复杂性是内在的,不太可能有"银弹"式的突破性解决方案出现。作者采取现实主义态度,认为这种认识虽然可能让人感到沮丧,但实际上是"清新的空气",因为它让我们能够将注意力从不切实际的期望转向可行的、渐进式的改进。
作者强调,软件开发行业应该专注于研究和分析可能的革命性进展,而不是被动地等待或希望奇迹出现。通过专注于实际可行的进步,软件生产率可以获得稳定的提升,这比追逐不太可能实现的突破性解决方案更有价值。
这一结论实际上表明,在《没有银弹》发表9年后,作者仍然坚持其原始论点,认为软件开发的根本复杂性没有发生变化,仍然需要务实的态度和渐进式的改进方法。
以下是 LLM 补充的思考和看法
你提供的《人月神话》第17章"再论'没有银弹'"的总结非常全面和详实。这一章是Brooks在原始"没有银弹"论文发表九年后的反思和深化,特别有价值。下面我来补充一些思考和观点,帮助你更深入理解这一章的内涵:
1. 预言的自证与历史视角
Brooks在1986年提出"没有银弹"的预言,然后在1995年回顾并坚持这一观点,这种"自我检验"极为罕见且有价值:
- 预言的勇气与诚实:多数技术预言者会避免回顾自己过去的预测,而Brooks主动检验自己的预言并公开讨论,体现了科学的诚实态度
- 九年时间框架的意义:九年是足够长的时间窗口,可以观察到技术趋势,但又不会太长以至于完全变成新的技术环境
- 现代启示:这种自我检验的方法值得今天的技术预测者学习,尤其是在AI、量子计算等快速发展的领域
2. 乐观主义与悲观主义的辩证关系
Brooks被批评为"悲观主义者",但这一章揭示了更复杂的立场:
- 工程现实主义而非悲观主义:Brooks的立场更准确地应被描述为工程现实主义—接受限制但在限制内寻找最佳解决方案
- 乐观主义的实际范围:Brooks自认为容易犯"乐观主义错误",这反映了软件工程师普遍的心理偏好—倾向于相信技术可以解决一切问题
- 建设性悲观主义的价值:在某些情况下,识别硬限制实际上是进步的催化剂,因为它迫使我们寻找替代方案和变通解决办法
3. 质量与生产率的深层关系
Brooks引用Jones的观点"关注质量,生产率自然会随之提高",这一洞见在现代软件开发中得到了多方面验证:
- 技术债务的概念:现代软件开发认识到,追求短期生产率往往会积累"技术债务",最终降低长期生产率
- DevOps实践的证明:DevOps通过自动化测试、持续集成等质量保障手段,实际上提高了整体交付速度
- 外部质量与内部质量:值得区分两种质量概念—外部质量(用户可见的功能完整性)和内部质量(代码可维护性),后者尤其影响长期生产率
- 质量曲线的非线性:Boehm提醒我们质量与生产率的关系不是线性的,存在一个最优点,过度追求质量(如航天飞机软件)同样会降低生产率
4. 重用经济学的深层思考
Brooks对软件重用的分析包含了深刻的经济学思考:
- 供需不平衡:重用面临的关键问题是供应方和需求方激励机制的不平衡
- 投资回报周期:构建可重用组件的三倍成本投入需要足够多的重用次数才能产生正回报
- 重用的组织挑战:大型组织vs小型组织在重用研究上的差异,反映了规模经济效应
- 市场机制的作用:软件包市场提供了一种替代内部重用的机制,效率可能更高
- 现代平台的变革:今天的npm、PyPI等包管理系统实际上解决了Brooks所描述的部分发现和分发问题
5. "词汇学习问题"的先见之明
Brooks对"学习大量词汇"的讨论,可能是这一章最有先见之明的部分:
- API经济的挑战:在现代API经济中,开发者需要学习数十甚至上百个API,每个都有复杂的"词汇"
- 认知负载的现实限制:人脑处理信息的能力有限,这是软件复杂性的一个根本约束
- 现代学习方法的演进:Stack Overflow、GitHub、交互式文档等,可视为对Brooks提出的"上下文中学习"建议的响应
- 语义网络的类比:人类记忆是通过语义联系组织的,现代API设计也越来越注重一致的概念模型和命名约定
6. 面向对象编程的长期影响
Brooks对OOP的分析既有准确之处,也有需要重新评估的点:
- OOP的主流化:如Brooks所预见,C++确实在许多机构取代了C,而今天面向对象理念已融入大多数主流语言
- 投资回报周期的演变:随着开发者教育的普及,OOP的前期投入成本已大幅降低
- 对OOP局限性的认识:现代软件工程已经认识到纯OOP的局限,混合范式(函数式+OOP)成为新趋势
- 设计vs语言特性:Parnas强调OOP是设计方法而非语言特性的观点,在微服务等现代架构中得到了呼应
7. 当代"银弹候选"的思考
通过Brooks的分析框架,我们可以评估一些当代的"银弹候选":
AI辅助编程(如GitHub Copilot):
- 主要解决次要困难(代码编写)而非根本困难(概念设计)
- 可能提高单个开发者生产率,但软件工程是团队活动
- 不解决需求理解和系统设计的复杂性
低代码/无代码平台:
- 针对特定领域的"银弹",而非通用解决方案
- 将复杂性从编码转移到配置和集成
- 仍然需要理解问题领域的概念模型
微服务架构:
- 将系统复杂性分解为更小、更可管理的部分
- 引入了新的分布式系统复杂性
- 解决了某些可变性问题,但增加了一致性维护成本
8. 与当代软件危机的联系
Brooks的分析与今天的软件危机有着深刻联系:
- 软件供应链安全:重用和依赖的增加带来了新的安全挑战
- 系统复杂性的爆炸:随着系统规模增长,复杂性以超线性方式增加
- 跨学科知识需求:现代软件工程师需要掌握心理学、设计、业务领域知识等,复杂性来源更多元
结论:持久的智慧
Brooks的"再论'没有银弹'"展示了罕见的智慧持久性。在快速变化的技术领域,能够经受九年检验的分析实属不易。今天再读这一章,许多洞见仍然适用,甚至在云计算、AI和移动互联网的时代更加相关。
真正的价值在于Brooks教会我们面对复杂性的态度:既不逃避它,也不期待魔法般的解决方案,而是通过务实的工程方法、渐进式改进和对人因素的关注,不断推动软件工程的边界。如他所说:"虽然没有通天大道,但路就在脚下。"
第18章 《人月神话》中的观点:是与非¶
我们理解也好,不理解也好,描述都应该简短精练。 --塞缪尔·勃特勒,《休迪布拉斯》
第1章 焦油坑¶
-
编程系统产品(Programming Systems Product)开发的工作量是供个人使用的、独立开发的构件程序的 9 倍。我估计软件构件产品化引起了 3 倍工作量,将软件构件整合成完整系统所需要的设计、集成和测试又强加了 3 倍的工作量,这些高成本的构件在根本上是相互独立的。
-
编程行业"满足我们内心深处的创造渴望和愉悦所有人的共有情感",其提供了五种乐趣:
-
创建事物的快乐;
-
开发对其他人有用的东西的乐趣;
-
将可以活动、相互啮合的零部件组装成类似迷宫的东西,这个过程所体现出令人神魂颠倒的魅力;
-
面对不重复的任务,不断学习的乐趣;
-
工作在如此易于驾驭的介质上的乐趣——纯粹的思维活动——其存在、移动和运转方式完全不同于实际物体。
- 同样,这个行业具有一些内在固有的苦恼:
-
将做事方式调整到追求完美是学习编程的最困难部分;
-
由其他人来设定目标,并且必须依靠自己无法控制的事物(特别是程序);权威不等同于责任;
-
实际情况看起来要比这点好一些;真正的权威来自于每次任务的完成;
-
任何创造性活动都伴随着枯燥艰苦的劳动,编程也不例外;
-
人们通常期望项目在接近结束时,软件项目能收敛得快一些,然而,情况却是越接近完成,收敛得越慢;
-
产品在完成前总面临着陈旧过时的威胁;只有实际需要时,才会用到最新的设想。
第2章 人月神话¶
-
缺乏合理的时间进度是造成项目滞后的最主要原因,它比其他所有因素的总和影响还大。
-
良好的烹饪需要时间,某些任务无法在不损害结果的情况下加快速度。
-
所有的编程人员都是乐观主义者:"一切都将运作良好"。
-
由于编程人员通过纯粹的思维活动来开发,我们期待在实现过程中不会碰到困难。
-
但是,我们的构思本身是有缺陷的,因此总会有 bug。
-
围绕着成本核算的估计技术,混淆了工作量和项目进展。人月是危险和带有欺骗性的神话,因为它暗示人员数量和时间是可以相互替换的。
-
在若干人员中分解任务会引发额外的沟通工作量——培训和相互沟通。
-
关于进度安排,我的经验是为 ⅓ 计划、⅙ 编码、¼ 构件测试以及 ¼ 系统测试。
-
作为一门学科,我们缺乏数据估计。
-
我们对自己的估计技术不确定,因此在管理和客户的压力下,我们常常缺乏坚持的勇气。
-
Brooks 法则:为进度落后的项目增加人手,只会使进度更加落后。
-
向软件项目中增派人手从三个方面增加了项目必要的总体工作量:任务重新分配本身和所造成的工作中断;培训新人员;额外的相互沟通。
第3章 外科手术队伍¶
- 同样有两年经验而且在受到同样培训的情况下,优秀的专业程序员的生产率是较差的程序员的 10 倍(Sackman、Erickson 和 Grant)。
感谢您的反馈和补充要求。以下是根据您的要求改进后的内容:
-
Sackman、Erickson 和 Grant 的数据显示,经验和实际表现之间没有相互联系,我怀疑这种现象是否普遍成立。
-
小型、精干队伍是最好的——思绪尽可能少。
-
两个人的团队,其中一个是领导者,常常是最佳的人员使用方法(留意一下上帝对婚姻的设计)。
-
对于真正意义上的大型系统,小型精干的队伍太慢了。
-
实际上,绝大多数大型编程系统的经验显示,一拥而上的开发方法是高成本、速度缓慢、低效的,开发出的产品无法进行概念上的集成。
-
一位首席程序员、类似于外科手术队伍的团队架构提供了一种方法——既能获得由少数头脑产生的产品完整性,又能得到多位协助人员的总体生产率,还彻底地减少了沟通的工作量。
第4章 贵族专制、民主政治和系统设计¶
-
"概念完整性是系统设计中最重要的考虑因素。"
-
"功能与理解上的复杂程度的比值才是系统设计的最终测试标准",而不仅仅是丰富的功能。(该比值是对易用性的一种测量,由简单和复杂应用共同验证。)
-
为了获得概念完整性,设计必须由一个人或者具有共识的小型团队来完成。
-
"对于非常大型的项目,将体系结构方面的工作与具体实现相分离是获得概念完整性的强有力方法。"(其同样适用于小型项目。)
-
"如果要得到系统概念上的完整性,就必须有人控制这些概念。这实际上是一种无需任何歉意的贵族专制统治。"
-
纪律、规则对行业是有益的。外部的体系结构规定实际上是增强,而不是限制实现小组的创造性。
-
概念上统一的系统能更快地开发和测试。
-
体系结构(architecture)、设计实现(implementation)、物理实现(realization)的许多工作可以并行。(软件和硬件设计同样可以并行。)
第5章 画蛇添足¶
-
尽早交流和持续沟通能使结构师有较好的成本意识,使开发人员获得对设计的信心,并且不会混淆各自的责任分工。
-
结构师如何成功地影响实现:
-
牢记是开发人员承担创造性的实现责任;结构师只能提出建议。
-
时刻准备着为所指定的说明建议一种实现的方法,准备接受任何其他可行的方法。
-
对上述建议保持低调和平静。
-
准备对所建议的改进放弃坚持。
-
听取开发人员在体系结构上改进的建议。
-
第二个系统是人们所设计的最危险的系统,通常的倾向是过分地进行设计。
-
OS/360 是典型的画蛇添足的例子。(Windows NT 似乎是 20 世纪90年代的例子。)
-
为功能分配一个字节和微秒的优先权值是一个很有价值的规范化方法。
第6章 贯彻执行¶
-
即使是大型的设计团队,设计结果也必须由一个或两个人来完成,以确保这些决定是一致的。
-
必须明确定义体系结构中与先前定义不同的地方,重新定义的详细程度应该与原先的说明一致。
-
出于精确性的考虑,我们需要形式化地设计定义;同样,我们需要记叙性定义来加深理解。
-
必须采用形式化定义和记叙性定义中的一种作为标准,另一种作为辅助措施;它们都可以作为表达的标准。
-
设计实现,包括模拟仿真,可以充当一种体系结构的定义;这种方法有一些严重的缺点。
-
直接整合是一种强制推行软件的结构性标准的方法。(硬件上也是如此——考虑内建在 ROM 中的 Mac WIMP 接口。)
-
"如果起初至少有两种以上的实现,(体系结构)定义会更加整洁和规范。"
-
允许体系结构师对实现人员的询问做出电话应答解释是非常重要的,并且必须进行日志记录和整理发布。(电子邮件是现在可选的介质。)
-
"项目经理最好的朋友就是他每天要面对的对手——独立的产品测试机构/小组。"
第7章 为什么巴比伦塔会失败¶
- 巴比伦塔项目的失败是因为缺乏交流以及交流的结果——组织。
交流
-
"因为左手不知道右手在做什么,从而进度灾难、功能的不合理和系统缺陷纷纷出现。" 由于存在对其他人的各种假设,团队成员之间的理解开始出现偏差。
-
团队应该以尽可能多的方式进行相互之间的交流:非正式地进行简要技术陈述的常规项目会议,共享的正式项目工作手册以及通过电子邮件。
项目工作手册
-
项目工作手册"不是独立的一篇文档,它是对项目必须产生的一系列文档进行组织的一种结构"。
-
"项目所有的文档都必须是该[工作手册]结构的一部分。"
-
需要尽早和仔细地设计工作手册结构。
-
事先制定良好结构的工作手册"可以将后来书写的文字放置在合适的章节中",并且可以提高产品手册的质量。
-
"每一个团队成员应该了解所有的材料(工作手册)。"(我现在想说的是,每个团队成员应该能够看到所有材料,网页即可满足要求。)
-
实时更新是至关重要的。
-
工作手册的使用者应该将注意力集中在上次阅读后的变更以及关于这些变更重要性的评述上。
-
OS/360 项目工作手册开始采用的是纸介质,后来换成了微缩胶片。
-
今天(即使在 1975 年),共享的电子手册是能达到所有这些目标的、更好的、更加低廉的、更加简单的机制。
-
仍然需要用变更条和修订日期[或具备同等功能的方法]来标记文字;仍然需要后进先出(LIFO)的电子化变更小结。
-
Parnas 强烈地认为使每个人看到每件事的目标是完全错误的;各个部分应该被封装,从而没有人需要或者被允许看到其他部分的内部结构,只需要了解接口。
-
Parnas 的建议的确是夹缝的处方。(Parnas 让我认可了该观点,使我彻底地改变了想法。)
组织架构
-
团队组织的目标是为了减少必要的交流和协作量。
-
为了减少交流,组织结构包括了人力划分(division of labor)和限定职责范围(specialization of function)。
-
传统的树状组织结构反映了权力的结构原理——不允许双重领导。
-
组织中的交流是网状,而不是树状结构,因此所有的特殊组织机制(往往体现为组织结构图中的虚线部分)都是为了进行调整,以克服树状组织结构中交流缺乏的困难。
-
每个子项目具有两个领导角色——产品负责人,技术主管或结构师。这两个角色的职能有很大的区别,需要不同的技能。
-
两种角色的任意组合都可以是非常有效的:
-
产品负责人和技术主管是同一个人;
-
产品负责人作为总指挥,技术主管充当其左右手;
-
技术主管作为总指挥,产品负责人充当其左右手。
第8章 胸有成竹¶
-
仅仅通过对编码部分时间的估计,然后乘以其他部分的相对系数,是无法得出对整项工作的精确估计的。
-
构建独立小型程序的数据不适用于编程系统项目。
-
程序开发随程序规模的大量增长而增长。
-
一些发表的研究报告显示,指数约为 1.5。(Boehm 的数据并不完全一致,在 1.05 和 1.2 之间变化[1]。)
-
Portman 的 ICL 数据显示,相对于其他活动,全职程序员仅将 50%的时间用于编程和调试。
-
Aron 的 IBM 的数据显示,生产率是系统各个部分交互的函数,在 1.5 千代码行/人-年至 10 千代码行/人-年的范围内变化。
-
Harr 的 Bell 实验室数据显示,对于已完成的产品,操作系统类的生产率大约是 0.6 千 LOC/人-年,编译类工作的生产率大约为 2.2 千 LOC/人-年。
-
Brooks 的 OS/360 数据与 Harr 的数据一致:操作系统 0.6~0.8 千 LOC/人-年,编译器 2~3 千 LOC/人-年。
-
Corbató的 MIT 项目 MULTICS 数据显示,在操作系统和编译器混合类型上的生产率是 1.2 千 LOC/人-年,但这些是 PL/I 的代码行,而其他所有的数据是汇编代码行。
-
在基本语句级别,生产率看上去是一个常数。
-
当使用适当的高级语言时,程序编制的生产率可以提高 5 倍。
第9章 削足适履¶
-
除了运行时间以外,程序所占据的内存空间也是主要开销。特别是对于操作系统,它的很多程序是永久驻留在内存中的。
-
即使如此,花费在驻留程序所占据内存上的金钱仍是物有所值的,比其他任何在配置上投资的效果都要好。规模本身不是坏事,但不必要的规模是不可取的。
-
软件开发人员必须设立规模目标,控制规模,发明一些减少规模的方法——就如同硬件开发人员为减少元器件所做的事情一样。
-
规模预算不仅仅在占据内存方面是明确的,同时还应该指明程序对磁盘的访问次数。
-
规模预算必须与分配的功能相关联;在指明模块大小的同时,确切定义模块的功能。
-
在大型团队中,各个小组倾向于不断地局部优化,以满足自己的目标,而较少考虑对用户的整体影响。这种方向性的问题是大型项目的主要危险。
-
在整个实现的过程期间,系统结构师必须保持持续的警觉,确保连贯的系统完整性。
-
从系统整体出发以及面向用户的态度是软件编程管理人员最重要的职能。
-
在早期应该制定策略,以决定用户可选项目的粗细程度,因为将它们作为整体打包能够节省内存空间(常常还可以节约市场成本)。
-
暂存区空间的尺寸以及每次磁盘访问的程序数量是很关键的决策,因为性能是规模的非线性函数。(这个整体决策已显得过时——起初是由于虚拟内存,后来则是成本低廉的内存。现在的用户通常会购买能容纳主要应用程序所有代码的内存。)
-
为了取得良好的空间-时间折衷,开发队伍需要得到特定的某种语言或者机型的编程技能培训,特别是在使用新语言或者新机器时。
-
编程需要技术积累,每个项目需要自己的标准组件库。
-
库中的每个组件需要有两个版本,运行速度较快和短小精炼的。(现在看来,这有些过时了。)
-
精炼、充分和快速的程序往往是战略性突破的结果,而不仅仅是技巧上的提高。
-
这种突破常常是一种新型算法。
-
更普遍的是,战略上的突破常来自于对数据或表的重新表达。数据的表现形式是编程的根本。
第10章 提纲挈领¶
-
"前提:在一片文件的汪洋中,少数文档成为了关键的枢纽,每个项目管理的工作都围绕着它们运转。它们是经理们的主要个人工具。"
-
对于计算机硬件开发项目,关键文档是目标、手册、进度、预算、组织机构图、空间分配以及机器本身的报价、预测和价格。
-
对于大学科系,关键文档类似于目标、课程描述、学位要求、研究报告、课程表和课程的安排、预算、教室分配、教师和研究生助手的分配。
-
对于软件项目,要求是相同的:目标、用户手册、内部文档、进度、预算、组织机构图和工作空间分配。
-
因此,即使是小型项目,项目经理也应该在项目早期对上述的一系列文档进行规范化。
-
以上集合中每一个文档的准备工作都将注意力集中在思索和对讨论的提炼上,而书写这项活动需要上百次的细小决定。正是由于它们的存在,人们才能从令人迷惑的现象中得到清晰、确定的策略。
-
对每个关键文档的维护提供了状态监督和预警机制。
-
每个文档本身就可以作为检查列表或者数据库。
-
项目经理的基本职责是使每个人都向着相同的方向前进。
-
项目经理的主要日常工作是沟通,而不是做出决定;文档使各项计划和决策在整个团队范围内得到交流。
-
只有一小部分管理人员的时间——可能只有 20%——用来从自己头脑外部获取信息。
-
出于这个原因,广受吹捧的市场概念——支持管理人员的"完全信息管理系统"并不基于反映管理人员行为的有效模型。
第11章 未雨绸缪¶
-
化学工程师已经认识到无法一步将实验室工作台上的反应过程移到工厂中,需要一个试验性工厂(pilot plant)来为提高产量和在缺乏保护的环境下运作提供宝贵经验。
-
对于编程产品而言,这样的中间步骤同样是必要的,但是软件工程师在着手发布产品之前,却并不会常规地进行试验性系统的现场测试。(现在,这已经成为了一项普遍的实践,beta 版本不同于有限功能的原型,alpha 版本同样是我所倡导的实践。)
-
第一个开发的系统对于大多数项目并不合用。它可能太慢、太大,而且难以使用,或者三者兼而有之。
-
系统的丢弃和重新设计可以一步完成,也可以一块块地实现,但这是必须完成的步骤。
-
将开发的第一个系统——丢弃原型——发布给用户,可以获得时间,但是它的代价高昂——对于用户,使用极度痛苦;对于重新开发的人员,分散了精力;对于产品,影响了声誉,即使最好的再设计也难以挽回名声。
-
因此,为舍弃而计划,无论如何,你一定要这样做。
-
"开发人员交付的是用户满意程度,而不仅仅是实际的产品。"(Cosgrove)
-
用户的实际需要和用户感觉会随着程序的构建、测试和使用而变化。
-
软件产品易于掌握的特性和不可见性,导致它的构建人员(特别容易)面临着永恒的需求变更。
-
目标上(和开发策略上)的一些正常变化无可避免,事先为它们做准备总比假设它们不会出现要好得多。
-
为变更而计划软件产品的技术,特别是拥有细致的模块接口文档的结构化编程广为人知,但并没有相同规模的实践。尽可能地使用表驱动技术同样是有所帮助的。(现在内存的成本和规模使这项技术越来越出众。)
-
高级语言的使用、编译时操作、通过引用的声明整合和自文档技术能减少变更引起的错误。
-
采用定义良好的数字化版本将变更量子化(阶段化)(当今的标准实践。)
为变更计划组织架构
-
程序员不愿意为设计书写文档,不仅仅是因为情性,更多的是源于设计人员的骄傲——要为自己尝试性的设计决策进行辩解。(Cosgrove)
-
为变更组建团队比为变更进行设计更加困难。
-
只要管理人员和技术人才的天赋允许,老板必须对他们的能力培养给予极大的关注,使管理人员和技术人才具有互换性;特别是希望在技术和管理角色之间自由地分配人手的时候。
-
具有两条晋升线的高效组织机构存在着一些社会性的障碍,人们必须警惕并积极地同它做持续的斗争。
-
很容易为不同的晋升线建立相互一致的薪水级别,但同等威信的建立需要一些强烈的心理措施:相同的办公室、一样的支持以及技术调动的优先补偿。
-
组建外科手术队伍式的软件开发团队是对上述问题所有方面的彻底冲击。对于灵活组织架构问题,这的确是一个长期行之有效的解决方案。
前进两步,后退一步——程序维护
-
程序维护基本上不同于硬件的维护;它主要由各种变更组成,如修复设计缺陷,新增功能,或者是使用环境或配置变换引起的调整。
-
对于一个广泛使用的程序,其维护总成本通常是开发成本的40%或更多。
-
维护成本受用户数目的影响。用户越多,所发现的错误也越多。
-
Campbell 指出了一个显示产品生命期中每月 bug 数的有趣曲线,其先是下降,然后上升。
-
缺陷修复总会以 20%~50%的几率引入新的 bug。
-
每次修复之后,必须重新运行先前所有的测试用例,确保系统不会以更隐蔽的方式被破坏。
-
能消除、至少是能指明副作用的程序设计方法,对维护成本有很大的影响。
-
同样,实现设计的人员越少、接口越少,产生的错误也就越少。
前进一步,后退一步——系统熵随时间增加
-
Lehman 和 Belady 发现,模块数量随大型操作系统(OS/360)版本号的增加呈线性增长,但是模块随版本号指数的增长而受到影响。
-
所有修改都倾向于破坏系统的架构,增加了系统的混乱程度(熵)。即使是最熟练的软件维护工作,也只是延缓了系统退化到不可修复的混乱状态的进程,以致必须重新进行设计。(许多程序升级的真正需要,如性能等,尤其会冲击它的内部结构边界。原有边界引发的不足常常在日后才会出现。)
第12章 千将莫那¶
-
项目经理应该制定一套策略,并为通用工具的开发分配资源;与此同时,他还必须意识到专业工具的需求。
-
开发操作系统的队伍需要自己的目标机器,进行调试开发工作。相对于最快的速度而言,它更需要最大限度的内存,还需要安排一名系统程序员,以保证机器上的标准软件是及时更新和实时可用的。
-
同时还需要配备调试机器或者软件,以便在调试过程中,所有类型的程序参数可以被自动计数和测量。
-
目标机器的使用需求量是一种特殊曲线:刚开始使用率非常低,突然出现爆发性的增长,接着趋于平缓。
-
同天文工作者一样,大部分系统调试工作总是在夜间完成。
-
抛开理论不谈,一次分配给某个小组的连续的目标时间块被证明是最好的安排方法,比不同小组的穿插使用更为有效。
-
尽管技术不断变化,这种采用时间块来安排匮乏计算机资源的方式仍能够延续 20 年(在 1975 年),这是因为它的生产率最高。(在 1995 年依然如此。)
-
如果目标机器是新产品,就需要一个目标机器的逻辑仿真装置。这样,可以更快地得到辅助调试平台。即使在真正机器出现之后,仿真装置仍可提供可靠的调试平台。
-
主程序库应该被划分成:(1)一系列独立的私有开发库;(2)正处在系统测试下的系统集成子库;(3)发布版本。正式的分离和进度提供了控制。
-
在编制程序的项目中,节省最大工作量的工具可能是文本编辑系统。
-
系统文档中的巨大容量产生了新的不易理解问题[例如,看看 Unix],但是它比大多数未能详细描述编程系统特性的短小文章更加可取。
-
自上而下、彻底地开发一个性能仿真装置。尽可能早地开始这项工作,仔细地听取 "它们表达的意见"。
高级语言
-
只有懒散和情性会妨碍高级语言和交互式编程的广泛应用。(如今,它们已经在全世界使用。)
-
高级语言不仅提高了生产率,还改进了调试:bug 更少,而且更容易寻找。
-
传统的反对意见——功能、目标代码的尺寸、目标代码的速度,随着语言和编译器技术的进步已不再成为问题。
-
现在可供系统编程合理选择的语言是 PL/I(不再正确)。
交互式编程
-
某些应用上,批处理系统决不会被交互式系统所替代(依然成立)。
-
调试是系统编程中较慢和较困难的部分,而漫长的调试周转时间是调试的祸根。
-
有限的数据表明,系统软件开发中,交互式编程的生产率至少是原来的两倍。
第13章 整体部分¶
-
第4、5、6章所意味的煞费苦心、详尽体系结构工作不但使产品更加易于使用,而且使开发更容易进行且 bug 更不容易产生。
-
Vyssotsky 提出,"许许多多的失败完全源于那些产品未精确定义的地方。"
-
在编写任何代码之前,规格说明必须提交给外部测试小组,以详细地检查说明的完整性和明确性。开发人员自己无法完成这项工作。(Vyssotsky)
-
"10 年内(1965—1975 年),Wirth 自上而下地进行设计(逐步细化)将会是最重要的新型形式化软件开发方法。"
-
Wirth 主张,在每个步骤中,都尽可能地使用级别较高的表达方法。
-
好的自上而下的设计从四个方面避免了 bug。
-
有时必须回退,推翻顶层设计,重新开始。
-
结构化编程中,程序的控制结构仅由支配代码块(相对于任意的跳转)的给定集合所组成。这种方法很好地避免了 bug,是一种正确的思考方式。
-
Gold 的试验结果显示,在交互式调试过程中,第一次交互取得的工作进展是后续交互的 3 倍。这实际上获益于在调试开始之前仔细地调试计划。(我认为,在 1995 年依然会如此。)
-
我发现对良好(对交互式调试做出快速反应)系统的正确使用,往往要求每两小时的终端会话对应于两小时的桌面工作:1 小时会话后的清理和文档工作;1 小时为下一次计划变更和测试。
-
系统调试(相对于单元测试)所花费的时间会比预期的更长。
-
系统调试的困难程度证明了需要一种完备系统化和可计划的方法。
-
系统调试仅仅应该在所有部件能够运作之后开始。(这既不同于为了查出接口 bug 所采取的"合在一起尝试"的方法,也不同于在所有构件单元的 bug 已知但未修复的情况下,即开始系统调试的做法。对于多个团队尤其如此。)
-
开发大量的辅助调试平台和测试代码是很值得的,代码量甚至可能有测试对象的一半。
-
必须有人对变更和版本进行控制和文档化,团队成员应使用开发库的各种受控接口来工作。
-
系统测试期间,一次只添加一个构件。
-
Lehman 和 Belady 出示了证据,变更的阶段(量子)要么很大,间隔很宽;要么小且频繁。后者很容易变得不稳定。(Microsoft(微软)的一个团队使用了非常小而频繁的阶段(量子)。结果每天晚上都需要重新编译生成增长中的系统。)
第14章 柄起萧墙¶
-
"项目是怎样被延迟了整整一年时间的······一次一天。"
-
一天一天的进度落后比起重大灾难更难以识别,更不容易防范和更加难以弥补。
-
根据一个严格的进度表来控制大型项目的第一个步骤是制定进度表,进度表由里程碑和日期组成。
-
里程碑必须是具体的、特定的和可度量的事件,能进行清晰的定义。
-
如果里程碑定义得非常明确,以至于无法自欺欺人时,程序员很少会就里程碑的进展弄虚作假。
-
对于大型开发项目中的估计行为,政府的承包商所做的研究显示:每两周进行仔细修订的活动时间估计,随着开始时间的临近不会有太大的变化;期间内对时间长短的过高估计,会随着活动的进行持续下降;过低估计直到计划的结束日期之前大约三周左右,才会有所变化。
-
慢性进度偏离是士气杀手。[Microsoft 的 Jim McCarthy 说:"如果你错过了一个最终期限(deadline),确保完成下一条最终期限。"[2]]
-
同优秀的棒球队伍一样,进取对于杰出的软件开发团队是不可缺少的必要品德。
-
不存在关键路径进度的替代品,使人们能够辨别计划偏移的情况。
-
PERT 的准备工作是 PERT 图使用中最有价值的部分。它包括了整个网状结构的展开、任务之间依赖关系的识别和各个任务链的估计。这些都要求在项目早期进行非常专业的计划。
-
第一份 PERT 图总是很粗糙,不过人们总是不断努力,运用才智来制定下一份 PERT 图。
-
PERT 图为那个使人泄气的借口——"其他的部分反正会落后"提供了答案。
-
每个老板同时需要采取行动的异常信息以及用来进行分析和早期预警的状态数据。
-
状态的获取是困难的,因为下属经理有充分的理由不提供信息共享。
-
老板的不良反应肯定会对信息的完全公开造成压制;相反,仔细区分状态报告、毫无愧疚地接收报告、决不越俎代庖,将能鼓励诚实的汇报。
-
必须有评审机制,使所有成员可以通过它了解真正的状态。出于这个目的,里程碑的进度和完成文档是关键。
-
Vyssotsky:"我发现在里程碑报告中很容易记录'计划(老板的日期)'和'估计(最基层经理的日期)'的日期。项目经理必须停止对估计日期的怀疑。"
-
对于大型项目,一个对里程碑报告进行维护的计划和控制小组是非常可贵的。
第15章 另外一面¶
-
对于软件编程产品来说,程序向用户所呈现的面貌——文档,与提供给机器识别的内容同样重要。
-
即使是完全开发给自己使用的程序,描述性文字也是必需的,因为它们会被用户-作者所遗忘。
-
培训和管理人员基本上没有向编程人员成功地灌输对待文档的积极态度——文档能在整个生命周期对克服懒惰和进度的压力起促进和激励作用。
-
这样的失败并不都是因为缺乏热情或者说服力,而是没能正确地展示如何有效和经济地编制文档。
-
大多数文档只提供了很少的总结性内容。必须放慢脚步,稳妥地进行。
-
由于关键的用户文档包含了与软件相关的基本决策,因此它的绝大部分需要在程序编制之前书写,它包括了 9 项内容(参见相应章节)。
-
每一份发布的程序拷贝应该包括一些测试用例,其中一部分用于校验输入数据,一部分用于边界输入数据,另一部分用于无效的输入数据。
-
对于必须修改程序的人而言,他们需要程序内部结构文档,同样要求一份清晰明了的概述,它包括了 5 项内容(参见相应章节)。
-
流程图是被吹捧得最过分的一种程序文档。详细逐一记录的流程图是一件令人生厌的事情,而且高级语言的出现使它显得陈旧过时。(流程图是图形化的高级语言。)
-
如果这样,很少有程序需要一页纸以上的流程图。(在这一点上,MILSPEC 军用标准文档需求实在错得很厉害。)
-
即使的确需要一张程序结构图,也并不需要遵照ANSI的流程图标准。
-
为了使文档易于维护,将它们合并至源程序是至关重要的,而不是作为独立文档进行保存。
-
最小化文档担负的三个关键思路:
-
借助那些必须存在的语句,如名称和声明等,来附加尽可能多的"文档"信息;
-
使用空格和格式来表现从属和联套关系,提高程序的可读性;
-
以段落注释,特别是模块标题的形式,向程序中插入必要的记叙性文字。
-
程序修改人员所使用的文档中,除了描述事情如何,还应阐述它为什么那样。对于加深理解,目的是非常关键的,即使是高级语言的语法,也不能表达目的。
-
在线系统的高级语言(应该使用的工具)中,自文档化技术发现了它的绝佳应用和强大功能。