文章搜索

代码会写完,但软件不会:重读《What Is Software Engineering?》

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

代码会写完,但软件不会:重读《What Is Software Engineering?》

阅读主线: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 的风险因此不只在于模型会写错代码,也在于它让人很容易跨过“短期程序”和“长期软件系统”之间的边界,却没有及时改变验证、文档、迁移和责任安排。

小黑蹲下给一座临时纸房垫上支撑石,房子的根部延伸向一页日历,表示原型寿命延长后维护责任随之增加。
图 1:原型一旦进入长期使用,寿命会把维护责任带进来。

二、“能运行”从来不是一个特别高的标准

软件开发里有一句危险的话:“反正现在能跑。”原章区分了两个状态:代码只是碰巧工作,还是具备长期维护的条件。两者都可能通过今天的演示,但对未来变化的承受力不同。

其中最经典的解释是 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 成本和恢复时间去检验的判断。生成能力提升的价值,取决于团队能否同时观察这些指标,并持续清理不再值得保留的复杂性。

小黑自身像摇柄线轴,吐出代码纸带并纠结成需要拆解的维护线团,表示产量增加可能带来复杂性膨胀。
图 2:生成速度提高以后,复杂性也可能更快积累。

四、从《人月神话》到多 Agent:规模问题从来没有消失

软件工程几十年来一直在面对一个问题:规模扩大以后,效率会不会线性增长?增加程序员不等于开发速度等比例提高,因为人与人之间存在沟通、协调、集成和理解成本。原章同样强调,软件工程不仅要考虑软件本身能不能 scale,还要考虑生产软件的组织能不能 scale。

到了今天,这个问题有了新的版本:如果一个 Agent 很快,同时开十个 Agent,是不是就快十倍?Agent 可以并行,但协调成本也会进入系统:两个 Agent 修改同一个模块怎么办;一个 Agent 改了 API,另一个仍按旧 API 工作怎么办;谁判断它们是否重复造轮子;谁检查局部最优决策在全局上是否冲突;大量提交进入仓库后,谁负责高价值 Review?

协调对象于是从 Human ↔ Human 延伸为 Human ↔ Agent ↔ Agent ↔ Codebase。多 Agent 没有消灭软件工程,只是产生了一种新的组织结构。没有任务边界、共享规范、测试体系、版本控制、Ownership 和清晰的验证机制,增加 Agent 可能与盲目增加工程师相似:局部吞吐量提高,全局协调成本也跟着提高。

三个小黑拼接圆形木环,中央的小黑拿尺对齐接口,表示并行工作仍需要共享边界。
图 3:并行执行扩大局部产能,也要求接口和责任边界对齐。

五、Shift Left,在 Agent 时代反而更重要

原章谈到 Shift Left:尽量更早发现问题。需求阶段发现设计问题,通常比上线后发现便宜;编译阶段发现类型错误,比生产环境发现容易定位;Review 或 CI 中发现安全风险,也比事故发生后修复更可控。它是软件开发中逐步形成的通用工程原则,不依赖某个特定时期的流行说法。原章对 Shift Left 的讨论

过去的流程大致是:

人写代码

检查代码

发现问题

修改

Agent Coding 让更理想的流程变得可行:

定义约束

Agent 生成

自动验证

不满足约束

Agent 修正

人工检查高价值决策

区别在于,约束被提前放进生成入口。测试、lint、类型、接口契约、仓库规范、任务验收条件、CI,以及 Agent 自己执行的验证脚本,都可以成为反馈循环的一部分。与其等 Agent 写完大批实现后再说“架构不对,重新来”,不如让它在生成前就能读取边界,在生成后立即得到可执行的失败信息。

小黑把星形模板送入手摇压机,右侧量规检验结果,表示反馈应尽早进入生成流程。
图 4:把约束放到生成入口,反馈才能在复杂性扩大前发挥作用。

OpenAI 后来介绍 Codex 编排系统 Symphony 时回顾:此前团队让仓库中的代码由 Codex 生成,并为此建设 agent-friendly repository、自动化测试和 guardrails;当互动会话和 context switching 成为瓶颈后,Symphony 把任务看板变成编排控制面,由多个 Agent 持续处理工作,人类 review 结果。OpenAI:Harness engineeringOpenAI: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 盛行的今天,仍然值得重新翻开。

参考资料

  1. What Is Software Engineering?:Titus Winters 撰写、Tom Manshreck 编辑;时间、规模、取舍、Hyrum 定律和 Shift Left 的原章来源。
  2. Karpathy 原帖:Vibe Coding 这一工作方式的原始语境。
  3. Simon Willison:Vibe Engineering / Agentic Engineering:用于区分探索式 Vibe Coding 与承担系统责任的工程工作。
  4. OpenAI:Harness engineering:用于讨论 agent-friendly repository、约束、自动化测试与反馈循环。
  5. OpenAI:An open-source spec for Codex orchestration: Symphony:用于讨论 context switching、任务编排和多 Agent 协调。
  6. Java HashMap 官方文档Go 语言规范:range:用于核对集合遍历顺序的契约边界。

正文四张小黑插图使用 AI 生成,用于解释概念隐喻,不代表实际系统架构。