代码会写完,但软件不会:重读《What Is Software Engineering?》
AI 让代码更容易生成,却没有取消长期维护、多人协作和承担系统后果的问题;重读软件工程经典章节,理解系统如何持续演进。

本篇目录 (9)
阅读主线:AI 正在降低把想法翻译成代码的成本,却没有取消长期维护、多人协作以及对系统后果负责的要求。重读 Titus Winters 撰写、Tom Manshreck 编辑的《What Is Software Engineering?》,可以把今天的 AI 编程放回时间、规模与取舍的坐标系里。
如果只看今天的产品演示,软件开发似乎已经进入了一个有些魔幻的阶段。描述需求,模型开始写代码;报错了,把错误丢回去;界面不好看,再补一句要求。经过几轮修改,一个过去可能要花更多时间搭起来的应用已经能运行。于是一个自然的问题出现了:既然代码越来越容易生成,我们为什么还需要学习软件工程?
Google 的这章给出的答案很简洁:“Software engineering is programming integrated over time.” 软件工程,是把编程放到时间轴上。原章围绕时间、规模和取舍展开;这些问题早于 Vibe Coding 和 Coding Agent,却因此更适合用来判断新工具带来的变化。AI 改变了写代码的方式,软件仍然要面对依赖升级、调用方增长、协作成本和失败后果。阅读原章:What Is Software Engineering?
一、编程解决“现在能不能跑”,软件工程还要回答“以后怎么办”
写一个 Python 脚本,把一批数据转换一次,然后删除。它通常不需要为五年后的 Python 版本升级设计完整的兼容层,也不需要为未来的调用方准备公开 API。一次性任务的生命周期短,许多长期风险还没有机会变成现实。
但支付系统、数据库中间件、开发者工具,或者准备持续维护多年的 SaaS,面对的是另一种问题。依赖会升级,操作系统和语言会变化,需求会移动,团队成员会离开,新成员会加入,曾经无人使用的接口也可能成为核心入口。代码本身未必突然“变坏”,时间只是让过去隐藏的假设逐渐显现。
原章把这种能力称为软件的 sustainability:真正重要的不是永远不修改,而是当值得修改的事情出现时,系统仍然有能力响应。预期寿命越长,升级、迁移、理解和验证就越不能靠运气。寿命很短的工具也不必套用大型组织的完整流程,工程投入应与变化概率、失败代价和维护能力相称。
这件事放到 Vibe Coding 时代,会变得格外明显。以前一个糟糕的原型往往不会长得太快,因为写代码本身会让人停下来判断它是否值得继续。现在,一个原本只准备验证一天的 Demo,可能很快就有登录、数据库、支付、Dashboard 和部署脚本。随后,别人自然会问:既然已经能用,为什么不能直接上线?
原型成长为产品的速度,可能超过工程化速度。 Vibe Coding 的风险因此不只在于模型会写错代码,也在于它让人很容易跨过“短期程序”和“长期软件系统”之间的边界,却没有及时改变验证、文档、迁移和责任安排。
二、“能运行”从来不是一个特别高的标准
软件开发里有一句危险的话:“反正现在能跑。”原章区分了两个状态:代码只是碰巧工作,还是具备长期维护的条件。两者都可能通过今天的演示,但对未来变化的承受力不同。
其中最经典的解释是 Hyrum 定律:只要一个 API 的用户足够多,系统任何可以被观察到的行为,最终都有可能被某个人依赖,哪怕那个行为从未写进 API 文档。
假设一个接口返回 A, B, C。文档只承诺返回三个元素,没有承诺顺序,但某个调用方写下 result[0],并默认第一项永远是 A。几年以后,服务端优化底层数据结构,返回 B, A, C。从公开契约看,返回集合没有变化;对调用方来说,默认值却变了。
这也是 Java HashMap 与 Go map 的顺序行为值得反复提醒的原因:两者都没有为这种容器提供可以依赖的固定遍历顺序。一次运行里碰巧看到的顺序,不能直接升级成跨版本、跨机器的接口保证。Java HashMap 官方文档;Go 语言规范:range。
到了 Coding Agent 时代,隐式依赖还可能传播得更快。Agent 会观察已有代码并模仿它;一个没有文档说明的约定,可能被复制到很多新模块。过去一个程序员偶然依赖 undocumented behavior,可能只产生一个调用点;现在自动化生成会把错误假设扩散到更大的依赖图里。
所以接口契约、类型系统、测试、静态分析和清晰的模块边界并没有过时。以前它们主要约束人,现在也同时约束机器。越容易生成代码,越需要先说清楚什么行为值得承诺。
三、当代码越来越便宜,我们反而要警惕“代码通胀”
原章讨论过一个很值得玩味的工程故事:早期本地构建变慢之后,Google 建设分布式构建系统,减少等待并提高构建能力;随后,快速构建也可能削弱开发者对依赖膨胀的即时感受,让增加大型依赖变得更容易。构建效率提高,不会自动保证系统规模得到控制。原章把这个现象联系到 Jevons Paradox:资源使用效率提高以后,总消耗量反而可能增加。原章相关讨论
这里的经济学类比需要保留边界。它说明一种值得观察的激励关系,并不意味着所有效率提升都会必然造成浪费。放到 AI Coding 上,代码生成变快确实可能让更多模块、helper、配置和 glue code 被纳入系统,也可能让团队更快删掉无效尝试;结果取决于约束、审查和系统边界。
过去写一大段代码需要投入较多时间,工程师往往会更早判断抽象是否值得。现在 Agent 可以快速生成大量实现,真正需要警惕的就从“功能写不出来”转向“功能维护起来贵不贵”。代码产量越来越不适合作为生产力指标;更有意义的问题是,几个月后团队能否安全修改这些代码,能否解释依赖关系,能否在升级和故障中保留选择空间。
于是“代码便宜不等于维护便宜”更像一个需要用构建时间、依赖图、缺陷率、Review 成本和恢复时间去检验的判断。生成能力提升的价值,取决于团队能否同时观察这些指标,并持续清理不再值得保留的复杂性。
四、从《人月神话》到多 Agent:规模问题从来没有消失
软件工程几十年来一直在面对一个问题:规模扩大以后,效率会不会线性增长?增加程序员不等于开发速度等比例提高,因为人与人之间存在沟通、协调、集成和理解成本。原章同样强调,软件工程不仅要考虑软件本身能不能 scale,还要考虑生产软件的组织能不能 scale。
到了今天,这个问题有了新的版本:如果一个 Agent 很快,同时开十个 Agent,是不是就快十倍?Agent 可以并行,但协调成本也会进入系统:两个 Agent 修改同一个模块怎么办;一个 Agent 改了 API,另一个仍按旧 API 工作怎么办;谁判断它们是否重复造轮子;谁检查局部最优决策在全局上是否冲突;大量提交进入仓库后,谁负责高价值 Review?
协调对象于是从 Human ↔ Human 延伸为 Human ↔ Agent ↔ Agent ↔ Codebase。多 Agent 没有消灭软件工程,只是产生了一种新的组织结构。没有任务边界、共享规范、测试体系、版本控制、Ownership 和清晰的验证机制,增加 Agent 可能与盲目增加工程师相似:局部吞吐量提高,全局协调成本也跟着提高。
五、Shift Left,在 Agent 时代反而更重要
原章谈到 Shift Left:尽量更早发现问题。需求阶段发现设计问题,通常比上线后发现便宜;编译阶段发现类型错误,比生产环境发现容易定位;Review 或 CI 中发现安全风险,也比事故发生后修复更可控。它是软件开发中逐步形成的通用工程原则,不依赖某个特定时期的流行说法。原章对 Shift Left 的讨论
过去的流程大致是:
人写代码
↓
检查代码
↓
发现问题
↓
修改
Agent Coding 让更理想的流程变得可行:
定义约束
↓
Agent 生成
↓
自动验证
↓
不满足约束
↓
Agent 修正
↓
人工检查高价值决策
区别在于,约束被提前放进生成入口。测试、lint、类型、接口契约、仓库规范、任务验收条件、CI,以及 Agent 自己执行的验证脚本,都可以成为反馈循环的一部分。与其等 Agent 写完大批实现后再说“架构不对,重新来”,不如让它在生成前就能读取边界,在生成后立即得到可执行的失败信息。
OpenAI 后来介绍 Codex 编排系统 Symphony 时回顾:此前团队让仓库中的代码由 Codex 生成,并为此建设 agent-friendly repository、自动化测试和 guardrails;当互动会话和 context switching 成为瓶颈后,Symphony 把任务看板变成编排控制面,由多个 Agent 持续处理工作,人类 review 结果。OpenAI:Harness engineering;OpenAI:An open-source spec for Codex orchestration: Symphony
这也说明,代码是不是人写的已经不是唯一关键问题。更重要的是,代码进入系统之前经历了什么约束,进入系统之后能否被验证、观察和恢复。
六、软件工程从来不是“最佳实践大全”,而是权衡
软件工程很容易被学成一套戒律:一定要微服务、TDD、领域驱动设计、ADR、Clean Architecture 或高覆盖率。但原章反复强调的第三个关键词是 trade-off。工程成本不只有服务器费用,还包括工程师时间、计算资源、行动成本、机会成本,以及失败可能带来的社会成本。
同一种技术行为,放在不同的软件寿命、团队规模和风险条件下,答案会不同。一个周末 Demo 使用 SQLite 可能很合理;一个多人协作、预期维护十年的核心系统没有测试,就需要认真评估风险。一次性内部脚本也可以更轻量,但仍要看它处理的数据是否敏感、是否具有破坏性、是否会被误当成长期工具。所谓“随便一点”必须建立在可重建、可隔离和失败代价可接受的条件上。
| 判断条件 | 可以接受的轻量方案 | 需要增加工程投入的信号 |
|---|---|---|
| 软件寿命 | 一次性任务、短期原型、容易重建 | 预期长期运行,或需求会持续变化 |
| 依赖规模 | 少量明确调用方,影响范围可快速确认 | 公共 API、多团队依赖、隐式行为已扩散 |
| 失败代价 | 可丢弃、可重算、无敏感副作用 | 权限、账务、数据迁移或生产事故 |
| 修改方式 | 重新生成比维护更便宜 | 需要兼容、迁移、审计和回滚 |
因此,Vibe Coding 不能简单归结为“好”或“坏”。应该问:软件准备活多久,多少人会依赖它,出错的代价是什么,修改频率有多高,重新生成便宜还是长期维护便宜?软件工程没有要求每行代码都按照大型搜索系统的标准编写,它要求工程师明确自己正在接受什么限制,以及限制失效时要付出什么代价。
七、Vibe Coding 最大的误解,是把 AI 写代码和 Vibe Coding 画等号
“使用 AI 编程”是一个很大的集合;Vibe Coding 更适合描述一种具体工作方式:较少阅读 diff 和内部实现,通过自然语言与错误信息连续驱动模型修改,直到结果能够运行。Andrej Karpathy 在 2025 年 2 月的原帖中正是以这种轻量、低风险、偏一次性的工作状态来描述它。Karpathy 原帖 它适合探索,但不能代表所有 AI 辅助工程。
一个工程师可以让 Agent 写下几乎全部代码,同时认真定义需求、Review 架构、运行测试、检查安全问题,并且愿意对最终系统负责。这样的工作方式已经更接近有约束的 Agentic Engineering,而非最初意义上的 Vibe Coding。Simon Willison 后来用 Vibe Engineering 区分不同程度的审查与责任,并在 2026 年 2 月的更新中讨论了 Agentic Engineering 这一说法;名称可以变化,边界仍然是:是否有人对最终系统负责。Simon Willison:Vibe Engineering / Agentic Engineering
软件工程的核心从来不是代码必须由人亲手敲出来。汇编器、编译器、IDE、搜索工具、Copilot 和 Coding Agent 都在降低把想法翻译成机器可执行代码的成本,工具层可以不断向上抽象,责任却没有因此消失。
模型可以决定一个函数怎么写,可以帮助重构模块,甚至完成一次完整功能开发,但仍需要有人回答:为什么要这样设计;这个依赖值不值得引入;这个 API 三年以后还能不能改;这个测试到底证明了什么;这个失败是实现问题还是需求本身有问题;系统上线以后谁来承担后果。这些问题构成了工程判断。
八、AI 没有消灭软件工程,只是重新划定工程师的工作边界
把过去几十年的软件开发历史放在一起,会看到每一次抽象层提高,都有人担心程序员会变得不重要。高级语言减少了手写汇编,垃圾回收减少了手工管理内存,云计算和 Serverless 隐藏了部分服务器运维。Coding Agent 则开始把代码本身变成一种更容易生成和替换的中间产物。
抽象层每提高一层,下面的问题并没有消失,只是工程师开始负责更高一层的问题。过去最稀缺的是把需求实现成代码的能力;未来更稀缺的可能是判断什么值得实现,把模糊需求变成清晰约束,设计能够持续变化的系统,建立可以判断“完成了没有”的验证体系,并知道什么时候应该让 Agent 自由发挥,什么时候必须给它设定护栏。
借用原章的表达,我更愿意把它理解为:
Programming is producing code. Software engineering is taking responsibility for that code over time.
(这两句是本文对原章观点的改写。)
编程是在产生代码。软件工程,是在时间中承担代码带来的后果。
当代码不再稀缺,决定软件质量的就越来越不是谁写得快,而是谁能让一个系统今天能跑,半年以后还能改,换一个人或模型还能看懂,依赖升级时不至于全线崩溃,需求改变后也不用推倒重来。
几十年前,软件工程试图解决的是:人越来越多、代码越来越多以后,怎样继续制造可靠的软件。今天的问题正在变成:当代码可以近乎无限地产生以后,怎样避免制造无限的复杂性。工具已经换了一轮,问题却依然是那个问题。这大概就是为什么,一本讨论传统软件工程的书,在 Vibe Coding 盛行的今天,仍然值得重新翻开。
参考资料
- What Is Software Engineering?:Titus Winters 撰写、Tom Manshreck 编辑;时间、规模、取舍、Hyrum 定律和 Shift Left 的原章来源。
- Karpathy 原帖:Vibe Coding 这一工作方式的原始语境。
- Simon Willison:Vibe Engineering / Agentic Engineering:用于区分探索式 Vibe Coding 与承担系统责任的工程工作。
- OpenAI:Harness engineering:用于讨论 agent-friendly repository、约束、自动化测试与反馈循环。
- OpenAI:An open-source spec for Codex orchestration: Symphony:用于讨论 context switching、任务编排和多 Agent 协调。
- Java
HashMap官方文档;Go 语言规范:range:用于核对集合遍历顺序的契约边界。
正文四张小黑插图使用 AI 生成,用于解释概念隐喻,不代表实际系统架构。