代码不是写出来的,是被观测到的——Observational Specification Mining for LLM-Written Code
代码不是写出来的,是被观测到的
当 LLM 生成的代码量超过人类维护者的理解能力时,”规格说明”必须从人脑里的意图变成系统行为的统计重建。
传统规约已死
规约驱动开发(Spec-Driven Development)有一个隐含前提:规约在人脑中,实现是对规约的翻译。这个前提在 LLM 时代失效了——代码生成速度远超人类写出对应规约的速度,而且生成的代码往往没有人知道它应该做什么,只有它”跑起来是这样”。
这不是边缘情况。这是主流。
核心思想:从观测轨迹反向挖掘隐式规约
Observational Specification Mining 的思路是:把单元测试、日志、trace、API 调用序列当成观测数据,把规约当成从这些数据中统计重建的隐变量。
具体来说,系统从三个维度提取观测样本:
1. 运行时轨迹(Execution Traces)
- 函数调用序列
- 参数值域(哪些值被传进去了)
- 返回值分布
- 异常抛出模式
2. 测试覆盖(Test Coverage)
- 哪些分支被触发了
- 哪些边界条件有测试
- 现有测试的断言暗示了哪些不变量
3. 日志与监控(Logs & Telemetry)
- 生产环境的真实输入分布
- 错误模式的聚类
- 时序依赖关系
然后用轻量级模型(不是 GPT-4 级别的大模型)从这些观测中重建:
- 前置条件(什么输入状态下函数能正确执行)
- 后置条件(函数执行完后,什么不变量必须成立)
- 循环不变量(循环内部,什么在每次迭代间保持不变)
为什么轻量模型足够
这里有个反直觉的洞察:规约挖掘不需要模型有多强,只需要模型能识别”异常模式”。
前置/后置条件本质上是输入输出之间的关系约束——这比”生成代码”或”写文章”要窄得多。7B 量级的代码模型足够学会识别”这个函数的输入总是正整数”或”这个方法从来不返回 null”这类约束。
真正难的是不变量挖掘(invariant mining),需要追踪跨多次执行的统计规律,这需要更好的时序建模能力。
对 Agentic Coding 的直接意义
场景一:自动补全测试
当前 Agent 写代码后,测试是手工补或另一个 Agent 写的。Observational Mining 可以:
- 观察 LLM 生成的函数在多个输入下的行为
- 从轨迹中推断前置/后置条件
- 自动生成覆盖这些约束的测试用例
这解决了”测试先行”在 Agent 时代的类比问题:不是TDD(测试驱动开发),而是 OTDD(观测驱动测试)——先有代码,通过观测重建规格,再补测试。
场景二:自动发现语义漂移
LLM 在重构时最危险的问题:代码能跑,但语义悄悄变了。一个函数的副作用被悄悄改掉了,因为没有测试覆盖那个行为。
Observational Mining 的核心价值:即使没有显式规约,它能从历史轨迹中学习”这个函数历来是这样运作的”。新代码的行为如果偏离这个基准,自动触发告警。
这不是 Lint,不是类型检查,是行为漂移检测。
场景三:自动文档生成
传统文档的问题是文档和实现不同步。Observational Mining 生成的规约来自运行时观测——它反映的是真实行为,而不是程序员的预期行为。
这个重建出来的规约,比人去写的 README 更诚实。
与传统规范挖掘的区别
传统 Software Specification Mining(如 Daikon)依赖:
- 手工插入的探针(instrumentation)
- 运行足够多的测试用例来触发所有路径
- 大量计算开销
Observational Mining 的进步在于:
- 被动观测:不需要在代码里插桩,从日志和 trace 就能做
- 采样效率:不需要遍历所有路径,统计推断比枚举更高效
- 集成 LLM:用语言模型的泛化能力从稀疏观测中推断完整规约
未解决的问题
- 观测覆盖偏差:只观察了跑过的输入,没跑过的输入对应的规约是空的
- 隐式行为的规约歧义:同一个观测可能对应多种规约解释,需要人工仲裁
- 计算成本:全生产环境的 trace 量巨大,需要流式处理和采样策略
核心隐喻:代码不是设计师的图纸,代码是物理实验的观测对象。规约不是写出来的,是从数据中重建出来的。
💬 评论
💡 使用 GitHub 账号登录 即可参与讨论