跳转到主要内容

第 17 章 · 再论"没有银弹"

九年之后作者逐一回应批评:措辞的澄清、对"悲观"的辩护、面向对象这枚铜质子弹、重用的真实成本,以及为什么形势没有改变。

题记两句都在说同一件事:威廉三世说"生死有命,富贵在天",蒲柏说"任何人若想看到一件完美无瑕的作品,他所想的那种作品过去不存在,现在和将来也不会出现"。

这一章讲了什么

这一章是对"没有银弹"发表九年(1986 到 1995)后的回应与复盘。作者先交代了一个小插曲:那篇文章最初是 IFIP 1986 年都柏林大会的约稿,《计算机》杂志翻印时用了类似《伦敦人狼》的恐怖剧照当封面,还配了一篇《杀死人狼》的补充报道——“我并未注意到补充报道和插图,也没有料到一篇严肃的技术性文字会被这样润色”。

存在着银弹——就在这里

他重申自己的接受标准,而且这条标准很实在:当某个名副其实的中立客户走到面前说"我用了这种方法、工具或者产品,它让我的软件生产率提高了 10 倍",我很乐意接受银弹已经出现的观点。事实是,他重读 1986–1987 年间的早期反馈时发现,当时被强烈推崇的秘方并没有出现所声称的戏剧性效果。

含糊的表达将会导致误解

关于 “accident” 一词。他指出这个来自亚里士多德的术语被误读了:它不是"偶然发生",也不是"不幸的",而是"附带的"“从属的”。他也不曾贬低软件构建中的次要部分,反而认同 D. 塞耶斯对创造性活动的三阶段划分——概念结构的形式规格化、用现实介质实现、在实际使用中与用户交互;“必要"是构思概念结构,“次要"是实现过程。

关于争论的实质。他把问题还原成一个可测的现实问题:整个开发工作中,哪些部分与概念结构的精确有序表达相关,哪些是创造这些结构的思维活动?判断方式是看缺陷的性质——概念性的(比如没能识别某些异常)还是表达上的(比如指针错误、内存分配错误)。他的判断是:开发的次要或表达部分如今已经下降到整个工作的一半或以下。而且他强调这一点原则上可以用测量技术来研究;值得注意的是,从来没有人告诉他次要任务占了 9/10

他还引了 Herzberg 等人 1959 年的研究(由 Bruce Blum 提示):动机因素能提高生产率,而环境和次要因素无论多么积极都无法提高生产率,只有在起负面作用时才会降低它。按这个框架,“没有银弹"说的正是——很多开发过程已经消除了那些负面因素:笨拙的机器语言、漫长的批处理周转、拙劣的工具、难以忍受的内存限制。

关于"因为是根本困难所以没希望"的误读。1990 年 Brad Cox 认为可交互的构件开发能解决根本困难,作者表示赞同,但指出 Cox 有两处误解:一是把他理解为"困难来自程序员缺乏技术”,而他坚持根本困难是固有的概念复杂性,任何时代、任何方法都存在;二是把他的结论读成"处理根本困难没有希望”——“这不是我的本意,每一种困难产生的麻烦都是可以改善的”。

由此他提出一个重要的限定:复杂性是层次化的。并非所有复杂性都不可避免,很多(不是全部)来自应用本身随意的复杂特性。他引 Sodahl 的观察:系统工作中遇到的大多数复杂性是组织结构失误的征兆,为这些现实建模、写出同等复杂的程序,实际上是隐藏而不是解决了混乱。Steve Lukasik 更进一步,认为连组织复杂性也不是任意的,可能受策略调整影响。作者以物理学家的背景回应:昨天的复杂性是今天的规律——分子的无序性启迪了气体动力学和热力学定律;他也承认自己没能力做更深层的分析,认为这门学科需要一种更广泛的信息理论,能像香农信息论量化交互流那样去量化静态结构的信息内容。

关于"悲观"的指责。他自嘲说,妻子、同事和编辑都发现他犯乐观主义错误的概率远大于悲观主义——毕竟是程序员出身,乐观是这个行业的职业病。而且文章里明确写过"怀疑论者并不是悲观主义者……虽然没有通天大道,但是路就在脚下”,还预言过:如果 1986 年的那些创新能持续开拓和发展,它们的共同作用就能带来数量级的提高。十年过后他的判断是:这个预言即使说明了什么,那也是过于乐观,而不是过于悲观。他用爱因斯坦的光速论断和哥德尔的不完备性做类比——一个结论让人沮丧,不等于它是错的。

他引 Turski 的回应作为这一节的收束,那段话用点金石的隐喻写得极好:炼金术里最诱人的目标是能把一般金属变成金子的点金石,它是从理想化想象和普遍假设中提取出的精华,是人类纯粹信仰的体现;即使被证明不存在,“寻找出路和希望能一劳永逸的愿望依然十分强烈”。于是——“将圆形变方的论文被发表,抑制脱发的洗液被研制和出售,提高软件生产率的方法被提出并成功地推销。“作者与 Turski 都认为,这个白日梦本身就限制了发展、浪费了精力。

随后他逐条回应 Harel 的批评。Harel 认为"消极"来自三个主题:把根本与次要划分开、独立评价每个候选银弹、只预言 10 年。作者的回应是:第一条正是文章的主旨,这个划分对理解软件为何难做绝对关键,也是改进方向的明确指南;第二条并不成立——各种技术是被逐个提出、且每一种都过分宣扬自身效果的,因此依次独立评估非常公平,他反对的不是技术,而是期望它们有魔术般作用的观点;第三条,选 10 年恰恰等于承认我们没能力预见 10 年以后的事——1975 年有谁能预见 1980 年代的微型计算机革命?而且所有银弹都宣称立刻见效,没有一种会说"投资我的秘方,10 年后你将成功”。

对 Harel 那个著名的思想实验——假设文章发表在 1952 年会怎样——作者也给了反驳:文章一开始就承认 1950 年代占支配地位的次要困难如今已不存在,消除它们已经带来数量级的改善;而且 Harel 对 1950 年代行业状态的设想并不准确。他列了一串事实:1952 年 Univac 用约八人开发复杂程序处理人口普查;汇编语言、重定位链接装载、浮点解释系统已被经常使用;1955 年已有 50–100 人年的商用程序;1956 年通用电气路易斯维尔工厂运行着超过八万条指令的薪资系统;1957 年 SAGE AN/FSQ-7 防空系统已经运行两年,是分布在 30 个地点、七万五千条语句的自消除故障热备实时系统。所以"个人程序的技术革命"并不能解释那段历史。

不过他也承认 Harel 有一部分感染了他:他自己喜欢问准程序员"下个 11 月在哪儿?",如果嫌问题太模糊,就再问"告诉我你自己关于时间历法的模型”——因为优秀程序员往往有很强的空间想象能力,心里装着时间的几何模型,而且高度个性化。

质量带来生产率,以及生产率数据的混乱

Capers Jones 提出一个视角转换:不要盯生产率,关注质量,生产率自然会跟着提高。理由是大量代价高昂的后续投入都花在寻找和修复规格说明、设计与实现上的错误,他给出的数据也显示了缺乏系统化质量控制与进度灾难之间的密切关系。作者认同这些数据,但补上 Boehm 的反例:一味追求完美质量,生产率会再次下降(他举 IBM 的航天飞机软件)。Coqui 的说法也被引用来佐证:系统化开发方法的发展本是为解决质量问题(尤其是避免大型灾难),而不是出于生产率考虑。

至于生产率数据本身,作者承认它非常难以定义、测量和寻找:Jones 相信相隔十年、功能等同的两个 COBOL 程序,用与不用结构化方法的差距是 3 倍;Yourdon 说"由于工作站和软件工具,我看到人们的工作获得了 5 倍的提高";DeMarco 则直说"我没有看到任何机构取得数量级的进步"。

这一节他最看重的是购买而非开发——而且他认为 1986 年那个"大众市场是软件工程领域意义最深远的开发方向"的估计被证实了。当软件包的销量达到百万甚至几千时,决定成败的问题就变成了质量、时机、产品性能和支持成本,而不再是开发成本。他给了一句很有画面感的建议:提高信息管理系统程序员生产率最戏剧化的方法,是去计算机商店买下本应由他们开发的成品;这些价格低廉、功能强大的软件更像电锯、电钻和砂磨机,而不是复杂的大型工具,因此必须"注重使用时的方便,而不是专业"。

这一节还有一段难得的自我修正。美国管理系统公司主席 Ivan Selin 在 1987 年写信质疑他"软件包没有真正改变很多"的观点,指出用户能感知到软件包变得更通用、更易定制,而这种感觉本身就会推动采用;他公司里多数情况下是最终用户而非软件人员不愿用软件包,因为他们担心失去必要特性。作者的回应很干脆:“Selin 是十分正确的——我低估了软件包客户化的程度和它的重要性。”

面向对象这枚铜质子弹

他先把问题放回"用更大的零件构建系统"这个框架:当零件很多、每个零件又很复杂时,只要接口流畅,丰富的结构就能快速组合起来。接着列出面向对象的几项特征——强制的模块化与清晰接口、封装、继承与层次化类结构、虚函数、强抽象数据类型化——并指出这些特征不必用整个 Smalltalk 或 C++ 就能获得,其中一些甚至出现在面向对象之前。他把它的吸引力比作复合维生素:一次再培训,拿到所有好处;结论是"一种非常有前途的概念"

为什么发展缓慢?James Coggins 的解释是:面向对象的程序员经历的多是错综复杂的低层次应用,于是关注低层次抽象——链表、集合类的开发,而不是用户接口、射线束、有限元素模型;而 C++ 里帮助避免错误的强类型检查,反倒让"从小事物构建大物体"变得非常困难。他主张通过客户参与提高脑力劳动的规模,并赞同自上而下设计:“我的眼科客户并不关心堆栈,他们关心描述眼角膜形状的勒让德多项式。在这方面,小规模的封装带来的好处比较少。”

David Parnas 的回信则指向方法论:人们只被告知"面向对象是一种特殊工具",却没人教他们设计方法——“我们可以用任何工具写出优质或低劣的代码。除非我们给人们讲解如何设计,否则语言所起的作用非常小。结果人们使用这种语言做出不好的设计,没有从中获得多少价值。而一旦获得的价值太少,它就不会流行。”

作者自己给出的诊断是经济学式的:面向对象的前期投入很大,而收益严重滞后。它的优势是客观的,但要到后续的开发、扩展与维护中才体现出来——用 Coggins 的话说,“面向对象技术不会加快首次或第二次的开发,产品族中第五个项目的开发将会异乎寻常地迅速”。他由此判断,极度的前期投入与收益推后,是面向对象应用迟缓的最大原因;即便如此,在很多机构里 C++ 仍然取代了 C。

重用的真实成本

“解决软件构建根本困难的最佳方法是不进行任何开发”——软件包是途径之一,另一条是重用。作者引了一串互相印证的数据和意见:Jones 说多数有经验的程序员都有自己的私人库,约 30% 的代码是重用的;公司级重用能做到 70%,但需要专门的库和管理支持,还要求对变更做统计和度量。JPL 的 Van Snyder 指出数学软件领域长期重用的原因:障碍不在生产者一侧,而在消费者一侧——如果潜在使用者"觉得"寻找并验证已有构件比自己写更贵,重复的构件就会继续产生,而关键在这个"觉得",它和真实投入无关。数学软件之所以不同,是因为它难写(每行都要高智商投入)、又有丰富的标准术语来描述功能,于是重开发成本高、检索成本低;核反应、天气与海洋模型也有同样的条件。

而现实并不乐观:Parnas 说重用"说起来容易做起来难,它同时需要良好的设计和卓越的文档",没有好文档就看不到可重用的构件;Ken Brooks 说自己第五次使用个人界面库时仍在不断修改;DeMarco 干脆说"对于重用,现有理论几乎整体缺乏";Yourdon 的经验法则是可重用构件的工作量是一次性构件的两倍,作者则根据自己的产品化成本观察估算为三倍

词汇量的代价

这是他给出的一个"可预见但还没被预言"的后果:思索的层次越高,需要掌握的基本要素就越多。要利用大型构件,无论对象类库还是程序库,都必须面对编程词汇规模的急剧扩大——现在已有成员超过三千的类库,很多对象需要十到二十个参数和选项,想拿到全部潜在重用,使用者必须学习每个成员的语法与语义。他认为这"并不是思维障碍中的一小部分"。

但也不绝望:一般人日常词汇超过一万个,受过教育者更多,而我们在自然语言中自然地学会了语法与微妙的语义——能区分"巨大、大、辽阔、大量、庞大",不会说"庞大的沙漠"或"辽阔的大象"。他由此提出三条教训:人们在上下文中学习,所以要出版复合产品的完整例子,而不只是零部件库;人们只背诵单词,语法和语义是在使用中逐渐习得的;人们按语义分类来组织词汇的使用规则,而不是比较对象子集。

结论

最后他回到基本问题:复杂性是这个行业的属性,也是主要的限制。他引 R. L. Glass 在 1988 年的总结——软件开发本来就是棘手的事情,前方不会有魔术般的解决方案,现在该做的是研究和分析革命性进展,而不是等待或希望它出现。他把这看作清新的空气:终于可以把焦点放在更可行的事情上,追求逐步的进展,而不是等待那个不大可能到来的突破。

我的判断

  • 这一章的价值在于它示范了如何回应批评:逐条区分"我表达不清"与"你理解有误",承认该承认的(如 Selin 指出他低估了软件包客户化),澄清误解的,并坚持核心论证。这种姿态在技术争论里很罕见——多数人要么全面防守,要么全面投降。
  • “accident 不是偶然或不幸,而是附带的、从属的"这个澄清很重要。中文译作"次要"大致抓住了意思,但仍容易读成"不重要”。作者的立场是:次要困难不该被贬低,它只是不该被误认为根本困难——否则改进的方向就会持续错位。
  • 他给出的可测化路径值得学:把"根本 vs 次要"从哲学争论变成分类问题——去看缺陷是概念性的还是表达性的。这比反复宣示立场有说服力得多。他也坦承没有人告诉他次要部分占 9/10,这正是把论证挂在可测事实上的好处。
  • “复杂性是层次化的"是这一章最容易被忽略的洞见。它意味着并非所有复杂度都命中注定,其中相当一部分是组织与策略失误的投影。Sodahl 那句"建立同等复杂的程序,实际上是隐藏而不是解决混乱"尤其锋利:我们常把组织问题翻译成技术复杂度,然后就再也拆不开了。
  • Turski 的点金石隐喻是我读过的对"银弹文化"最准确的讽刺。它的深刻之处不在嘲笑追求者,而在指出这种追求本身消耗了本该用于逐步改进的精力
  • 关于面向对象,作者的诊断今天依然成立,只是对象换成了别的技术:前期投入大、收益滞后,是新技术推广迟缓的最大原因。Coggins 的"不会加快第一次和第二次开发,第五个项目才会"是一条极好的采纳判据——它要求我们诚实地问自己:我有没有"第五个项目”?
  • 重用那一节给出的三条判据我认为可以直接搬用:障碍在消费者一侧;重用的前提是良好设计与卓越文档;可重用构件的工作量是一次性构件的二到三倍。“觉得比自己写更贵就不重用"这句话尤其值得记住,因为它把问题从技术可复用性转到了检索与信任成本上。
  • 词汇量的代价是我此前没有认真想过的一条。类库越大,使用者需要记住的语法与语义就越多,重用的收益会被学习成本吃掉一部分。他提出的"上下文学习"结论对今天的意义比对 1975 年更大:示例与复合产品比 API 清单更能让人学会使用

和我手上的工作有什么关系

  • 这一章的"可测化"思路我打算直接用在自我评估上:当我说"某件事很难"时,先分清它难在概念设计还是难在表达实现。这个划分能让我判断该换工具还是该停下来想清楚——换工具解决不了概念问题,这正是第 16 章那个 9/10 算术的现实版本。
  • “动机因素提高生产率,环境和次要因素只能拖累不能提升"这条框架,解释了工具投资回报的天花板:把环境从差改善到不差,收益很大;从不差继续改善到很好,收益就平了。所以我的 tools/bin/build.sh 这类投入,价值主要在消除摩擦,而不是提高上限——这也让我不再期待它们带来神奇效果。
  • 面向对象的采纳判据给了我一把尺子:任何新技术值不值得投,关键看有没有"第五个项目"来摊薄前期成本。对单人项目而言,这意味着不要为不存在的规模预先付出架构成本;同时,重复出现的同类任务(比如这个读书笔记系列)恰恰是"第五个项目"的雏形,值得为它做一次性的方法投资。
  • 重用的三条判据我已经在实践中撞到了:这个站点把 OINK 主题复用过来,省下的是开发成本,付出的是学习主题约定的时间。Van Snyder 所说"障碍在消费者一侧"正是我的体验——寻找与验证既有方案的信任成本,常常高于自己写的心理成本。一旦承认这一点,学习成本就该被算进预算,而不是当成额外开销。
  • “示例比清单更能教会使用"这条我立刻能用:这个读书笔记系列本身就是一个复合产品的例子,而每篇里"和我手上的工作有什么关系"那一节,实际上是在为将来的自己提供上下文。作者说人们在上下文中学习,这个系列的设计正好符合这一点。
  • 最后那句"追求逐步进展,而不是等待不大可能到来的突破”,对我有直接的行为含义:与其寻找一次性把读书笔记写好的方法,不如接受一篇一篇地写、每篇都留下可运行的状态——这正是这个系列已经跑通的节奏。

一句话记住

九年过去,形势没有改变:复杂性是这个行业的属性;所有银弹都宣称立刻见效,而真正的进展来自承认根本困难、放弃一劳永逸的幻想,然后一点一点改善。