用构建规则约束 AI,少靠提示词记纪律
文章借信息隐藏原则提出:优先用类型、API 和模块边界阻止错误,再用测试与静态检查发现违规,将 AI 审查留给需要判断的问题。作者建议先把一条架构约束变成构建检查,同时明确这套结构性论证尚未得到因果验证。
AI 深度解读
让 AI 多读几份文档,不一定能让它改得更安全。作者借用 David Parnas 在 1972 年提出的信息隐藏思想,主张把容易变化的设计决策藏在模块内部,再用接口和自动检查约束修改范围。重点是让代码库自身具备守住边界的能力。
这是《The Agentic Development Lifecycle》第 10 章讨论的问题。它对日常 AI 编程很实用:当 Agent 改坏了一处公共逻辑,原因可能不只是上下文不足,也可能是依赖关系和隐含约定太难看清。不过,作者也明确承认,这套论证尚未证明能直接降低错误率。
Agent 需要的边界,比文件数量更重要
Parnas 所说的信息隐藏,不是把代码藏起来不让人读,而是让模块通过稳定接口对外提供能力,把可能变化的实现决策留在内部。
这样做能缩小 Agent 必须理解的范围。如果修改模块内部逻辑就必须了解整个系统,模块的隔离作用便很有限。
问题在于,接口看起来简单,实际使用条件可能很复杂。隐藏状态、加锁要求、重试行为,以及没有写下来的假设,都可能决定一次修改是否正确。Agent 即使准确理解了函数签名,也可能漏掉这些约束。
所以,判断一个模块是否适合交给 Agent,不能只看代码是否短、命名是否清楚,还要看调用者能否仅凭接口理解必须遵守的约定。
小模块能限制修改范围,公共接口却可能放大影响
限制模块大小、接口规模和依赖扇出,有助于控制实现变更的影响。依赖扇出可以理解为:一个模块向外依赖了多少其他模块。
但如果改的是一个被广泛使用的接口,情况就变了。Agent 可能需要找到所有调用者,并确认它们是否依赖旧约定。修改文件只有几十行,并不代表验证工作也只有几十行。
修改成本既取决于模块内部有多复杂,也取决于多少地方依赖它的约定。
这也是作者提出「有时重复代码比抽取公共抽象更安全」的原因。当调用关系无法被可靠发现时,抽象会把多个位置绑在一起,一次看似局部的修改就可能沿着公共接口传播。
这里的前提不能省略:作者说的是调用关系难以可靠发现的情况,并没有主张普遍放弃复用。重复代码同样会带来修复不同步、行为逐渐分叉等维护问题。
把规则写进构建,比让 Agent 记住更可靠
作者给出了一套约束优先级:
- 用类型、API 和模块边界,让非法状态与依赖无法被表达。
- 用测试、lint、类型检查和架构适应度检查,确定性地发现违规。
- 把确实需要判断的问题交给 AI reviewer。
- 依靠提示词、上下文文件和书面原则,要求 Agent 自觉遵守。
架构适应度检查,就是把架构要求变成可执行的检查。例如「领域层不得导入数据库层」,不再只是一句文档说明,而是一旦违反就让构建失败的规则。
前两层也有区别:类型或 API 可以限制错误写法;构建检查则允许代码先被写出,再阻止违规变更通过。它们都能减轻对 Agent 记忆和理解能力的依赖。
作者建议,本周就选一条架构边界,把它变成构建规则。原文没有提供具体工具、配置或实现代码,这是一项实践建议,也没有附带实验结果。
我的判断
我认同把可机械判断的规则交给工具执行。对于已经明确的依赖方向,反复提醒 Agent 遵守,通常不如让检查直接报错。文档仍然负责解释规则的目的,自动检查负责执行,两者各有用途。
但「边界清楚」并不能自动保证修改正确。业务语义、并发行为、重试是否产生副作用,未必都能被类型和依赖规则覆盖。测试与人工审查仍然需要围绕这些问题展开。
原文给出的是结构性假说,没有提供对照实验或错误率数据。
相较于单纯补充提示词,这个方向有更清晰的执行机制;至于能改善多少、建设成本是否划算,还需要团队自己验证。把信息隐藏用于 AI 编程很有启发,但标题里的「最佳安全工具」没有比较数据支撑,其讨论范围也主要是代码修改的可靠性。
能拿来做什么
- 在 Node 或全栈项目里,先选一条已有共识的依赖规则,例如领域层不得导入数据库层,把违规检查接入构建,避免一开始就重做整个架构。
- 让 Agent 修改公共接口前,先列出调用者和它们依赖的行为约定;调用关系没有查清时,不要仅凭模块很小就判断变更风险低。
- 给难改的模块补充接口约定,优先说明隐藏状态、加锁要求和重试行为,再考虑增加更多泛泛的上下文文档。
- 遇到重复代码时,先确认是否存在稳定的共同约定,再决定是否抽取;如果调用关系难以追踪,可以暂缓合并,保留局部修改的空间。