自动编程、规格与实现
Robert Balzer 在 1985 年的 A 15 Year Perspective on Automatic Programming 中讨论的,并不是今天常见意义上的“AI 自动写代码”。他真正关心的问题是:软件能否在更高抽象层上被描述、验证和变换,直到最后才落到代码层。
因此,这篇文章更适合作为软件工程中的“抽象与自动化”问题来阅读。它关心的不是某一个 compiler 有多强,而是从 high-level specification 到 implementation 的整条链条该如何组织,这一点和今天的 program synthesis、AI coding assistants、agentic software engineering 都有直接关系。
Balzer 扩展了什么叫 automatic programming
| 狭义理解 | Balzer 的扩展理解 |
|---|---|
| 把高级语言翻译成代码 | 从问题表达、规格获取、验证、变换到实现的完整链条 |
重点在 compiler |
重点在 specification、validation、transformation |
| 目标是更快生成程序 | 目标是让正确的软件在更高抽象层被逐步派生 |
Balzer 的关键洞见是:自动编程首先是一个 specification problem,其次才是一个 code generation problem。如果高层规格不完整、不正确、不可验证,那么再强的代码生成都只是在更快地产生错误实现。
这个扩展为什么重要
在很多日常讨论里,“自动编程”会被缩成一句话:从自然语言直接生成代码。但 Balzer 的视角更宽。他提醒我们,真正昂贵的不是最后一段翻译,而是前面这些问题:
- 需求是否已经稳定
- 领域知识是否已经显式化
- 规格是否具有可验证语义
- 变换过程是否保留了关键约束
如果这些条件没有成立,那么“自动生成代码”更像是在加速一个尚未被正确定义的问题。
从高层规格到程序
flowchart LR
A[Problem Description] --> B[High-Level Specification]
B --> C[Validation of Intended Meaning]
C --> D[Transformation to Lower-Level Specification]
D --> E[Automatic Compilation]
E --> F[Program]
这个流程图表达的是 Balzer 最有价值的一点:程序生成只是最后一段。真正的难点是前面几段是否能稳定成立,尤其是“用户真正想要的东西”能否被准确表达并持续验证。
一个形式化的链条视角
Balzer 的想法可以被写成一个逐层精化过程:
其中:
- \(S_0\) 是最初的高层规格
- \(S_i\) 是中间层规格或中间表示
- \(T_i\) 是一次语义受约束的变换
- \(P\) 是最终程序
这个式子的重点不在数学花哨,而在于:Balzer 假设“自动编程”应该是一串可检查的语义保持变换,而不是一个黑箱直接把模糊意图映射成代码。
如果写成工程语言,这意味着系统至少要回答三件事:
- 输入规格的语义是什么
- 每一步变换保留了哪些约束
- 最终程序如何被验证仍然满足初始意图
为什么这是软件工程问题,而不仅仅是编译器问题
为什么这是软件工程问题,而不仅仅是编译器问题
Balzer 的框架与传统编译器研究不同。编译器通常默认输入程序已经写对,任务是把一种形式变成另一种形式;而 Balzer 讨论的是在输入本身还不稳定时,怎样逐步逼近一个可执行、可验证的系统描述。
这使得它自然落在软件工程关心的几个核心问题上:
requirements acquisitionrequirements validationspecification refinementmaintenance与重新派生(re-derive)实现
这也是为什么 Balzer 看上去像“早期 AI 研究”,本质上却很接近今天的软件工程元问题。
可以换个角度看:编译器通常处理的是“已成型规格”之间的转换,而 Balzer 讨论的是“规格尚未稳定”时,人和系统如何一起把它塑形成可执行对象”。这一步恰恰是软件工程中最贵、最难自动化的部分。
中间表示为什么关键
如果自动编程不是黑箱,就需要中间表示(intermediate representation, IR)承担桥梁作用。这个 IR 不一定是编译器里的 SSA,也可以是:
- 类型化 API 契约
- 状态机模型
- 工作流 DSL
- 形式化测试预言(test oracle)
- 领域规则与约束集合
中间表示的重要性在于,它让“高层意图”第一次变成可被讨论、验证和变换的对象。没有这一步,系统就很难知道自己到底在生成什么。
| 阶段 | 典型中间表示 | 作用 |
|---|---|---|
| 需求整理 | 用户故事、用例、约束列表 | 明确目标和边界 |
| 规格层 | Schema、契约、状态机、DSL | 定义可验证语义 |
| 架构层 | 模块关系、接口、数据流 | 决定职责与耦合 |
| 实现层 | 代码骨架、测试、配置 | 把前面各层落地 |
这也是为什么今天很多强工程工作流都在强调 Schema first、API first、tests as executable specification。它们在做的事情,本质上与 Balzer 的方向相通。
这条路线依赖什么
从 Balzer 的前后工作来看,这条路线并不等于“用户一句自然语言,系统全自动完成”。它更依赖以下条件:
- 领域知识(
domain knowledge)足够明确 - 用户和系统之间存在交互式澄清(
interactive refinement) - 规格说明语言(
specification language)足够有表达力 - 系统能在规格层而不是代码层完成大部分演化
所以 Balzer 的真正方向更接近“人类引导的自动化(human-guided automation)”,而不是“黑箱式万能自动生成”。
规格获取为什么这么难
很多人第一次接触 Balzer,会以为瓶颈在“生成器还不够强”。但真正的困难往往更早出现:人并不总能一次性把自己想要的系统讲清楚。
常见的困难包括:
- 目标本身包含冲突,需要取舍
- 业务规则分散在口头知识和历史流程里
- 一部分要求只有在运行后才会暴露
- 不同利益相关方对“正确”的定义并不一致
因此,规格获取(requirements/specification acquisition)不是“把用户的话抄下来”,而是一个澄清、压缩、验证和协商的过程。
现代 AI 工作流和 Balzer 有什么关系
今天的 AI coding assistants、program synthesis、agentic software engineering 看上去离 Balzer 很远,但其实经常在重复他的基本问题。
一个典型工作流往往是:
- 用户给出自然语言目标
- 系统提取任务结构与约束
- 生成中间计划、测试、Schema 或接口
- 再生成实现与修复循环
这个流程如果有效,通常不是因为“模型魔法般理解了一切”,而是因为工作流里存在:
- 可检查的中间表示
- 可执行的反馈信号
- 能与人类反复澄清的接口
换句话说,越是稳定的 AI 工程流程,越不像“直接生成代码”,而越像 Balzer 所说的“人类引导的规格链条”。
如何判断一个自动化方案是否靠谱
如果一个自动化方案声称自己解决了高层到低层的全部问题,至少可以用下面几条来检验:
- 它是否明确了输入规格的语义
- 它是否显式地表示关键约束
- 它是否能解释中间变换而不是只给结果
- 它是否提供验证机制,而不是只给“看起来合理”的输出
- 它是否支持后续维护,而不是一次性生成后就失去可演化性
如果这些问题都没有答案,那么它更可能只是一个“强代码补全器”,而不是严格意义上的自动编程系统。
它和 Brooks 为什么互补
软件复杂度与银弹强调:软件最难的部分往往在规格、设计与复杂度控制,而不是纯粹编码。Balzer 则进一步说明:如果自动化想真正深入软件开发,就必须进入这些高层环节,而不能只停留在 implementation。
两者拼起来,会得到一个清楚的判断:
- 只会生成代码,不等于真正解决了自动编程
- 自动化越往上走,就越接近软件工程最难的部分
- 这也是为什么自动化讨论最终会落到
specification、验证和演化
Brooks 告诉你“难点在哪里”,Balzer 告诉你“自动化若要真正深入,就必须进入哪里”。两者共同指向一个结论:代码层自动化当然重要,但越想取得系统级收益,就越绕不开规格层。
维护与重新派生为什么重要
Balzer 讨论自动编程时,还有一个今天特别值得重看的点:软件不会只被生成一次。它会持续演化,所以系统必须支持从修改后的规格重新派生实现。
这意味着自动化若想真的进入工程主线,就必须考虑:
- 版本化规格如何管理
- 中间表示如何追踪变更
- 已有实现如何与新规格对齐
- 回归测试如何证明“重新派生”没有破坏核心行为
在这层意义上,自动编程并不是编译器的替代品,而是与版本控制与CI/CD、测试与质量保障深度耦合的工程体系。
三种典型失败方式
很多“自动编程”方案之所以最后没有走进主工程链路,往往是因为它们在下面几类地方失效:
1. 输入规格只是表面自然语言
如果输入只有一句模糊描述,没有领域词汇表、约束条件、边界案例和验收标准,那么系统即使生成了“看起来合理”的代码,也很难保证语义正确。
2. 中间表示不可检查
有些系统确实会生成 plan 或中间结构,但这些对象本身并不可验证,也没有稳定语义。这样一来,中间层就只是“更多文本”,而不是可治理的规格层。
3. 缺乏维护路径
第一次生成也许能工作,但只要需求变化,团队就必须重新手工接管。这样的系统更像一次性原型生成器,而不是可持续的自动编程体系。
这三种失败方式说明:自动编程真正难的地方,不在于第一次生成能不能出结果,而在于生成出的结果能不能进入长期维护闭环。
一个现代实现模板
如果把 Balzer 的思路映射到今天更现代的工程栈,一个相对稳健的实现模板通常会包含下面几层:
| 层 | 现代等价物 | 目的 |
|---|---|---|
| 意图输入 | 用户故事、工单、自然语言需求 | 提供问题背景 |
| 规格层 | Schema、接口契约、状态机、测试用例 | 固化语义与约束 |
| 计划层 | 任务分解、变更计划、依赖分析 | 把规格映射到可执行步骤 |
| 实现层 | 代码、配置、迁移脚本 | 生成具体系统变更 |
| 验证层 | 单测、集成测试、静态检查、回归基线 | 检查生成结果是否符合规格 |
这个模板的重要性在于,它让“自动编程”不再是一个黑箱承诺,而是一条可治理、可中断、可回滚的工程链路。
为什么“从提示到代码”容易被高估
今天最容易被高估的画面,是“用户写一句 prompt,系统直接产出可长期维护的软件”。这个画面吸引人,是因为它把最麻烦的中间环节都隐藏掉了。
但真正决定结果质量的,往往正是这些被隐藏的中间层:
- 需求有没有被澄清
- 约束有没有被显式表示
- 测试是否代表了关键语义
- 后续维护是否还能回到规格层
所以 Balzer 的提醒直到今天仍然非常实用:如果一个自动化系统把所有中间层都抹掉,你就应该默认它的工程可控性会比较弱。
为什么测试经常扮演“规格代理”
在理想情况下,团队会拥有清晰的形式规格;但在大量实际工程里,测试往往承担了“可执行规格”的角色。
这也是为什么很多现代自动化工作流都会把测试放在中心位置:
- 测试给出了机器可检查的行为约束
- 测试能为生成结果提供快速反馈
- 测试能把“看起来合理”与“真正满足要求”区分开
当然,测试不是完整规格。但在缺乏严格 DSL 或形式模型时,它常常是最接近规格层的工程对象。
一个现代阅读方法
今天重读 Balzer,最好的方式不是问“他是否已经实现了万能自动编程”,而是问:
- 他把问题定义完整了吗
- 他指出的中间层今天是否仍然存在
- 现代 AI 工作流是否真的绕开了这些中间层
多数情况下,答案会是:这些中间层并没有消失,只是换了名字。
一个最短判断标准
如果必须用最短标准判断一个系统离 Balzer 的设想有多近,可以问一句:
它是在生成代码,还是在治理规格链条?
前者当然也有价值,但后者才更接近自动编程的完整问题定义。
一个补充判断
如果一个系统只能在“需求已经几乎写成代码”的情况下工作,那么它更像高级实现工具;只有当它能帮助团队处理规格获取、约束表达和验证链条时,它才真正开始接近 Balzer 的问题域。
也因此,Balzer 最值得保留的不是某个具体实现方案,而是他对问题边界的定义方式。
延伸问题
- 当前工作流里,规格层对象到底是什么?
- 中间表示是否真的可检查?
- 维护时团队能否回到规格层,而不只是补代码?
- 测试在多大程度上承担了规格代理角色?
这些问题越能被清楚回答,一个系统就越接近 Balzer 想象中的“可治理自动编程”。
小结
Balzer 真正留下来的,不只是“代码能不能自动生成”这个问题,而是“规格链条能不能被稳定治理”这个问题。
也正因为如此,这篇文章今天仍然比很多表面更现代的讨论更完整。
它真正难得的地方,在于把问题定义得足够深,也足够诚实。
到 2026 年,这篇文章为什么还重要
今天很多关于 AI 代码生成的讨论,实际上重新踩回了 Balzer 当年画出的边界。大家表面上在讨论 code generation,但真正卡住系统级结果的,往往是:
- 意图获取(
intent acquisition) - 规格确认(
specification validation) - 中间表示(
intermediate representation) - 领域约束注入(
domain constraint injection) - 维护路径(
maintenance path)
所以从 2026 年回头看,Balzer 的意义不在于“他已经解决了自动编程”,而在于他把问题定义得比很多今天的讨论更完整。
更准确地说,Balzer 给今天留下的不是现成答案,而是一组非常现代的问题模板:
- 你的系统有没有规格层对象
- 你的自动化是不是通过可检查的中间表示在工作
- 你的维护链条是不是还能回到规格层
- 你的验证是不是只停在“代码能跑”
与其他主题的关系
- 参见 软件工程的抽象、自动化与边界,理解自动化边界为何主要出现在高层抽象
- 参见 软件复杂度与银弹,理解为什么代码生成不能自动消灭本质复杂度
- 参见 系统设计,理解高层意图如何被逐步分解成架构与接口
- 参见 测试与质量保障,理解为什么自动化越强,验证链条越重要
- 参见 版本控制与CI/CD,理解规格变化如何落入持续交付和维护流程
- 参见 编译原理简介,理解“编译问题”和“规格问题”的边界
参考文献
- Robert Balzer, "A 15 Year Perspective on Automatic Programming", IEEE Transactions on Software Engineering, 1985.
- Robert Balzer, "A Global View of Automatic Programming", IJCAI, 1973.
- Frederick P. Brooks, Jr., "No Silver Bullet: Essence and Accidents of Software Engineering", Computer, 1987.
- David L. Parnas, "On the Criteria To Be Used in Decomposing Systems into Modules", Communications of the ACM, 1972.
- OpenAI, "Introducing the SWE-Lancer benchmark", 2025.