怎么让 AI 别在你项目里乱改代码:约束工程的四道防线
只想让 AI 写个 50 行的数据清理脚本,一小时后回来一看:项目里多出了三个微服务、一个没人要的前端仪表盘、一套 Docker 编排,还顺手引了 Redis——而那个脚本依然报着诡异的类型错误。
这不是个例,是 AI 编程的默认行为。它是个极度迎合你的"顺向执行器",给它越大的视野,它能顺手污染的面就越大。光靠在 prompt 里写"请不要乱改别的文件"是拦不住的——它会越界,因为没人真的拦着它。
真正能拦住它的,是一套叫约束工程的东西:提前把你脑子里的"全局不变量",沉淀成 AI 每一轮都绕不过去的硬关卡。这篇把它讲透。
先分清:软约束 vs 硬约束
这是整件事的地基,搞混了后面全白搭。
- 软约束:
CLAUDE.md/.cursorrules/AGENTS.md这类规则文件。本质是把全局不变量硬编码进上下文,变成 AI 每轮照办的紧箍咒。但它是"软"的——靠模型愿意守。模型疲劳了、上下文被冲淡了、它觉得"绕一下更快",它就越界。 - 硬约束:测试、类型/契约、架构边界。这些不靠模型自觉。模型违反了,它根本过不去——测试红、编译报错、跨边界根本看不到那个接口。

约束工程的全部功夫,就一句话:尽量把软约束升级成硬约束。 下面四道防线,按"兜底强度"从弱到强排。
防线一:全局不变量硬编码
把你脑子里那几条高压线写下来,放进规则文件。但有三个铁律,违反任何一条,规则文件就退化成噪音:
- 只写不变量,不写教程。 规则文件不是给 AI 上课的,是给它划红线的。“分层依赖方向单向”“金额只走 decimal”“这张表禁止直连”——一条一句,可判定。
- 可判定,不可模糊。 "代码要优雅"是废话,AI 不知道怎么照办。"禁止在 controller 里写 SQL"是高压线,AI 一眼能对照。
- 越短越硬。 一份 30 行、每行都是红线的规则文件,比一份 300 行掺着说明的强十倍。
判断信号:如果你在 review 里反复纠正 AI 同一类越界(又穿透分层了、又直连那张表了),说明这条高压线该进规则文件,而不是靠你每次人肉拦。
防线二:TDD 当锁链
这是约束工程里最被低估、却最该立刻做的一件事。
以前觉得写单测太慢?在 AI 时代这个判断彻底反转了——单测现在是你手里唯一一道不靠 AI 自觉的退出闸门。
注意因果顺序,它是约束工程和 Vibe Coding 的分水岭:
- Vibe Coding:先让 AI 生成实现 → 再补测试(甚至 AI 自己补)。这时测试是事后追认,验证的是"AI 写了什么",不是"业务要什么"。橡皮图章。
- 约束工程:人先写测试,把业务的"正确"定义死 → AI 在这个笼子里生成实现。测试变成先验的退出闸门——AI 不管怎么发挥,撞到闸门就得退回来。
为什么这道闸门能兜底?回想成本不对称:生成 O(1)、验证 O(N),你的审查瓶颈降不下来。测试是把 O(N) 的人肉验证,一次性固化成 O(1) 的自动验证。你写一次断言,它替你审查一万次。 那个降不下来的瓶颈,第一次有了杠杆。它还顺手治了并发、竞态这类静态读代码看不出来的雷——你写一个能复现它的测试,AI 就再也绕不过去。
防线三:类型与契约——把防线提前到编译期
测试是运行期的闸门,类型和契约是编译期的闸门,更早、更狠,AI 连"写出来"的机会都没有。
核心是把你脑子里的隐式假设,从脑子里搬进类型系统:
- 那个"有时静默截断、下游必须二次校验"的字段,别用裸
string,用Validated<T>或返回Result——逼下游显式处理,AI 想跳都跳不过。 - 那个"金额必须强一致、不许走浮点"的约束,别用
float,用Money/Decimal类型——AI 拿到的就是个不许做浮点运算的东西。 - 那个"这个 ID 只在已鉴权上下文里有效"的潜规则,用 branded type 编码进类型——没鉴权的地方根本拿不到。
这就是"让非法状态无法被表示"(make illegal states unrepresentable)。一旦一条潜规则被编码进类型,它就从"靠 AI 记得"升级成"AI 违反就编译不过"。你把一条隐式契约,变成了一道硬关卡。
防线四:DDD 划边界——切断它感知全局的能力
前三道防线都在"拦 AI 别越界"。第四道更釜底抽薪:让它根本看不到可以越界的地方。
永远不要把整个代码库的上下文,直接扔给一个全局的 AI 助手。用 DDD 的 Bounded Context 严格限制它的可见范围:你要做哪个领域的功能,就只喂给它那个领域的核心实体和接口规范,其余一概不给。
它看不到那张可以直连的表,就没法直连;它看不到那个可以穿透的底层接口,就没法穿透。可见性即权力——你限制了它的视野,就限制了它的破坏半径。 这一招还顺带治了 Scope Creep:AI 看不到全局,就发不起"既然加了鉴权,顺便把 OAuth2 全家桶接了吧"这种"贴心"建议。
合起来:动手前走一遍
接一个新任务、动一段 AI 代码之前,从上到下过一遍:
- 不变量:这次涉及的架构高压线,写进规则文件了吗?
- 测试当锁链:核心断言是我先写的吗?还是 AI 写完我事后追认的?
- 类型/契约:这次的隐式契约,编码进类型了吗,让 AI 违反就编译不过?
- 可见性:我喂给 AI 的上下文,是"只够干这一件事",还是"整个 repo"?
这四步的共同点是一个词:前置。约束工程的全部秘密,就是把所有"判断",从"AI 写完之后我来兜"前置成"AI 动手之前就立好的硬关卡"。
你不再是那个在后面给屎山打补丁的人,你变成在前面立笼子的人。你给 AI 定义的边界有多清晰,AI 吐出的代码就有多强壮。
我是随机比特,腾讯十年开发,天天拿 AI 干活、被坑过、把判断沉下来。更多 AI 工程化的硬核拆解,关注公众号「随机比特」,或来 rbits.uk 翻完整的 270+ 篇。
—— 随机比特
