文章目录
- [1. Software Crisis(软件危机)](#1. Software Crisis(软件危机))
-
- [1.1 历史背景](#1.1 历史背景)
- [1.2 关键发展里程碑](#1.2 关键发展里程碑)
- [1.3 核心问题](#1.3 核心问题)
- [2. 无法瞬间解决所有问题](#2. 无法瞬间解决所有问题)
-
- [2.1 本质性困难(Essence)](#2.1 本质性困难(Essence))
-
- [2.1.1 Complexity(复杂性)](#2.1.1 Complexity(复杂性))
- [2.1.2 Conformity(一致性/顺应性)](#2.1.2 Conformity(一致性/顺应性))
- [2.1.3 Changeability(可变性)](#2.1.3 Changeability(可变性))
- [2.1.4 Invisibility(不可见性)](#2.1.4 Invisibility(不可见性))
- [2.1.5 案例](#2.1.5 案例)
- [2.2 偶然性困难(Accidents)](#2.2 偶然性困难(Accidents))
-
- [2.2.1 定义](#2.2.1 定义)
- [2.2.2 本质性困难(Essence)和偶然性困难(Accidents)的对比](#2.2.2 本质性困难(Essence)和偶然性困难(Accidents)的对比)
-
- [2.2.2.1 更多练习](#2.2.2.1 更多练习)
- [2.3 构建软件是困难的](#2.3 构建软件是困难的)
- [2.4 小结](#2.4 小结)
- [3. 成功技术](#3. 成功技术)
-
- [3.1 潜在银弹](#3.1 潜在银弹)
- [4. 未来方向](#4. 未来方向)
-
- [4.1 购买](#4.1 购买)
- [4.2 需求精炼与原型](#4.2 需求精炼与原型)
-
- [4.2.1 Incremental Development(增量开发)](#4.2.1 Incremental Development(增量开发))
- [4.3 卓越设计师](#4.3 卓越设计师)
- [4.4 Brooks理论当代相关性](#4.4 Brooks理论当代相关性)
- [5. 小结](#5. 小结)
1. Software Crisis(软件危机)
软件危机是20世纪60年代至70年代,随着计算机应用的快速发展,社会对复杂软件系统的需求急剧增长,但当时的软件开发能力(包括设计、实现和维护)严重跟不上需求,导致了一系列严重问题的时期。
1.1 历史背景
1940s-1950s,计算机刚刚诞生,体积庞大、价格昂贵,并且主要服务于政府和科研机构,而非普通商业或个人用户。编程方式采用机器语言或汇编语言,这样的方式耗时且容易出错。
商业计算的兴起------计算机从军事/科研走向企业商用,因此需要更加复杂的软件系统,这就是我们下面讲的经典案例IBM OS/360操作系统(史上最大的软件项目之一)。
该项目严重延期、预算大幅超支。项目负责人后来总结教训写到(向进度落后的项目中增加人手,只会让它更落后)------ Brooks法则。
这就是软件需求与开发能力之间的差距日益扩大所导致的缺乏标准化的开发方法和流程。
1968年正式提出"软件危机"这一术语,北约组织召开的会议(1968年10月,德国加尔米施),会议承认:无法按时、按预算交付可靠、高效的软件是一个重大问题。由此呼吁建立一门新学科,以满足需要一种规范化、有纪律的软件开发方法的迫切需求,这为软件工程奠定基础。、
随着系统复杂度增加,危机进一步恶化。项目严重超支和失败的情况持续发生,因此结构化编程引入,瀑布模型诞生。
人们认识到软件维护负担日益加重,维护工作消耗大量资源和预算。
软件危机带来了巨额财务损失、项目失败和低效软件导致企业亏损。而开发人员普遍承受巨大压力和职业倦怠。因为不切实际的截止日期和需求导致了技术停滞,这阻碍了各行业的技术进步和创新。
1.2 关键发展里程碑
软件危机后,软件工程作为独立学科正式形成,它与计算机科学分离,成为独立学科。
它聚焦质量与流程,介绍质量保证实践、项目管理技术、流程改进模型。
软件危机带来的遗产与教训:
- 理解软件复杂性的重要性。
- 有效沟通的重要性。
- 方法论需要持续演进。
1.3 核心问题
关于软件的关键问题有:
- 项目超期、超预算。
- 软件无法满足用户需求。
- 质量差、不可靠的软件。
- 维护困难、成本激增。
2. 无法瞬间解决所有问题
1986年发表的No Silver Bullet: Essence and Accidents of Software Engineering《没有银弹:软件工程的本质性与偶然性》指出了软件开发的根本困难在于本质复杂性。
没有单一的神奇技术或方法能解决所有软件问题,因为软件固有的、无法消除的复杂性(概念性构造的复杂性),这些由于当前工具、方法不足造成的困难,可以逐步消除。
Brooks将"银弹"定义为:能单枪匹马、立竿见影地将软件生产力/可靠性/简洁性提升10倍的单一技术或管理方法;他断言这种银弹不存在,因为软件开发的根本困难源于其本质复杂性,而非偶然的技术局限------这一论断彻底打破了业界对"万能解决方案"的幻想。
2.1 本质性困难(Essence)
这是软件固有的、不可消除的"硬核部分"。
本质指软件作为"抽象概念构造"的固有属性,有四个本质属性。
| 属性 | 英文 | 核心含义 | 为何困难 |
|---|---|---|---|
| 复杂性 | Complexity | 组件数量与交互远超人类认知极限 | 非线性增长,思维无法同时把握 |
| 一致性 | Conformity | 必须与外部系统、接口、历史数据兼容 | 环境不断变化,兼容性要求持续叠加 |
| 可变性 | Changeability | 软件必须随需求持续演进 | 业务变化快,修改引入新错误 |
| 不可见性 | Invisibility | 软件是无实体的抽象构造 | 无法可视化,难以直观理解和沟通 |
2.1.1 Complexity(复杂性)
系统内部庞大的状态数量和交互关系导致的本质困难,而非偶然困难。
具体困难如下。
| 困难 | 英文 | 核心问题 | 现实后果 |
|---|---|---|---|
| 沟通困难 | Communication | 团队成员无法共享对系统的完整理解 | 知识孤岛、误解需求、设计冲突 |
| 状态理解不可靠 | Understanding all states, unreliable | 人脑无法穷举所有可能状态 | 遗漏边界情况、测试覆盖不足 |
| 功能扩展副作用 | Extending functions without side-effect | 修改一处影响多处 | 回归bug、架构腐烂、不敢重构 |
| 隐藏状态安全陷阱 | Hidden states that constitute security trapdoors | 未预料的状态成为攻击入口 | 安全漏洞、数据泄露、系统被攻破 |
2.1.2 Conformity(一致性/顺应性)
软件必须顺应人类制度和现有系统,这些制度和系统本身复杂且不断变化。
软件是最晚出现的新来者,被视为最容易妥协/适应的一方。
2.1.3 Changeability(可变性)
困难被迫持续不断变化因为需求、技术、环境的持续演进。
所有成功的软件都会被修改。
因为软件会被用于原始设计边界或超出预期领域。
而且软件比最初运行的硬件寿命更长。
2.1.4 Invisibility(不可见性)
与物理系统不同,软件缺乏物理形态,难以可视化和概念化。
2.1.5 案例
Knight Capital Group(骑士资本)2012年8月1日因为软件部署错误导致了45分钟内损失4.4亿美元,公司濒临破产。
根本原因是旧代码复用 + 部署不一致 + 隐藏状态陷阱。
- Complexity(复杂性)
系统状态是非预期的。7台已更新服务器 + 1台遗留服务器的混合状态。开发者未能考虑这种交织复杂性,也未能考虑当环境并非完全统一时单个复用标志位会如何表现。 - Invisibility(不可见性)
不像桥梁,你能看到缺失的支撑梁。工程师无法"看到"第8台服务器在运行旧代码。 - Conformity(一致性)
必须修改软件以顺应纽交所新的RLP(零售流动性计划)规则。这种压力迫使团队走捷径(复用旧标志位),而非彻底重构,最终埋下隐患。 - Changeability(可变性)
纽交所改变了交易规则,如果骑士资本系统是硬件电路,可能会说"我们改不了这么快"。 但因为是软件,因此被假设它可以且应该被立即修改。利用软件可变性的压力导致仓促的手动部署,漏掉了第8台服务器。
2.2 偶然性困难(Accidents)
困难并非软件本质所固有的,而是开发方法和环境的副产品。
示例如下。
| 偶然性困难 | 过去的问题 | 现代的解决方案 |
|---|---|---|
| Syntax errors(语法错误) | 机器语言/汇编极易写错 | 高级语言、IDE语法检查、自动补全 |
| Slow compilation(编译慢) | 大型程序编译需数小时 | 增量编译、并行编译、硬件提速 |
| Manual memory management(手动内存管理) | C语言malloc/free易出错 | 垃圾回收(GC)、Rust所有权系统 |
| Pointer errors(指针错误) | 野指针、内存泄漏 | 智能指针、安全语言设计 |
| Lack of debugging tools(缺乏调试工具) | 只能print调试 | 图形化调试器、日志框架、性能分析器 |
2.2.1 定义
偶然性困难源于当前技术、工具和实践状态的挑战。这些困难并非软件本质所固有,而是开发方法和环境的副产品。
2.2.2 本质性困难(Essence)和偶然性困难(Accidents)的对比
本质性困难(Essence)和偶然性困难(Accidents)的对比如下。
| 本质性困难(Essence) | 偶然性困难(Accidents) | |
|---|---|---|
| 来源 | 软件的根本性质 | 当前技术工具的局限 |
| 能否消除 | ❌ 不可消除 | ✅ 可以逐步消除 |
| 例子 | 设计算法、理解业务逻辑 | 语法错误、内存泄漏、编译速度慢 |
| 比喻 | 数学定理证明的困难 | 用纸笔 vs 用计算机辅助证明 |
我们现在尝试对下面几个进行归类。
- Moving from Assembly to Python(从汇编到Python)
Accident,消除底层语法细节、内存管理、指针错误等偶然困难。 - Domain-Driven Design(领域驱动设计,DDD)
Essence,直接应对软件的复杂性(Complexity)和不可见性(Invisibility)。 - Cloud Infrastructure(云基础设施)
Accident,消除硬件配置、部署、扩容等运维层面的偶然困难。 - Test-Driven Development(测试驱动开发,TDD)
Accident + Essence,但以Accident为主,因为如果是先写测试后编码那就是强迫开发者先明确需求(规约Essence),再实现。如果是自动化测试或者快速反馈那就是Accident。
2.2.2.1 更多练习
- 确定跨境税务计算引擎的交织业务规则
Essential(本质性),这是 Complexity(复杂性) 和 Conformity(一致性) 的直接体现:
• 跨境税务规则本身极其复杂,涉及多国法律交互
• 必须精确映射现实世界的非规则性(各国税法差异、例外条款)
• 无法被工具简化,只能靠领域专家理解和设计
• 属于"概念构造"的规约和设计,是软件的硬核 - 配置Kubernetes集群的YAML文件以确保高可用性
Accidental(偶然性),这是当前容器编排工具和配置格式的局限:
• YAML语法繁琐、易错是 工具/格式问题,非软件本质
• Kubernetes的配置复杂性是 当前技术栈的偶然状态
• 未来可能出现更直观的声明式界面或自动化配置工具
• 高可用性本身是Essential,但YAML配置方式是Accidental - 解决C++手动内存管理导致的"段错误"
Accidental(偶然性),这是 编程语言设计的局限,非软件固有:
• 手动内存管理是C++的 语言特性选择(追求性能)
• 现代语言(Java、Python、Rust)通过GC或所有权系统 消除 此类错误
• 段错误是 表达层面的故障,非概念构造的困难
• Brooks原文例子:内存管理是典型的Accidental Difficulty - 梳理医院管理系统三位不同利益相关者的冲突需求
Essential(本质性),这是 Invisibility(不可见性) 和 Conformity(一致性) 的核心:
• 需求冲突源于 人类制度的不规则性(医院流程、科室利益、法规要求)
• 软件必须 顺应 这些复杂的人类组织,无法简化
• 利益相关者的"心理模型" 不可见,难以对齐
• 属于规约(Specifying)阶段的本质困难,无法自动化 - 优化Python脚本语法以在特定 legacy 处理器上更快运行
Accidental(偶然性),这是 硬件环境和解释器实现的局限:
• Python解释器性能、处理器架构是 外部技术条件
• 更换硬件或使用JIT编译器(如PyPy)可能 消除 此需求
• 语法优化是 表达层面的调优,非算法设计本质
• 若使用更现代硬件或编译技术,此困难不复存在
2.3 构建软件是困难的
最困难的不是写代码(表达),而是想清楚要做什么、如何设计、以及验证设计是否正确(概念构造的规约、设计和测试)------这正是软件的Essence(本质),也是为什么"没有银弹"的根本原因。
2.4 小结
- 软件复杂性导致哪些困难?
| 困难 | 英文 | 核心问题 | 现实后果 |
|---|---|---|---|
| 沟通困难 | Communication | 团队成员无法共享对系统的完整理解 | 知识孤岛、误解需求、设计冲突 |
| 状态理解不可靠 | Understanding all states, unreliable | 人脑无法穷举所有可能状态 | 遗漏边界情况、测试覆盖不足 |
| 功能扩展副作用 | Extending functions without side-effect | 修改一处影响多处 | 回归bug、架构腐烂、不敢重构 |
| 隐藏状态安全陷阱 | Hidden states that constitute security trapdoors | 未预料的状态成为攻击入口 | 安全漏洞、数据泄露、系统被攻破 |
- 为什么软件被视为最容易顺应/妥协的元素?
| 原因 | 解释 |
|---|---|
| Most recent arrival(最晚出现) | 软件是技术栈中的"新来者",硬件、制度、流程已存在数十年 |
| No physical form(无物理形态) | 不像硬件有实体约束,软件被认为"改改代码就行" |
| Malleability perception(可塑性认知) | 软件不像建筑或机械,看似可以无限修改而不产生物理成本 |
| Business pressure(业务压力) | 组织倾向于要求软件适应流程,而非改变流程适应软件 |
- 定义本质性困难和偶然性困难,各举两例
- Essential Difficulties(本质性困难)
定义:软件固有的、不可消除的困难,源于软件作为抽象概念构造的本质属性(复杂性、一致性、可变性、不可见性)。无论工具如何进步,这些困难永远存在,只能管理,无法消除。
| 例子 | 说明 |
|---|---|
| 1. 设计跨境税务系统的业务规则 | 必须精确映射多国复杂税法(Conformity),涉及无数例外和交互(Complexity),无法被工具简化 |
| 2. 协调医院系统利益相关者的冲突需求 | 不同科室流程冲突源于人类组织复杂性(Invisibility),软件必须顺应而非简化 |
- Accidental Difficulties(偶然性困难)
定义:源于当前技术、工具、方法局限的困难,非软件固有属性,可以随着技术进步逐步消除或大幅改善。
| 例子 | 说明 |
|---|---|
| 1. 手动内存管理导致的段错误 | C++需要手动malloc/free,现代语言(Java/Rust)通过GC或所有权系统已消除此类错误 |
| 2. 配置Kubernetes的YAML文件 | 格式繁琐易错是当前工具局限,未来可能出现更直观的声明式界面或自动化配置 |
3. 成功技术
这些技术确实带来了显著进步,但Brooks指出它们只解决了偶然性困难(Accidental Difficulties),而非本质性困难(Essential Difficulties)。
- High Level Languages(高级语言)
最重要的生产力发展,减少偶然复杂性。 - Time Sharing and Development Interactivity(分时系统与开发交互性)
即时响应使开发者能够集中注意力。 - Unified Programming Environments(统一编程环境)
如Unix,提供工作台和工具集。
3.1 潜在银弹
- 更好的高级语言?
1986年已有Fortran、C、Pascal等成熟高级语言。从汇编到高级语言是巨大飞跃,但进一步改进收益有限。语言只能改善表达形式(Accidental),无法帮助概念设计(Essential)。因此不是银弹------最多再提升20-30%,不可能10倍。 - 面向对象编程?
1980年代Smalltalk、C++兴起,被寄予厚望。通过封装、继承、多态管理复杂性,但不消除复杂性。"面向对象编程在某些领域有帮助,但不是突破性的"。 1990年代OOP普及,确实未带来10倍提升。 - 人工智能?
1980年代专家系统、第五代计算机 hype。对AI作为"银弹"持强烈怀疑态度。AI能辅助编程,但无法替代人类概念设计。1980s AI寒冬到来;2020s大模型仍未能实现10倍提升。
大语言模型显著提升了编码效率(消除Accidental困难),但无法替代概念设计、架构权衡和需求理解(Essential困难)------因此不是"银弹",而是Accidental消除工具的最新进化;这验证了Brooks论断的持久生命力:软件的Essential困难是固有的,技术进步只能管理,无法消除。 - 专家系统?
1980年代AI高潮,专家系统被视为"知识工程"的突破。将领域专家知识编码为规则,自动解决复杂问题。软件开发不是稳定领域,规则难以提取和维护。软件设计需要创造性判断,非机械规则应用。 - "自动"编程?
"自动编程"是误导性标签,掩盖了真正的困难。每代新语言都声称是"自动编程",实则只是更高层抽象。规格说明(specification)本身就是最难的部分。 - 图形化编程?
用图形替代文本,降低编程门槛,提升可视化。软件本质是概念构造,非空间构造。图形适合表示空间关系,不适合表示抽象逻辑。 - 程序验证?
用数学方法形式化证明程序正确性,消除bug。验证规格说明的正确性比验证代码的正确性更难。规格说明本身就是不精确、不完整的。 - 环境与工具?
IDE、调试器、版本控制、性能分析器等工具成熟。这些确实提升了效率,但边际效益递减。工具只能加速表达和测试,不能加速思考和设计。 - 工作站?
个人工作站性能快速提升,取代大型机/终端。这是硬件进步,属于外部技术,非软件方法突破。硬件速度提升 vs 软件生产力提升是不同维度。
4. 未来方向
4.1 购买
不要自建,要购买,购买现成的大众市场产品。这样即时交付,错误更少,而且低成本,成本由众多用户分摊。、
示例:
- API-driven Development(API驱动开发)
不自建功能,调用第三方API。例如支付(Stripe)、地图(Google Maps)、短信(Twilio)、AI(OpenAI)。
这样即时集成、专业维护、按需付费。
但需要注意供应商锁定、数据隐私、API变更。 - Software Framework(软件框架)
不从零写基础设施,复用成熟框架。例如Web开发(React/Django/Spring)、机器学习(TensorFlow/PyTorch)。
最佳实践内置、社区支持、快速启动。
需要注意权衡,因为框架选择本身是Essential决策。
4.2 需求精炼与原型
构建软件系统最困难的部分是精确决定要构建什么。
我们不可能完整、精确、正确地指定确切需求。
但我们可以迭代式地决定"精确要构建什么"。
这一思想已成为现代敏捷开发和产品管理的核心基石。
4.2.1 Incremental Development(增量开发)
我们可以"生长"软件,一步步来,而非试图一次性构建复杂系统。
软件不是可以一次性设计完美的建筑,而是必须持续演化的有机体;增量开发通过小步快跑、持续交付来管理Changeability和Complexity等本质困难,这一思想已成为现代敏捷、DevOps和微服务架构的哲学基础。
4.3 卓越设计师
伟大的设计来自伟大的设计师。
伟大的设计带来更快、更小、更简单、更干净、花费更少努力的设计。
伟大的产品来自一个或少数几个设计头脑。
因此软件工程的根本是人才培养,而非技术堆砌。
4.4 Brooks理论当代相关性
持久的洞见: relevant 尽管技术进步,Brooks识别的核心挑战依然相关。
练习:
- Agile(敏捷开发)应对Essential Difficulties
| 本质困难 | Agile策略 | Brooks理论呼应 |
|---|---|---|
| Complexity(复杂性) | 迭代拆分、用户故事、限界上下文 | "Growing software bit by bit" |
| Conformity(一致性) | 用户参与、产品负责人代表业务 | "Requirements refinement" |
| Changeability(可变性) | 拥抱变化、短周期迭代、重构 | "Incremental development" |
| Invisibility(不可见性) | 可工作软件、原型、可视化看板 | "Prototyping makes requirements visible" |
- AI-Enhanced Tools(AI增强工具)应对Essential Difficulties
| 本质困难 | AI工具能力 | 局限(Brooks验证) |
|---|---|---|
| Complexity(复杂性) | 辅助代码生成、模式识别 | ❌ 无法替代架构设计决策 |
| Conformity(一致性) | 代码规范检查、文档生成 | ⚠️ 外部系统适配仍需人类 |
| Changeability(可变性) | 自动化重构、测试生成 | ⚠️ 变更影响分析仍需人类判断 |
| Invisibility(不可见性) | 代码解释、可视化 | ⚠️ 需求理解仍需人类沟通 |
5. 小结
本质性困难是软件固有的障碍。叫本质的原因是因为即使你有完美的计算机,这些问题依然存在。
比如理解税法逻辑、设计微服务通信策略;它们是软件作为人类概念构造的固有属性,需要人类智慧去解决,技术只能辅助,无法替代。
偶然性困难是因为我们今天构建软件的方式而遇到的障碍,不是问题本身的一部分,只是我们当前技术的局限。
比如手动内存管理、编译慢、IDE难用;它们不是软件本身固有的,随着技术进步(更好的语言、更快的编译器、云计算、AI工具)会被逐步消除,让我们能更专注于那些无法被工具解决的真正困难(本质性困难)。