最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Coding Agent 太会写怎么办:用四层工程约束控制 AI 编程风险
时间:2026-09-21 08:26:02 编辑:袖梨 来源:一聚教程网
当代码生成的成本快速下降,团队面对的新难题已不再是 AI 能不能写,而是它会不会在缺少边界时写得过多、改得过头。过度设计、贴合实现的单测和缺乏证据的重构,都可能让看似完整的产出埋下风险。要改善这种状况,需要把工程判断变成 Agent 能执行和验证的约束。
AI 写代码最大的风险不是不会写,而是太能写:我给 Coding Agent 加的 4 层工程约束
过去大半年,几乎每个使用 Coding Agent(Claude Code、Cursor、Windsurf 等)的工程师都会经历同一种心路历程:
从最初惊叹于它几秒钟写出几十行代码,到后来对着它生成的 PR 陷入深思——AI 很少因为“语法不会”而写错,它绝大多数时候失控,是因为它在错误的优化目标下,把代码写得“太像那么回事了”。
你让它加一个轻量级的 Token 缓存,它顺手给你塞了一套工厂、接口、策略模式加单例;
你让它补单测,它顺手把私有方法的内部实现全 mock 了一遍,测了个寂寞;
你让它做 Code Review,它先夸你一句“架构清晰、逻辑优雅”,然后优雅地放过了最致命的并发竞争;
你让它解释一个老模块,它耐心地读完 30 个文件,然后把你本就能看到的文件列表原封不动地念了一遍。
这些问题,没有一个是语法问题,全都是“工程判断”的问题。
很多人的第一反应是继续往 system_prompt 或 .cursorrules 里加规矩:“不要过度设计”、“多测边界条件”、“认真找 Bug”。但现实是残酷的:规则越长,模型注意力越稀释,最后什么都没约束住。
传统的软件工程规范(Lint、CI、Code Review)是为了对抗人类的惰性与遗忘;
但面对 Agent 时,工程体系必须用来对抗模型的顺从、过度生成与过拟合。
在实际项目踩坑后,我把这些隐性的工程判断抽离出来,写成了 6 个具备明确输入、边界、验证脚本与退出条件的 Agent Skill。它们本质上不是 6 个 Prompt,而是加在 Agent 自主决策链路上的 4 层工程约束环。
第一层:改动约束(code-ablation)
约束 Agent 的重构冲动:不要删有价值的复杂度,只消融无价值的包装。
AI 编程有一个极其危险的本能:要么在写新代码时过度设计,要么在重构时把“代码简化”理解成“疯狂删代码,行数越少越好”。
在 code-ablation 这个 Skill 里,我写在最前面的核心约束是:
“只有一个实现只是线索,不是删除依据。”
看一个很典型的场景:
// 很多 AI 看到这里只有一个实现,会直接把它内联掉
export interface TokenStore {
get(key: string): Promise<string | null>;
set(key: string, value: string, ttl: number): Promise<void>;
}
export class RedisTokenStore implements TokenStore { ... }
如果只看当下,删掉 TokenStore 接口确实能省掉 4 行代码。但这个抽象可能承担着跨模块边界、测试桩替换、或者隔离第三方基础设施的责任。盲目删掉它,本质上是在用未来的架构可维护性,换取当下的行数缩减。
因此,这个约束明确框定了消融的前提条件:
- 不变性约束(Invariants):公共 API 不变、错误语义不变、副作用不变、非法输入行为不变、权限与幂等性边界不变。
- 证据驱动:必须有代码调用链或生命周期证据证明某段逻辑是死代码。测试全绿不代表没有外部使用。
- 接受“零删除”:如果分析完发现每层抽象都有其防御意义,允许并鼓励 Agent 输出“无需消融”。
Agent 在尝试“简化”代码时,必须先回答:这层抽象到底现在解决了什么真实问题?它如果消失,破坏的是哪个设计边界? 答不上来,就不准动。
第二层:验证与证伪(test-first + pre-mortem)
防止 Agent 陷入“用自己的实现来证明自己正确”的自圆其说。
让 AI 写测试最容易出现的灾难是:它先写完代码,然后照着当前实现的每一行去写断言。 这种测试哪怕覆盖率达到 100%,对回归测试也毫无意义,因为如果明天重构内部实现,测试会全部崩溃;而如果代码逻辑本身理解错了,测试反而固化了错误。
这一层由两个呈对偶关系的 Skill 组成:
1. test-first:测试约束的是行为契约,不是实现细节
这个 Skill 强迫 Agent 遵守一套认识论:测试不仅是给机器跑的,更是给人类维护者看的验收文档。
它强制 Agent 的单测描述必须遵循“业务主语 + 行为 + 触发条件”,严禁出现 should work、test submit、calls charge once 这种面向实现的废话。
更关键的约束是:
需求意图
↓
定义验收场景(契约)
↓
写出必然失败的测试(先红)
↓
编写满足测试的最小实现(再绿)
↓
重构
如果 Agent 测到了私有变量、依赖了未公开的实现细节,必须打回。验证这套测试合不合格的标准只有一个:如果我明天把函数体用完全不同的算法重写一遍,你的测试能不能不改一行直接跑通?
2. pre-mortem:改变任务的前提,而不是改变提问的措辞
普通的 Review Prompt:“请审查这段代码是否存在 Bug。”
在模型的概率分布里,这个任务的前提是:作者写了一段看起来合理的代码,请帮他查漏补缺。 于是模型很自然地进入夸夸群模式,挑几个无关紧要的命名建议就交差了。
而在 pre-mortem Skill 里,我直接切断了这个前提,把它的目标函数强行修改为:
“假设系统已经在半年后的高并发场景下发生了 P0 级事故,现在复盘调查。不要替作者辩解,找出直接导致这次崩溃的 3 个最致命原因。”
当目标从“评价代码”变成“事故调查”时,Agent 的关注点瞬间转移到了:
- 爆炸半径:一个节点的失败会不会级联打垮上游?
- 静默失败:有没有吞掉异常导致状态不一致?
- 不可逆性:代码可以瞬间回滚,但被污染的数据不一定能回滚。
通过切换任务的立足点,从根源上摧毁了模型默认的“协作迎合倾向”。
第三层:知识表达分流(self-documenting-code + code-comments)
让机器可以表达的留在机器里,让机器无法表达的留给人类。
很多开发者误以为“好代码少写注释”和“详尽注释”是对立的。实际上,这俩是一套严密的信息分流漏斗:
具体逻辑行为 → 由清晰代码自身承载
状态与结构约束 → 由类型系统(Type / Schema)承载
运行时边界限制 → 由断言(Runtime Assert / Guard)承载
行为契约与验收条件 → 由自动化测试(Test)承载
外部无法表达的知识 → 留给注释(Comments)
1. self-documenting-code:不要让人记住机器本来能记住的事
AI 最喜欢写这种代码:
// 错误示范:靠注释维持脆弱的结构
interface ApiResponse {
status: 'loading' | 'success' | 'error';
// 当 status 为 success 时 data 必然存在
data?: UserData;
// 当 status 为 error 时 error 必然存在
error?: Error;
}
这就是典型的“把编译器的活丢给人脑”。只要有人漏看注释,就会产生运行时隐患。
这个 Skill 约束 Agent:必须将状态收敛到结构与类型里:
// 正确做法:用类型消除非法状态
type ApiResponse =
| { status: 'loading' }
| { status: 'success'; data: UserData }
| { status: 'error'; error: Error };
如果代码和类型已经能够自闭环表达“是什么”,任何关于“是什么”的注释全部算作代码噪音,必须清除。
2. code-comments:保留代码无法表达的决策与外部现实
当代码已经被类型和结构极致表达后,剩下的注释应该写什么?
这个 Skill 规定:注释只用来记录外部世界的事实、历史妥协与决策代价。
// 合格的注释:代码无法表达的现实约束
// Safari 16.0~16.3 在页面退入后台时会重复触发 visibilitychange,
// 此处做 150ms 节流以规避主流程重复初始化。
同时设立边界:
- 函数外注释:服务于调用者,只讲输入前置条件、返回值保证、可能抛出的异常,绝口不提内部怎么实现的。
- 函数内注释:服务于维护者,只讲为什么选这个算法、为什么不能采用看起来更优解的替代方案(Why over What)。
第四层:交付与认知编译(context-compression)
Agent 应该压缩的是人类的认知负荷,而不是简单地压缩字数。
这是日常体验中最容易被忽视、但最影响人机协作效率的一环。
当一个 Agent 在终端里排查完一个涉及 20 多个文件的老旧复杂 Bug 后,它通常有两种坏习惯:
- 纯流水账:从 A 文件讲到 Z 文件,每行改了什么全部倒出来;
- 纯结论:一句话“已经修复了并发问题”,丢下一个巨型 Git Diff 让你盲猜。
第一种直接引爆开发者的认知负荷,第二种根本无法建立信任。
context-compression 约束的本质是:要求 Agent 替人类开发者完成最后一次“认知编译”。
源码结构与零散链路
↓(Agent 深度分析)
完整技术结论与因果链
↓(上下文认知压缩)
交付给开发者的心理模型(Mental Model)
它强迫 Agent 在交付结论时,必须组织为:
- 核心矛盾与系统模型:问题的根因机制是什么,用最小因果链解释;
- 关键决策与非目标:我们选择了什么方案,主动放弃了什么,为什么;
- 架构变动点:关键边界、生命周期和副作用转移到了哪里;
- 验证证据:哪个自动化测试证明了该场景已被覆盖。
开发者不是小白,他们只是没有刚刚读过这 20 个文件的即时上下文。Agent 存在的价值,不仅是搞清楚代码,更是帮开发者用最低的认知成本,快速建立起这块代码的心理模型。
写在最后:走向 Agent 原生工程规范
回过头看这 6 个 Skill,它们贯穿了一个非常清晰的生命周期:
【理解模块】
↓
context-compression
↓
【设计结构】
↓
self-documenting-code
↓
【实现细节】
↓
code-comments
↓
【验证行为】
↓
test-first
↓
【逆向审查】
↓
pre-mortem
↓
【精简收敛】
↓
code-ablation
在日常使用中,你不需要一股脑把它们全扔给模型。当需要做模块重构时,挂载 code-ablation;当要准备提交关键 PR 时,调用 pre-mortem;当要让它写核心业务逻辑时,触发 test-first。
AI 编码工具发展到今天,代码生成的边际成本几乎已经降到了零。
但软件工程从来不是比拼“谁能在单位时间内生产更多字符”。越是在生成容易的时代,对不必要复杂度的警惕、对独立证伪能力的坚持、以及对认知负荷的控制,反而变得比以往任何时候都更加关键。
这或许就是 AI Coding 时代正在催生的新常态:我们不再只是给人类写规范,我们正在将那些沉淀多年的工程判断,编码成 Agent 必须遵守的可执行契约。
仓库开源在 GitHub:DBAAZzz/skills ,包含各个 Skill 的完整定义、边界条件与配套 scripts,欢迎参考或提 PR 讨论。