剪掉 AI Agent 写的平庸样板代码:工作环境配置指南
把 AI Agent 放到实际开发环境中,很快就会露馅。在不懂业务上下文的情况下,它往往机械地滥用工厂模式,或者源源不断地吐出毫无用处的工具函数。GitClear 在 2024 年发布了一份针对 6.23 亿行 Commit 的分析报告,报告显示在 AI 工具普及后,代码重复率上升了 81%,而清理已有代码的重构比例则从 25% 骤降至 10% 以下。由于上下文窗口的近因偏见(Recency Bias),Agent 为了规避风险,会不断逃向训练数据中最常见的标准模式。我们必须通过上下文文件显式声明团队共享的架构规则和同步逻辑,从而压制 Agent 的误操作。
1. 遏制遗留代码库的规则偏离
要想管控各个开发工具中参差不齐的指令,首先需要在项目根目录下建立一个充当单一事实来源(Single Source of Truth)的 AGENTS.md。Cursor、Claude Code 等工具都会读取该规则。单个 .cursorrules 文件早已被废弃。我们需要将其拆分并放入 .cursor/rules/ 目录下的 MDC 文件中,而在 Claude Code 环境下,则通过在 CLAUDE.md 中引入 @AGENTS.md 来防止规则失效或失效扩散。
`yaml
description: Backend API and Domain Service architectural constraints
globs: apps/api//*.ts, services//*.ts
alwaysApply: false
Backend Architectural Constraints
Mandatory Patterns
- Use Canonical Response DTOs located in
apps/api/src/common/dto.
- All database mutations must go through the Unit of Work pattern defined in
services/shared/uow.
Explicit Negative Constraints
- NEVER create single-child base classes or single-implementation interfaces.
- NEVER write wrapper functions that merely pass arguments to lower-level services.
- NEVER implement strategy or factory patterns when standard conditional logic (if/switch) suffices.
- NEVER add defensive "just-in-case" try-catch blocks that return dummy fallback values without rethrowing.
`
当上下文的总长度超过 800 行(约 2,000 个 Token)时,Agent 就会开始忽略写在顶部的约束条件。因此,AGENTS.md 中应该只保留绝不可妥协的禁止事项,而详细的架构决策记录(ADR)则应推送到 docs/context/ 目录下。
约束条件应用顺序
- 工作内容: 挑选 1 个核心模块,指定禁止模式与例外规则。
- 执行方法:
- 在项目根目录下创建
AGENTS.md,写明禁止单一实现接口、禁止防御性 try-catch 等条件。
- 在
.cursor/rules/backend-constraints.mdc 位置配置目标文件路径(globs)和 YAML Frontmatter 约束。
- 在
CLAUDE.md 中添加 @AGENTS.md 语法以适配环境。
- 预期结果: 减少不必要的样板代码生成,每周用于修改代码的时间可减少约 4 小时。
2. Agent 代码验证流水线
Agent 生成的看似合理的代码背后隐藏着陷阱。根据 Veracode 在 2024 年的研究结果,AI 建议的代码中有 45% 被发现存在 OWASP Top 10 级别的安全漏洞。相比人类编写的代码,AI 用 try-catch 敷衍地包裹可能失败的逻辑,随后抛出空对象或 null 来隐瞒错误的情况高出了 47%。此外,在 Read-Modify-Write 操作中漏掉乐观锁,或者在循环中频繁调用数据库的 N+1 问题也时有发生。
| 验证领域 |
详细验证项目 |
风险模式及 Agent 陷阱 |
Merge 阻断标准 |
| 安全漏洞 |
是否使用 Parameterized Query、租户隔离、检查未经授权的包 |
基于字符串拼接的 SQL 注入、因幻觉生成的未经验证的包调用 |
缺少输入值验证、添加来源不明的外部分析依赖时阻断 |
| 性能瓶颈 |
ORM 延迟加载、Hot path 内的嵌套循环、数据库索引 |
循环内针对单个实体的遍历查询、内存中全表过滤 |
循环内存在数据库及外部 API 调用时阻断、未应用分页时阻断 |
| 类型安全性 |
Strict Type 验证、Boundary 异常处理、并发控制 |
滥用 as any、通过 Empty Catch 块隐瞒错误 |
存在 any 及无节制的 as 类型断言时阻断、日志缺失的 Catch 块阻断 |
Agent 编写的测试代码往往很容易沦为直接复制粘贴实现代码的同义反复测试(Tautological Test)。这种测试完全无法捕获实际的业务缺陷。必须严格执行“如果已存在现有工具函数,则删除 Agent 新创建的工具函数并复用现有代码”这一规则。
手动验证流程
- 工作内容: 将涵盖安全、性能和类型的检查清单填入工作模板和 CI Gate 中。
- 执行方法:
- 在 PR 模板中加入 Parameterized Query、防止 N+1 查询、禁止
any 等检查项。
- 在 Git Pre-commit Hook 中挂载静态分析工具,一旦捕获到
as any 或循环内的 await 即打回 Commit。
- Review 时,强制将 Agent 擅自新建的工具函数替换为既有的通用模块。
- 预期结果: 阻止 N+1 查询或内存泄漏等缺陷流入生产环境。
3. 防止思维停滞的分拆提示词(Decomposed Prompting)
如果试图通过单个 Prompt 注入复杂的结算逻辑或基于状态机的订单处理,Agent 可能会陷入工具调用循环,或者只吐出徒有其表的花架子代码。这是因为尝试一次性处理 Schema 设计、API 接口、异常处理和业务规则会导致 Token 分配顺序混乱。
`
[第 1 阶段: 数据建模] -> 生成 DB 实体、Zod Schema
│
▼ (将输出结果作为上下文传递)
[第 2 阶段: 接口定义] -> 定义 API DTO、Custom Error、服务签名
│
▼ (将第 1+2 阶段的输出结果作为上下文传递)
[第 3 阶段: 业务逻辑实现] -> 完成事务、状态转移、并发控制
`
手动进行这一交互过程相当繁琐。编写 CLI 自动化脚本(scripts/agent-decomposed-build.ts),将前一阶段的产出物绑定作为下一阶段的输入上下文,是更明智的选择。
`typescript
import { execSync } from 'child_process';
import * as fs from 'fs';
interface TaskPipeline {
featureName: string;
stage1Prompt: string;
stage2Prompt: string;
stage3Prompt: string;
}
async function runDecomposedAgentPipeline(pipeline: TaskPipeline) {
console.log([Stage 1] Executing Data Modeling for ${pipeline.featureName}...);
const stage1Output = execSync(claude --print "${pipeline.stage1Prompt}").toString();
fs.writeFileSync(./tmp/${pipeline.featureName}_stage1.ts, stage1Output);
console.log([Stage 2] Executing Interface Definition...);
const stage2InputPrompt = ${pipeline.stage2Prompt}\n\nContext Models:\n${stage1Output};
const stage2Output = execSync(claude --print "${stage2InputPrompt}").toString();
fs.writeFileSync(./tmp/${pipeline.featureName}_stage2.ts, stage2Output);
console.log([Stage 3] Executing Business Logic Implementation...);
const stage3InputPrompt = ${pipeline.stage3Prompt}\n\nContext Models:\n${stage1Output}\n\nContext Contracts:\n${stage2Output};
const stage3Output = execSync(claude --print "${stage3InputPrompt}").toString();
fs.writeFileSync(./src/services/${pipeline.featureName}.service.ts, stage3Output);
console.log([Pipeline Complete] Business logic generated cleanly without cognitive stagnation.);
}
`
搭建分拆提示词脚本
- 工作内容: 确立 3 阶段提示词模板,并建立顺序执行该模板的 CLI 脚本。
- 执行方法:
- 将 Prompt 拆分为数据建模、接口定义、业务逻辑实现 3 个部分。
- 编写
scripts/agent-decomposed-build.ts,使先前的输出能够作为下一个 Prompt 的上下文顺畅衔接。
- 在创建复杂的新模块时执行该脚本。
- 预期结果: 消除 Agent 无法给出解答而“发呆”的现象,将手动重头推翻代码的返工率从 20% 左右降至 5% 以下。
4. 在代码中保留决策依据的配置
使用 AI 编码工具越多,缺乏“为什么这样写”这一理由的“只写代码”就会堆积得越多。未记录架构选择权衡(Trade-off)的代码,日后会变成人类难以维护的沉重负担。必须在 AGENTS.md 中强制要求 Agent 在生成代码时,将 TSDoc 标准注释写进源代码中。
`typescript
/**
- @description Processes deferred settlement payouts for multi-vendor orders.
- @why Uses pessimistic database locking on the Wallet entity instead of optimistic locking because payout calculation involves high-frequency concurrent balance updates.
- @tradeoff Slight P95 latency increase under high contention in exchange for 0% financial drift.
- @complexity Time: O(N log N) due to vendor sorting | Space: O(N) for batch processing buffer.
*/
export async function processDeferredSettlement(orderId: string): Promise {
if (account.hasOutstandingBalance()) {
this.applySettlementHold(account);
}
}
`
为了防止不同开发者之间的 Agent 配置出现偏差,应当将 AGENTS.md 作为单一事实来源,并在 Commit 时运行脚本(tools/sync-agent-rules.ts)将其同步至 CLAUDE.md 和 .cursor/rules/global.mdc。
`typescript
import * as fs from 'fs';
import * as path from 'path';
const AGENTS_MD_PATH = path.join(__dirname, '../AGENTS.md');
const CLAUDE_MD_PATH = path.join(__dirname, '../CLAUDE.md');
const CURSOR_RULE_PATH = path.join(__dirname, '../.cursor/rules/global.mdc');
function syncRules() {
if (!fs.existsSync(AGENTS_MD_PATH)) {
console.error('Error: AGENTS.md does not exist.');
process.exit(1);
}
const baseRules = fs.readFileSync(AGENTS_MD_PATH, 'utf-8');
const claudeContent = # AUTOMATICALLY GENERATED FROM AGENTS.md - DO NOT EDIT DIRECTLY\n\n${baseRules};
fs.writeFileSync(CLAUDE_MD_PATH, claudeContent);
const mdcHeader = ---\ndescription: Global Agent Rule Sync\nglobs: **/*\nalwaysApply: true\n---\n\n;
fs.writeFileSync(CURSOR_RULE_PATH, ${mdcHeader}${baseRules});
console.log('Successfully synchronized AGENTS.md to CLAUDE.md and Cursor MDC rules.');
}
syncRules();
`