Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.2 从软件危机到没有银弹——工程化的三次追问

加米施的雪

想象你来到了1968年10月7日,坐在德国巴伐利亚州加米施-帕滕基兴小镇的一间会议室里。窗外阿尔卑斯山的初雪正在飘落,屋内五十位世界顶尖程序员挤在一起,烟灰缸堆满了烟蒂,黑板上画满了流程图。气氛是压抑的:那是集体性的自我怀疑,而非学术会议惯常的相互恭维。

NATO科学委员会召集了这场会议。主题是“软件工程“(Software Engineering),但讽刺的是,这个术语在彼时还不存在。会议主席弗里德里希·鲍尔(Friedrich Bauer)在开场白中使用了这个词,他在黑板上写下“Software Engineering“,然后转身面对众人,说了一句石破天惊的话:“我们之所以用’工程’这个词,是因为我们想要达到桥梁工程师那样:在建造之前就能计算。”

在场没有人笑。因为他们都知道现状是什么:IBM的OS/360项目(由小弗雷德里克·布鲁克斯(Frederick Brooks Jr.)主持的史上最雄心勃勃的操作系统开发)耗费了5年、5000人年、超过2亿美元(按1960年代币值),最终交付的系统在内存管理和任务调度上依然漏洞百出。同一时期,美国空军的SAGE防空系统代码量超过百万行,但几乎没有两个子系统能可靠地通信。1960年代末期,一份英国政府的调查报告使用了这样一个词来描述软件开发的状态:“混乱”(chaos)。

这就是“软件危机“(Software Crisis)的诞生时刻。它是整个行业共同的觉醒:我们无法按时、按预算、按规格交付能够正常工作的软件。

但这只是本书1.2节的开场。我想请你先停下来,退后一步。这本书的名字叫《汽车嵌入式软件工程》,我没有在写一部计算机通史。那么,为什么我要用1968年的一场会议来开启我们对汽车软件工程的讨论?因为那场会议提出的核心问题,到今天仍然在追问每一个写汽车嵌入式代码的工程师:包括你。

核心洞察:软件危机的本质是对复杂性的认知不足。 建筑工程师可以用力学方程验证一栋大楼不会倒塌:他们拥有确定性。而软件工程师面对的系统,其状态空间是组合爆炸的。这是本质性的区别,不是技术手段不够先进的问题。

第一次追问:为什么是“工程“?

鲍尔选用“engineering“这个词,并非修辞上的随意之举。“工程“和“手艺”(craftsmanship)的区别在于:手艺依赖个人经验、师徒传承和直觉判断;工程依赖可量化的原则、可靠的模型和可验证的约束。一名石匠可以凭经验告诉你“这堵墙够厚了“,一名结构工程师能用公式算出在给定载荷下这堵墙需要多厚。

那么,软件可以被“工程化“吗?

这个问题比表面看起来深邃得多。要理解它,我们需要回到一个更根本的源头:阿兰·图灵(Alan Turing)。

1936年,图灵发表了《论可计算数及其在判定问题上的应用》(On Computable Numbers, with an Application to the Entscheidungsproblem)。这篇论文为计算机科学奠定了一切基础,但其中包含了一个让所有软件工程师绝望的结论:停机问题(Halting Problem)。

停机问题是这样陈述的:是否存在一个通用算法,对于任意给定的程序和输入,能够判断这个程序最终会停止(halt),还是会永远运行下去?图灵的答案是:不存在,而且是用数学严格证明的不存在。

你可能会问:这和“软件有没有bug“有什么关系?

关系重大。如果存在一个“完美测试工具“可以告诉你“你的代码没有任何bug“,那么这个工具首先必须能够判断你的代码在所有输入下的行为:包括是否会终止。而图灵已经证明了,在最一般的意义上,这是不可能的。换句话说:

“没有银弹“不是一个工程经验总结:它是一道数学定理。

停机问题是“没有银弹“的数学证明。如果你连“这段代码是否会终止“都无法在算法上判断,那你自然也无法用任何工具保证“这段代码没有缺陷“。软件中的bug的不可消除性,根源在于计算理论本身。

核心洞察:你永远无法建造一台“bug检测机“来消灭所有缺陷——这是数学的判决,不是工程上暂时的困难。 软件的根本困境不在于工具不够好、语言不够强、流程不够规范,而在于图灵停机问题所揭示的那个不可逾越的壁垒:程序的非平凡语义属性在算法上是不可判定的。

第二次追问:布鲁克斯的《人月神话》

如果说图灵从数学上定义了软件复杂性的上限,那么弗雷德里克·布鲁克斯则从人类组织协作的角度揭示了它的下限。

1975年,布鲁克斯出版了《人月神话》(The Mythical Man-Month)。这本不到200页的小书成为整个软件工程领域的“圣经“,因为它精确地描述了一个至今无解的问题。

书中最著名的论断是布鲁克斯法则(Brooks’ Law):

“向一个已经延期的软件项目增加人手,只会让它更加延期。”

为什么?布鲁克斯给出了一个简洁到残酷的解释:沟通成本随团队规模以O(n²)增长。一个两人团队只有1条沟通路径;一个十人团队有45条;一个五十人团队有1225条。在某个临界点之后,新加入的人不仅无法增加产出:他们消耗在沟通上的时间反而减少了团队的整体效率。

人月(man-month)这个概念本身就是一个谬误。管理者习惯认为“100个人月的工作量 = 100个人干1个月 = 1个人干100个月“。布鲁克斯指出,这在软件工程中完全不成立。因为软件开发包含那些必须经由大脑完成的、不可分割的概念性工作。一个女人需要九个月生一个孩子,九个女人不可能一个月生一个孩子。

这个洞察对汽车嵌入式软件工程的意义,怎么强调都不过分。你所在的团队可能同时负责三个ECU的软件开发,每个ECU涉及十几种通信协议、几十个SW-C(软件组件)、数百条需求。当你试图“给这个模块加两个开发人员“来赶上SOP节点时,你实际上在做的是:往本已脆弱的沟通网络中再增加n条路径,让本已紧张的集成时间表再增加多次合并冲突。

布鲁克斯进一步区分了两种复杂性:

偶然复杂性(accidental complexity):来自工具、语言、环境的不足。比如,用汇编语言写一个排序算法很复杂,但用高级语言就简单了:这是偶然的。更好的编译器、更强的IDE、更丰富的库可以消除偶然复杂性。

本质复杂性(essential complexity):来自问题域本身的固有难度。无论用什么语言、什么工具,控制一台混动汽车的扭矩分配策略都不会变得“简单“,因为这个问题本身就涉及多体动力学、热力学、电磁学和实时控制的交叉:它的复杂性存在于物理世界和大自然之中,而不是存在于你的代码里。

布鲁克斯的结论是:大部分早期软件工程的进展(高级语言、结构化编程、分时系统)解决的只是偶然复杂性。而本质复杂性(概念结构的复杂性)在过去二十年中几乎没有被触及。这才有了他的另一本经典著作的标题:

《没有银弹》(No Silver Bullet)。

核心洞察:人和月不能互换,因为软件开发不是搬砖,而是概念性的劳动;沟通成本以O(n²)增长,加人常常是火上浇油。 在汽车嵌入式领域,这一点被ISO 26262的多层独立安全审核进一步放大:你不仅需要开发人员之间的沟通,还需要安全工程师、功能安全经理、供应商和OEM之间的跨组织沟通。

第三次追问:没有银弹之后——布鲁克斯的复盘

1986年,布鲁克斯发表了《没有银弹:软件工程的本质与偶然》(No Silver Bullet — Essence and Accidents of Software Engineering)。这篇论文的开篇句成为计算机科学中最著名的预言之一:

“我相信,在未来的十年内,没有任何一项单一的技术或管理进步,能够独自在软件的生产力、可靠性和简洁性方面,带来一个数量级的提升。”

1995年,布鲁克斯发表了《“没有银弹“重装上阵》(“No Silver Bullet” Refired),回顾了自1986年以来软件工程的所有重大进展(面向对象编程、AI辅助编程、快速原型、增量开发),然后得出了一个令所有人沉默的结论:

本质复杂性依然没有被触及。

二十年后,布鲁克斯依然正确。三十年后的今天,他仍然正确。我们有了GitHub Copilot,有了Rust的所有权系统,有了形式化验证工具:这些解决了大片的偶然复杂性(你不再需要手动管理内存,不再需要手工编写重复的样板代码),但没有一个解决了本质复杂性。实现一个CAN总线的诊断协议依然需要你理解UDS标准中的每一个服务ID和子功能码。AI可以辅助你更快地检索这些信息,但它无法替你“理解“它们。

对汽车嵌入式软件工程师而言,这意味着:没有捷径。

你可以在Stack Overflow找到答案,可以把需求转给离线团队,可以引入一个什么“框架“来帮你处理通信层。但最终,你必须自己理解:这个SW-C的运行周期为什么是10ms而不是5ms?这个DTC的debounce策略为什么要采用基于时间的去抖而不是基于计数的去抖?这份AUTOSAR配置为什么把该Runnable映射到该Task?这些问题没有银弹:它们要求你的大脑去处理本质复杂性。

建筑的隐喻:关于不可见性的思考

回到建筑的隐喻。

布鲁克斯在被问及“为什么软件似乎比其他工程学科更难“时,提出了一个令人震动的观察:软件是没有物理实体的。

建筑物的破坏是可见的。一座桥梁的坍塌(无论是1940年塔科马海峡大桥的风致共振崩塌,还是2018年热那亚莫兰迪大桥的混凝土腐蚀断裂)都是瞬间的、公开的、不可否认的。死者的名字会出现在报纸上,调查委员会会追查每一根钢筋的计算图纸,工程师会入狱,规范会被修改。

而软件的故障是不可见的。一辆汽车的刹车系统软件包含一个竞态条件bug:它只在环境温度低于零下15°C、车速在63-67km/h之间、且CAN总线负载超过75%时触发。这个bug可能潜伏了四万公里,直到某个冬天清晨,在一个长下坡路段,刹车响应延迟了120ms。没有人会第一反应想到“这是软件问题“。调查人员要看数周的数据日志,用示波器复现信号时序,最终才在那个if-else的缝隙里找到了那行缺失的volatile声明。

一个倒塌的大楼,死亡的瞬间在物理世界中凝固:肉眼可见,立即可知。一个汽车软件的崩溃,死亡被溶解在时间、温度和统计分布之中:不可见、不可立即追溯。

正如布鲁克斯所写的:“软件看不见摸不着,它不以任何物理形式存在。因此,它的结构、它的复杂性、它的脆弱性:都隐藏在我们无法直接感知的层次里。”

但这里有一个关键的反转:正是因为软件是不可见的,我们更需要“建筑学“的思维方式。古罗马建筑师维特鲁威在《建筑十书》中提出建筑的三原则:坚固(firmitas)、实用(utilitas)、美观(venustas)。罗马万神殿至今仍然屹立,因为设计者在建造之前,已经在脑海中完成了结构的全部承重分析。

一个软件架构师要做的事情,本质上和维特鲁威一样:在没有物理实体的世界里,凭靠抽象的思维工具(接口、模块、依赖关系、状态机),去“看见“一座即将承载数百万行代码的建筑的结构完整性。在没有重力提醒你的情况下,你依然需要判断:这块砖(这个模块)放这里,会不会让整座楼塌掉?

核心洞察:软件的不可见性使其故障模式比任何物理工程都更难追踪和归因——你建造的是一座看不见的大厦。 这是汽车嵌入式软件面临的第一性约束,而你只能通过侧面证据(测试覆盖率、静态分析报告、代码评审记录)来推断它的结构完整性。这就是为什么ASPICE、ISO 26262、MISRA C这些重型流程不是官僚主义的冗余:它们是你唯一可以用来“看见“这座无形建筑的镜子。

从架构到人:三次追问的终极答案

现在,把三次追问连成一条线:

第一次追问(图灵,1936):软件中的bug在数学上不可消除:这不是你不够努力,这是数学。

第二次追问(布鲁克斯,1975):软件开发的人力成本不可线性估算:原因在于沟通的固有成本,而非管理不善。

第三次追问(布鲁克斯,1986/1995):没有银弹:过去没有,现在没有,将来也不会有。本质复杂性是问题域本身赋予的,任何工具都消除不了。

读到这里,你可能会感到一种宿命般的无力感。如果bug不可消除,如果沟通天然低效,如果永远没有银弹:那我们还在这里努力什么?

答案恰恰在这三条追问的交汇处:

正因为在数学上不可能消除所有bug,我们需要系统性的工程方法来将残余缺陷率降低到一个可验证、可接受的水平。这就是ASPICE能力等级2→3的实践意义:追求可预测性。

正因为沟通成本不可线性压缩,我们需要架构的模块化:用清晰的接口(如同建筑中的门窗系统)将模块之间的耦合降到最低,让每个模块可以在相对独立的语境下被开发、测试和维护。这就是AUTOSAR的分层架构背后的动机:因为人类有限的沟通容量。

正因为没有银弹,我们需要持续投入而非一次性突击:每一代技术平台(从OSEK到AUTOSAR CP到AUTOSAR AP)都在消除一层偶然复杂性,但本质复杂性始终留给你自己。而直面本质复杂性的唯一方式,是通过反复的工程实践、深入的领域理解和严谨的同行评审,一寸一寸地提升对系统的认知。

布鲁克斯在他晚年的访谈中说过一句话,我觉得最适合作为这一节的收尾:“我们不是失败者。我们只是承担了一个本质困难的任务,在这样的任务面前,谦卑是最大的美德。”


本篇小结

  • 软件危机(1968年NATO会议)是整个行业第一次集体承认:软件开发的质量、成本和进度不可预测,需要系统性的工程方法而非个人天才来应对。
  • 图灵的停机问题从数学上证明了“完美自动检测所有软件缺陷“的不可能性:软件缺陷的不可根除性是计算理论的必然。
  • **布鲁克斯的《人月神话》**揭示了软件开发的三个硬约束:(1)人月不可互换,(2)沟通成本以O(n²)增长,(3)区分偶然复杂性与本质复杂性是理解一切工程方法的前提。
  • 《没有银弹》的结论,没有单一技术能够使软件生产力提升一个数量级,在三十余年后依然成立。汽车嵌入式领域的重型流程(ASPICE、ISO 26262、MISRA C)并非累赘,而是对“没有银弹“的工程回应。
  • 建筑的隐喻揭示软件的独特困境:因为没有物理实体,故障不可见;因为故障不可见,需要额外的系统性手段(测试、审查、静态分析)来“看见“结构完整性,这些手段构成了软件工程区别于传统工程的独有方法论。
  • 三次追问的终极答案:接受不可消除的复杂性,通过架构模块化管理沟通成本,通过持续实践消化本质复杂性,谦卑地做困难的事。

【下集预告】 既然没有银弹,那么人类是如何应对软件复杂性的?下一节我们将进入三个时代、三种方法论的对决:瀑布模型认为需求可以预先固定,敏捷宣言认为需求必须逐步发现,而ASPICE说:在造车这件事上,两种路径都要走,但要穿上“安全“这件防弹衣。1968年的那个会议室里没有人知道,这场“如何管理复杂性“的争论,将持续半个世纪。