Claude Code 写了一堆没人用的 helper:6 个 over-engineering 触发 + YAGNI 强制

你要一个函数,PR 多送 5 个「以后可能用」的——在 prompt + CLAUDE.md 限 scope,review 时狠手删。

你让 Claude Code 加一个 formatPrice。PR 加了 formatPrice——同时多了 formatCurrencyformatPercentformatCompactparseAmountvalidatePrice。这五个没一个被任何地方 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 #过度工程