你让 Claude Code 加一个 formatPrice。PR 加了 formatPrice——同时多了 formatCurrency、formatPercent、formatCompact、parseAmount、validatePrice。这五个没一个被任何地方 call。PR 描述写「complete formatting utilities suite」。你要一个函数,它给你一个小框架。
这是自动驾驶式的 over-engineering。Claude 默认走「complete solution」——训练数据里这种受表扬。但真实代码库里每个没用的 export 都是维护负担、新人困惑、bug 表面积。修法:prompt + CLAUDE.md 双层限 scope,review 时狠手删。
常见原因
按命中率从高到低:
1. Prompt 没限 scope
「加一个 price formatter」没说”只要我需要的那一个”。Claude 假设”和 price 相关的所有合理 utility”都在范围内,把全套打包送出。
如何判断:你 prompt 是泛话(“加 X helpers”)。改成「确切加 formatPrice(cents): string,其他都不要」重试。
2. Claude 默认「comprehensive」
训练数据里全是「完整 X 库」的例子。让它加一个函数,它给你一个函数 + 4 个明显的兄弟——这看起来”专业”。
如何判断:多出来的 helper 和要求的那个组成自然家族——这就是自动驾驶模式。
3. 没现有 utils 文件让它锚
Greenfield 区域,Claude 没”一个 utility 模块该多大”的参考,就默认设计一个看上去完整的模块。
如何判断:新文件落在没有同伴的目录——Claude 从零设计了模块形状。
4. Task 用词暗示了「库」框架
「Build a date utility module」读起来就是「构建 date utilities 库」。Claude 就给你构建库。本来你只是要 formatDateForInvoice。
如何判断:prompt 里有「module」「library」「suite」「set of utilities」这种词——它们授权了复数。
5. CLAUDE.md 没写 YAGNI
没明确「不要为未来设计」规则,Claude 默认是”为合理的未来设计”。YAGNI 必须是显式 policy 不是文化。
如何判断:cat CLAUDE.md | grep -i "yagni\|unused\|only what's needed" 空。
6. Claude 模仿了训练里类似库的形状
date-fns 有几百个 export。Claude 看过它就觉得”date utilities 意味着很多 export”——你项目不需要这么多,但 Claude 看不出来。
如何判断:helper 形状镜像某个流行库的 API——它是模式匹配,不是按你需求设计。
最短修复路径
按收益从高到低。Step 1 + 2 防它在发生前。
Step 1:scope-bounded prompt
**确切**加这些:
- 函数:formatPrice(cents: number, currency: "USD"): string
- 文件:src/lib/formatPrice.ts
- 测试:src/lib/formatPrice.test.ts
- 3 个测试用例:0、正常、大金额
**不要**加:
- 其他 formatter(formatCurrency 等)——就算看着有用
- "utilities barrel" 或 index 文件
- 只被内部用的 helper——直接 inline
- export 上 JSDoc 之外的文档
「不要」清单是杠杆——没它,Claude 会用”合理增量”塞满。
Step 2:CLAUDE.md 写 YAGNI 规则
## YAGNI 政策
- 只写当前任务需要的代码。
- 不要为未来需求设计,除非已排期。
- 新 helper 至少要有 2 个 caller 才进 merge——1 个 caller 的代码就 inline。
- 新抽象至少 3 处具体用法——过早抽象 review 时拒收。
- "以后可能用"不是加代码的理由——删掉,真要用时再加。
让默认行为对齐,不是每次 task 单独说。
Step 3:review 时把没用的 export 全删
PR 落地后跑:
# 找未使用 export
pnpm dlx ts-unused-exports tsconfig.json
# 或调用图
pnpm dlx knip
每个未使用 export 删掉。不要”以备不时之需”地 merge 死代码。
Step 4:拒收宽文件、重 prompt 收窄
Claude 返了一个”完整 utilities”文件:
你的文件有 6 个 export,任务只需 1 个。删掉:
- formatCurrency
- formatPercent
- formatCompact
- parseAmount
- validatePrice
只留 `formatPrice`。测试文件也只测 `formatPrice`,重写。
session 内的纠正会保持。
Step 5:单 caller 的 helper inline 掉
Claude 建了个内部 helper 只一个地方用——inline:
// 差——只用一次的 helper
function calculateTax(amount: number): number {
return amount * 0.08;
}
function checkout(cart: Cart): Receipt {
const tax = calculateTax(cart.subtotal);
// ...
}
// 好——inline,无 helper
function checkout(cart: Cart): Receipt {
const tax = cart.subtotal * 0.08;
// ...
}
Helper 是给复用用的,一次性逻辑就 inline。
Step 6:季度审现有 helper
helper 默默积累。跑:
# 每个 utils 文件多少 export
find src/lib src/utils -name "*.ts" -exec sh -c \
'echo "$(grep -c "^export" "$1") $1"' _ {} \; | sort -nr | head -20
对 export 多的文件 review 一下:实际被 call 的有哪些?没 call 的删掉。
预防建议
- CLAUDE.md 写 YAGNI:新 helper 要 ≥2 caller,新抽象要 ≥3 用法
- 每个代码 prompt 显式带「不要加」清单,挡住”合理增量”
- CI 跑
ts-unused-exports/knip,新增未使用 export 直接 fail PR - review 时拒「以后可能用」推理——真要时再加
- prompt 不要写”build a library / module / utility suite”——这种词授权复数
- 一次性逻辑保持 inline,helper 只给复用用
相关阅读
标签: #Claude Code #排查 #排查 #YAGNI #过度工程