前提 — 软件为什么难,代码为什么必然被改
这一章讲三件事: 编程为什么没有一劳永逸的特效药; 为什么说「代码才是真正的设计文档」;以及为什么代码从写完那天起就注定被改。 这三条是全书的地基——后面九章的每一条原则,都在回应这一章留下的那个事实: 代码必然被修改,而修改很贵。
1. 先看现象:为什么提升总是卡在某个坎上
很多人学完一门编程语言的语法(语言的书写规则),能写出能跑的代码了,然后就会停在一个尴尬的位置: 代码能跑,但不听话——发布后频频出故障,隔几个月自己都读不懂,想加个功能不知道从哪里下手1。
原作者上田勋写这本书,就是给卡在这个位置的人看的。他的判断是:这个坎靠工具和语法书迈不过去。 初学者教材只教语法,大师著作又门槛太高;遇到好老师、好代码全凭运气。 中间缺的那块东西,他叫编程的原则——不是某种具体技术,而是「帮助程序员成长的捷径」2。
这里有一个贯穿全书的读法,先立在这:
原则(为什么做)──决定──→ 技术怎么做(结构化编程、面向对象、函数式……)
↑ │
└── 技术会过时消亡,原则不会 ←──┘
图说:技术是为实现原则的目的而诞生的;能满足原则的技术留下来,
满足不了的消失。原则是慢变量,技术是快变量。
作者的原话是:结构化编程、面向对象编程、函数式编程这些技术, 「都是为了更好地实现编程原则的目的而诞生的」;技术不断生灭, 「只有那些可以真正解决本质问题的技术留存到了今天」3。
这就是为什么一本 2016 年写的书,2026 年读不觉得过时:它押注在慢变量上。 这也解释了这本书奇怪的写法——通篇没有一行真实代码。作者自己解释: 给出代码范例,读者的理解就会被禁锢在范例里;把具体方案留给读者, 原则的适用范围才能铺开4。我们认为这个选择是对的,代价是抽象,收益是不随语言过时。
2. 没有银弹:软件难在本质上
这一节回答:为什么编程没有特效药——不是还没发明,是原理上不可能有。
「银弹」的典故来自欧洲民间传说:狼人看起来是普通人,只有银制子弹能杀死它。 软件工程借这个词指一击致命的万能方案。1995 年写《人月神话》的布鲁克斯给出的结论是: 编程领域没有银弹5。上田勋把这条列为全书第一条原则,并且给出了自己的展开: 不是大家不够努力,而是软件在本质上具有难度。
「本质」要讲清楚:本质是定义上无法舍去的东西——舍掉了,软件就不再是软件。 就像「会飞」对鸟是本质,对石头是多余的。软件的本质里有四种性质,每一种都直接制造难度6。
四个本质性质
① 复杂性。 软件是庞大且复杂的东西,几千万行的代码并不少见。 更要命的是各部分之间的依赖:软件规模扩大,要素之间的依赖关系呈非线性——增长得比规模还快: 规模翻一倍,牵一发动全身的连线远不止翻一倍。作者断言:软件比其他任何构造物都复杂7。
② 同步性。 软件不是单独存在的:它连着硬件、网络、其他软件、人的行动习惯, 在现实世界中被使用。现实世界变,它就得跟着变——现实世界的复杂性,决定了编程存在难度8。
③ 可变性。 这是最反直觉的一条:就算软件按计划顺利交付了,用户也会提出更多要求。 作者给的机制很干净:成品软件会改变世界——软件影响用户的认知,认知产生新的需求9。 用过智能手机的人都能体会:正是因为地图应用存在,你才会想到「能不能顺便看堵车」。 需求不是挖不完的矿,是软件自己造出来的。所以作者说, 「无法驻足于安宁之地是软件的宿命」10。
④ 不可见性。 软件是概念的集合体,概念是肉眼看不见的。 你可以把它画成界面、画成架构图,但画出来的每张图都做了舍象—— 把一部分信息扔掉了才能画。所以软件永远无法被完整地「看见」11。
判断(我们的,不是书里的): 这四条里,②③(同步、可变)是「软件会变」的根源, ①④(复杂、不可见)是「变了之后管不住」的根源。整本书后面九章, 实际上就沿着这两根轴展开:一半原则在管「怎么迎接必然的修改」, 一半在管「修改之后怎么不失控」。把四性质当成全书的地图,101 条就不会散。 如果错,会错在: 如果某条原则(比如本书讲的「经济原则」——给程序员买好设备) 既不回应变化也不回应复杂,那这四性质地图就只是个整理工具,不是原书的结构。
本质难,不代表没有抓手:本质与非本质
「难在本质」容易读成宿命论。作者马上补了关键一刀:事物都由本质部分和非本质部分构成。 构建环境、编程语言、库、框架——这些全是非本质的:换掉它们,软件还是那个软件12。
而非本质部分恰恰是最好改善的,其中成效最大的是自动化: 测试、构建、环境搭建的自动化,大幅改善了工作效率和质量。 作者的结论是一句分工令:把非本质的部分尽量自动化,把省下的时间留给本质部分13。
这条对今天的读者尤其好懂:这三十年的工具演进(持续集成——构建、测试、发布交给机器反复自动执行;云环境、今天的 AI 辅 助编程) 几乎全部落在非本质侧。工具让人失望,不是因为工具没用,而是因为它们天生只能解决非本质的那一半。 重复出现的 bug、读不懂的逻辑、改不动的设计,仍然要靠下一章开始的原则去对付。
3. 代码即设计:制造是编译器的事
这一节回答:编程在整个工程里算哪一步——它的答案推翻一个流行了几十年的类比。
先看那个类比错在哪
硬件的工程流程很直观:先设计,画出设计图;然后照着设计图制造。 把这个类比搬到软件上,就成了很多人默认的分工: 上游工程师写设计文档(设计),程序员照着文档写代码(制造)。
作者的判断是:这个类比错在把制造安错了位置。 软件真正的「制造」环节是编译和构建——把代码变成可执行文件的那一步, 负责制造的不是程序员,而是编译器和构建系统(编译器:把人写的代码自动翻译成机器能执行的文件的工具)14。 那么从需求到代码的整条链——基本设计、详细设计、编程、测试、调试——全部属于设计。 而设计的输出物是什么?是代 码。所以:
在软件开发里产生的所有文本中,能真正称得上工程文档的,只有代码。15
为什么上游文档定不下来
这个判断还有一个证据链。如果上游文档真是设计图,那它应该在设计阶段就能定稿。 实际上呢?很多东西只有开始编程之后才能搞清楚——上层设计文档必须等到编程结束才能确定下来16。 写代码的过程会不断反过来修正设计,这两件事根本分不开。 所以让一个人写文档、另一个人写代码,等于让设计者远离自己的设计。
主走查:把一个功能从立项走到发布,看每一步谁在设计
拿一个具体功能走一遍:「电商网站的商品页要显示打折价」。 (这个例子是我们编的场景,数字也是编的,原书没有电商,原文的论断适用于任何功能。)
步骤 发生了什么 属于设计还是制造
─────────────────────────────────────────────────────────────────
需求文档 「显示会员九折」六个字 设计(很粗的设计)
基本设计 「原价划掉,折后价标红」 设计(仍然粗:
到这一步还不知道小数位怎么取整 舍象掉了实现细节)
编程开始 写完三行代码发现:券和折扣可以叠加,
而需求文档没说能不能叠加 设计在反哺上游
代码定稿 87 行,叠加规则、取整、四舍五入全在里面 设计的最终产物
构建/编译 一条命令,2 分钟 制造(机器干的)
发布 上线 制造
图说:90% 的时间在设计,真正的「制造」只要一条命令。
所以读懂 87 行代码,才是理解这个功能的唯一途径——这就是下一章的起点。
这张表里真正承重的是中间那行:设计上的漏洞是在写代码时暴露的(折扣能不能叠加)。 这正是作者说「尽早开始编写代码」的理由——不写代码,设计只会无限拖延下去17。
代码是设计,不等于不需要别的文档
容易读歪的地方要当场掰正。作者明确说:代码不是唯一的文档。 最能说明问题的是敏捷开发——很多人以为它轻视文档,实际上它只是不生成无用的文档18。 作者推荐了一份叫「罗塞塔石碑」的文档(借自那块让考古学家读懂古埃及文字的石碑): 写给未来维护负责人的简短手册,讲清构建与测试怎么执行 、软件架构长什么样19。
它存在的理由是代码有一个真实短板:代码能表达「怎么做」和「是什么」,表达不了「为什么」。 当初为什么选方案 A 不选方案 B,代码里没有——只能靠注释和文档补20。
4. 代码必然被修改:全书的地基
这一节回答:前面两条怎么汇成一个行动准则。
前两节是「难」和「设计」;这一节只有一句话的结论,却是全书的支点: 代码不是写完就结束的,它在日后必然会被修改。没有写完就扔的一次性代码21。
为什么必然?把第 2 节的可变性展开就是完整论证:
- 软件在本质上有复杂性 → 不可能完美无缺 → 发布后必然发生故障,要修;
- 有些问题只有用户真用起来才会被发现 → 发布后必然有新需求;
- 用户的商务环境本身在变(公司转型、法规改了、竞争对手出了新东西)→ 需求又变22。
作者还补了一个容易被忽略的角度:开发过程本身也是一连串修改—— 给昨天的代码加新功能、为了让它更好读而重构(不改软件对外表现、只整理内部结构), 第一个迭代——「开发→可用」的一次完整循环——写出的代码,在下一次迭代里被推翻重写23。 修改不是软件生命的一个阶段,修改就是软件生命本身。
由此得出全书的总准则:编程中的任何一个判断,都要以「代码会被修改」为前提。24
这条准则立刻给出一个反直觉的优先级。作者说,代码这种东西,读远比写费时间; 如果接受了「代码会被改」,那么写的时候多花时间没关系,只要读的时间能缩短,就赚回来了25。 「怎么让代码读得快、改得动」——这个问题从下一章开始,将占据全书九成的篇幅。
5. 作者的判断与证据
书里给了证据的:
- 四个本质性质的论证出自布鲁克斯《人月神话》,这是软件工程史上被引用最多的论文之一,作者逐条沿用5;
- 「代码即设计」出自 Robert C. Martin 的《敏捷软件开发》,是敏捷运动的核心论断之一26;
- 前言里「技术为原则而生、生灭不息」是作者的演化史视角,有出处链支撑(全书 101 条各标出处),但没有实证数据。
作者的推测,要分开看的:
- 「成品软件会改变用户认知、认知产生新需求」——这是一个机制性猜想,方向上符合经验,但书里没有给数据;
- 「非本质部分改善成效最大的是自动化」——1995 年布鲁克斯的原文把「高级语言和 AI」列为有希望的方向,上田勋 2016 年把「自动化」推到 C 位,这是他基于这二十年的判断,不是布鲁克斯的原话。
6. 边界与局限
- 四性质不是全部。 布鲁克斯原文还讨论了「复杂性没有等级之分」等推论,本书只取了四性质的清单式版本;想看完整论证要回到《人月神话》(本库书架暂未收,待收后可补)。
- 「代码即设计」有适用边界。 它的隐含语境是「持续维护的软件」;一次性脚本、竞赛代码不在此列。书里没有讨论这种例外。
- 「原则不过时」要打折听。 原则本身确实慢,但每条原则的「为什么」依赖当时的成本结构。比如本章「读比写贵」在 AI 辅助阅 读时代会不会翻转,书写作时无法预见。我们把它写成判断而非事实。
- 原书明确声明「本书内容由作者独自研究完成」,即各章的取舍与编排是个人判断,不是学科共识的综述27。
7. 可带走的
- 遇到「XX 新工具能终结一切痛点」的宣传,先想起银弹:工具只能动非本质的部分;
- 判断一个问题属于哪一半:换工具能解决的(环境、构建、重复劳动)→ 自动化; 换工具解决不了的(复杂、读不懂、改不动)→ 靠原则,老老实实改设计;
- 别把编程当「照图施工」:从需求到代码每一步都在设计,写代码的人就是设计者;
- 设计定不下来就先写代码——很多设计问题只有写的时候才会暴露;
- 给代码补「为什么」:注释和文档的真正职责不是复述代码,是记下代码说不出口的设计理由;
- 每个决定都假设它将来会被改——这是后面九章所有建议的总前提;
- 学技术先问它服务哪条原则:技术是快变量,原则是慢变量,押注慢变量。
8. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 卡在坎上的人、写书动机 | 前言 | text/02-fm.txt:27(搜「到达一定程度」) · text/02-fm.txt:38(搜「更好的程序员」) |
| 技术为原则而生 | 前言 | text/02-fm.txt:52(搜「结构化编程」) · text/02-fm.txt:59(搜「消亡」) |
| 不给代码范例的理由 | 序章 本书导读 | text/04-fm.txt:177(搜「本书不提供代码」) |
| 原则相悖取更优解 | 序章 本书导读 | text/04-fm.txt:189(搜「相悖」) · text/04-fm.txt:197(搜「更优解」) |
| 没有银弹、狼人典故 | 第1章 前提 | text/05-ch01.txt:13(搜「狼人」) · text/05-ch01.txt:18(搜「万能解决方案」) |
| 四个本质性质 | 第1章 前提 | text/05-ch01.txt:23(搜「本质是指」) · text/05-ch01.txt:25(搜「复杂性」) |
| 复杂性:依赖非线性增加 | 第1章 前提 | text/05-ch01.txt:30(搜「非线性」) |
| 同步性 | 第1章 前提 | text/05-ch01.txt:37(搜「现实世界的大量事物」) |
| 可变性:成品改变世界 | 第1章 前提 | text/05-ch01.txt:45(搜「品软件会使世界发生改变」) · text/05-ch01.txt:47(搜「宿命」) |
| 不可见性 | 第1章 前提 | text/05-ch01.txt:51(搜「概念的集合体」) |
| 本质与非本质、自动化 | 第1章 前提 | text/05-ch01.txt:76(搜「非本质」) · text/05-ch01.txt:84(搜「自动化」) |
| 代码即设计、编译器制造 | 第1章 前提 | text/05-ch01.txt:113(搜「编译器或构建系统」) · text/05-ch01.txt:115(搜「工程文档」) |
| 上游文档等编程结束才能定 | 第1章 前提 | text/05-ch01.txt:126(搜「编程结束之后」) |
| 尽早写代码 | 第1章 前提 | text/05-ch01.txt:147(搜「无限拖延下去」) |
| 罗塞塔石碑、敏捷不轻文档 | 第1章 前提 | text/05-ch01.txt:153(搜「敏捷开发」) · text/05-ch01.txt:149(搜「罗塞塔石碑」) |
| 代码表达不了为什么 | 第1章 前提 | text/05-ch01.txt:162(搜「设计理由」) |
| 代码必然被修改 | 第1章 前提 | text/05-ch01.txt:182(搜「一次性代码」) |
| 修改的四种来源 | 第1章 前提 | text/05-ch01.txt:189(搜「发生故障」) · text/05-ch01.txt:193(搜「商务环境」) |
| 开发过程也是修改 | 第1章 前提 | text/05-ch01.txt:196(搜「新软件的编程过程」) |
| 读比写费时间 | 第1章 前提 | text/05-ch01.txt:206(搜「比写要费时间」) |