跳转至

自动编程、规格与实现

Robert Balzer 在 1985 年的 A 15 Year Perspective on Automatic Programming 中讨论的,并不是今天常见意义上的“AI 自动写代码”。他真正关心的问题是:软件能否在更高抽象层上被描述、验证和变换,直到最后才落到代码层。

因此,这篇文章更适合作为软件工程中的“抽象与自动化”问题来阅读。它关心的不是某一个 compiler 有多强,而是从 high-level specificationimplementation 的整条链条该如何组织,这一点和今天的 program synthesisAI coding assistantsagentic software engineering 都有直接关系。

Balzer 扩展了什么叫 automatic programming

狭义理解 Balzer 的扩展理解
把高级语言翻译成代码 从问题表达、规格获取、验证、变换到实现的完整链条
重点在 compiler 重点在 specificationvalidationtransformation
目标是更快生成程序 目标是让正确的软件在更高抽象层被逐步派生

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 \xRightarrow{T_1} S_1 \xRightarrow{T_2} S_2 \xRightarrow{T_3} \cdots \xRightarrow{T_n} P \]

其中:

  • \(S_0\) 是最初的高层规格
  • \(S_i\) 是中间层规格或中间表示
  • \(T_i\) 是一次语义受约束的变换
  • \(P\) 是最终程序

这个式子的重点不在数学花哨,而在于:Balzer 假设“自动编程”应该是一串可检查的语义保持变换,而不是一个黑箱直接把模糊意图映射成代码。

如果写成工程语言,这意味着系统至少要回答三件事:

  • 输入规格的语义是什么
  • 每一步变换保留了哪些约束
  • 最终程序如何被验证仍然满足初始意图

为什么这是软件工程问题,而不仅仅是编译器问题

为什么这是软件工程问题,而不仅仅是编译器问题

Balzer 的框架与传统编译器研究不同。编译器通常默认输入程序已经写对,任务是把一种形式变成另一种形式;而 Balzer 讨论的是在输入本身还不稳定时,怎样逐步逼近一个可执行、可验证的系统描述。

这使得它自然落在软件工程关心的几个核心问题上:

  • requirements acquisition
  • requirements validation
  • specification refinement
  • maintenance 与重新派生(re-derive)实现

这也是为什么 Balzer 看上去像“早期 AI 研究”,本质上却很接近今天的软件工程元问题。

可以换个角度看:编译器通常处理的是“已成型规格”之间的转换,而 Balzer 讨论的是“规格尚未稳定”时,人和系统如何一起把它塑形成可执行对象”。这一步恰恰是软件工程中最贵、最难自动化的部分。

中间表示为什么关键

如果自动编程不是黑箱,就需要中间表示(intermediate representation, IR)承担桥梁作用。这个 IR 不一定是编译器里的 SSA,也可以是:

  • 类型化 API 契约
  • 状态机模型
  • 工作流 DSL
  • 形式化测试预言(test oracle)
  • 领域规则与约束集合

中间表示的重要性在于,它让“高层意图”第一次变成可被讨论、验证和变换的对象。没有这一步,系统就很难知道自己到底在生成什么。

阶段 典型中间表示 作用
需求整理 用户故事、用例、约束列表 明确目标和边界
规格层 Schema、契约、状态机、DSL 定义可验证语义
架构层 模块关系、接口、数据流 决定职责与耦合
实现层 代码骨架、测试、配置 把前面各层落地

这也是为什么今天很多强工程工作流都在强调 Schema firstAPI firsttests as executable specification。它们在做的事情,本质上与 Balzer 的方向相通。

这条路线依赖什么

从 Balzer 的前后工作来看,这条路线并不等于“用户一句自然语言,系统全自动完成”。它更依赖以下条件:

  • 领域知识(domain knowledge)足够明确
  • 用户和系统之间存在交互式澄清(interactive refinement
  • 规格说明语言(specification language)足够有表达力
  • 系统能在规格层而不是代码层完成大部分演化

所以 Balzer 的真正方向更接近“人类引导的自动化(human-guided automation)”,而不是“黑箱式万能自动生成”。

规格获取为什么这么难

很多人第一次接触 Balzer,会以为瓶颈在“生成器还不够强”。但真正的困难往往更早出现:人并不总能一次性把自己想要的系统讲清楚。

常见的困难包括:

  • 目标本身包含冲突,需要取舍
  • 业务规则分散在口头知识和历史流程里
  • 一部分要求只有在运行后才会暴露
  • 不同利益相关方对“正确”的定义并不一致

因此,规格获取(requirements/specification acquisition)不是“把用户的话抄下来”,而是一个澄清、压缩、验证和协商的过程。

现代 AI 工作流和 Balzer 有什么关系

今天的 AI coding assistantsprogram synthesisagentic software engineering 看上去离 Balzer 很远,但其实经常在重复他的基本问题。

一个典型工作流往往是:

  1. 用户给出自然语言目标
  2. 系统提取任务结构与约束
  3. 生成中间计划、测试、Schema 或接口
  4. 再生成实现与修复循环

这个流程如果有效,通常不是因为“模型魔法般理解了一切”,而是因为工作流里存在:

  • 可检查的中间表示
  • 可执行的反馈信号
  • 能与人类反复澄清的接口

换句话说,越是稳定的 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 给今天留下的不是现成答案,而是一组非常现代的问题模板:

  • 你的系统有没有规格层对象
  • 你的自动化是不是通过可检查的中间表示在工作
  • 你的维护链条是不是还能回到规格层
  • 你的验证是不是只停在“代码能跑”

与其他主题的关系

参考文献


评论 #