芥末
发布于 2026-05-04 / 0 阅读
0
0

用 CLAUDE.md 约束 Claude Code 的四条工程规则

Claude Code 这类 AI 编程代理很擅长快速生成代码,但它们也有几个非常稳定的坏习惯:不确定时自己猜,简单需求写成复杂架构,修改一个 bug 时顺手重构一片文件,甚至删掉和任务无关的注释或代码。

这些问题不是模型不会写代码,而是协作边界不清楚。

人类工程师加入团队时会读开发规范、代码风格、提交流程和测试要求。Claude Code 也需要类似的“入职文档”。在 Claude Code 里,这个文件通常就是项目根目录下的 CLAUDE.md

CLAUDE.md 不负责提供业务功能,也不是普通 README。它的作用是告诉 AI:在这个仓库里写代码时,应该怎么思考、怎么提问、怎么改文件、怎么验证结果。

flowchart LR
    A[开发者提出任务] --> B[Claude Code 启动会话]
    B --> C[读取项目代码]
    B --> D[读取 CLAUDE.md]
    C --> E[理解当前上下文]
    D --> E
    E --> F{任务是否明确}
    F -- 不明确 --> G[先提问或列出假设]
    F -- 明确 --> H[制定最小修改计划]
    G --> H
    H --> I[编辑必要文件]
    I --> J[运行测试或检查]
    J --> K{是否通过}
    K -- 否 --> I
    K -- 是 --> L[返回结果和关键 diff]

把规则写进 CLAUDE.md 的好处在于,它不是一次性提示词。只要文件留在仓库里,后续会话就会反复读取同一套约束,团队不需要每次都重新提醒 AI “不要乱改”“先写测试”“别过度抽象”。

AI 编程代理最容易失控的三个地方

大语言模型 LLM(Large Language Model,大语言模型)在编码场景里的问题,通常不是语法错误,而是工程判断错误。它能写出看似合理的代码,却不一定知道什么时候该停。

常见问题表现对项目的影响
自行假设需求用户说得不完整时,AI 不提问,而是选择一种理解直接实现实现方向可能从第一步就错了,后续修复成本高
过度设计小需求引入多层抽象、配置项、工具函数、兼容逻辑代码量膨胀,审查困难,维护成本上升
无关修改修一个点时顺手格式化、重命名、删注释、重构相邻代码diff 变脏,代码审查无法判断真正变更点
缺少验证闭环写完代码后不运行测试,也不说明如何确认结果表面完成,实际可能没有解决问题

CLAUDE.md 要解决的不是“让 AI 更聪明”,而是让 AI 更像一个受约束的工程协作者:不懂就问,只改该改的地方,写完后用明确标准验证。

规则一:编码前先澄清,不要把猜测当事实

AI 编程代理有一种很危险的倾向:它会把自己的推测包装成确定结论,然后沿着这个结论继续写代码。问题越复杂,这种偏差越容易放大。

一个好的 CLAUDE.md 应该要求 Claude 在动手前做几件事:

  • 明确列出当前假设;
  • 需求有多种解释时,不要偷偷选一种;
  • 信息不足时先问问题;
  • 发现更简单的方案时,允许提出异议;
  • 不确定影响范围时,先说明风险。

例如开发者给出一个模糊任务:

给用户接口加个校验,非法数据不要入库。

没有约束时,AI 可能直接开始改 controller、service、数据库模型,甚至新增一套验证框架。

更好的行为是先把不确定点暴露出来:

我需要先确认三个点:

1. 非法数据具体指哪些字段不合法?
2. 校验失败时返回 400、422,还是沿用当前错误码?
3. 是否只处理创建接口,还是更新接口也要同步处理?

如果只针对创建接口,我会优先在现有参数校验层增加规则,并补充无效输入测试,避免改动 service 以外的逻辑。

这种行为会稍微变慢,但能避免 AI 在错误方向上快速前进。对于复杂需求,“先慢一点”通常比“先写一堆再返工”更便宜。

规则二:简单优先,不做推测性开发

AI 很容易把“可能以后会用到”当成“现在应该实现”。这会导致一个小功能被写成一个迷你框架。

典型过度设计包括:

  • 单次使用的逻辑拆出抽象接口;
  • 还没有第二个实现,就先写策略模式;
  • 用户没要求配置化,AI 主动加配置项;
  • 为理论上不会发生的场景写大量错误处理;
  • 为了“扩展性”引入新依赖。

判断是否过度设计,可以用一个很实用的问题:

如果一个资深工程师审查这个改动,会不会说“这个需求不需要这么复杂”?

如果答案是会,那就应该删掉抽象,回到最直接的实现。

例如只需要判断邮箱是否为空和格式是否正确,简单实现已经够用:

function validateEmail(email: string): string | null {
  if (!email.trim()) {
    return "Email is required";
  }

  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
    return "Email is invalid";
  }

  return null;
}

不需要为了这个需求立刻写成:

interface Validator<T> {
  validate(value: T): ValidationResult;
}

class EmailValidator implements Validator<string> {
  validate(value: string): ValidationResult {
    // ...
  }
}

class ValidationPipeline<T> {
  constructor(private validators: Validator<T>[]) {}

  run(value: T): ValidationResult[] {
    // ...
  }
}

抽象不是坏事,但抽象应该来自重复和稳定的变化点,而不是来自 AI 对未来需求的想象。

规则三:精准修改,只动任务需要的代码

AI 写代码时常见的另一个问题是“顺手”。顺手格式化一个文件,顺手改掉变量名,顺手重排 import,顺手删掉看不懂的注释。

这些操作单看可能没错,但会污染 diff。PR(Pull Request,代码合并请求)里一旦混入大量无关变更,审查者就很难判断哪些代码是为了解决问题,哪些只是 AI 的自作主张。

精准修改可以拆成两条:

场景应该做不应该做
修改现有代码保持当前风格,只改完成任务必须改的行按自己的偏好重排代码
看到旁边有坏味道可以在结果里指出不要主动重构
新增代码导致 import 无用删除自己造成的无用 import删除早就存在但无关的死代码
任务只涉及一个函数尽量限制在该函数及测试附近扩散到模块级重构

可以把判断标准写得非常直接:

每一行改动都必须能回答一个问题:它和当前请求有什么关系?
如果回答不上来,就不要改。

这个规则对大型仓库尤其重要。大型项目里的历史代码、兼容逻辑、奇怪注释,往往有看不见的背景。AI 不理解时直接删除,风险比保留更高。

规则四:把任务改写成可验证目标

“修一下这个 bug”“加个校验”“优化一下性能”这类指令对 AI 来说太模糊。它可能会完成某个动作,但不一定真的达成结果。

更好的方式是把任务改写成可验证目标。

模糊指令可验证目标
加个验证为非法输入补测试,然后让测试通过
修这个 bug先写一个能复现 bug 的测试,再修到测试通过
重构这个模块重构前后测试都通过,外部行为不变
提高接口稳定性补充失败场景测试,并确认错误响应符合约定
清理无用代码只删除当前任务产生的无用代码,并确保构建通过

目标越具体,AI 越容易进入“执行—检查—修正”的闭环。

flowchart TD
    A[收到任务] --> B[定义成功标准]
    B --> C[写测试或确定检查命令]
    C --> D[实现最小改动]
    D --> E[运行验证]
    E --> F{验证通过?}
    F -- 否 --> G[根据失败信息修正]
    G --> E
    F -- 是 --> H[说明改动范围和验证结果]

例如不要只说:

修复订单金额计算错误。

可以改成:

先添加一个失败测试:当订单包含折扣券和运费时,总金额应等于商品总价 - 折扣 + 运费。
测试失败后再修复实现,最终运行订单模块测试并通过。

这种表达把成功标准讲清楚了,Claude Code 就不需要猜“修好了”到底是什么意思。

一个可直接使用的 CLAUDE.md 模板

项目可以从一个通用模板开始,再逐步加入自己的构建命令、测试命令、代码风格和领域规则。为了减少歧义,很多团队会选择用英文写给模型;如果团队更习惯中文,也可以使用中文版本。

# CLAUDE.md

Use these rules when working in this repository. Combine them with the project-specific instructions below.

## Working style

Prefer careful, minimal changes over fast broad edits. For tiny tasks, keep the response short, but do not skip necessary clarification.

## 1. Clarify before editing

Before changing code:

- State important assumptions when they affect the implementation.
- If the request can be interpreted in more than one way, list the options instead of silently choosing one.
- Ask a question when missing information can change the solution.
- Point out a simpler approach when the requested approach seems unnecessarily complex.
- Stop and explain the uncertainty when the code or requirement is unclear.

## 2. Keep the solution small

Write only the code needed for the requested behavior.

- Do not add features that were not requested.
- Do not create abstractions for code that is used only once.
- Do not add configurability unless the task requires it.
- Do not introduce new dependencies without a clear reason.
- If a short direct solution works, prefer it over a framework-like design.

Before finalizing, check whether the same result can be achieved with fewer moving parts.

## 3. Make focused changes

When editing existing code:

- Touch only files and lines related to the task.
- Match the existing style in the surrounding code.
- Do not reformat, rename, or refactor unrelated code.
- If unrelated dead code is found, mention it separately instead of deleting it.
- Remove only the unused imports, variables, or functions created by your own changes.

Every changed line should be traceable to the user request.

## 4. Work toward verifiable outcomes

Turn implementation requests into checks that can pass or fail.

Examples:

- Validation work: add invalid-input tests, then make them pass.
- Bug fixes: add a failing reproduction test, then fix the bug.
- Refactors: confirm tests pass before and after the change.
- Multi-step tasks: include a short plan with a verification step for each phase.

For non-trivial tasks, use this structure:

1. Change: [what will be changed] -> Verify: [test or command]
2. Change: [what will be changed] -> Verify: [test or command]
3. Change: [what will be changed] -> Verify: [test or command]

## Definition of done

A task is complete only when:

- The requested behavior is implemented.
- Unrelated code is not changed.
- Relevant tests or checks have been run, or the reason they were not run is stated.
- The final response summarizes the changed files and verification result.

如果仓库已经有明确的工程规范,可以在模板后面追加项目专属内容:

## Project commands

- Install dependencies: `pnpm install`
- Run unit tests: `pnpm test`
- Run lint: `pnpm lint`
- Type check: `pnpm typecheck`

## Project conventions

- Use TypeScript for new application code.
- Do not add runtime dependencies without approval.
- Keep API error responses compatible with existing clients.
- Add tests under `__tests__` next to the changed module.

这样 Claude 不只知道“要谨慎”,还知道具体用什么命令验证结果。

怎么在项目里使用

最简单的方式是在仓库根目录创建 CLAUDE.md

cat > CLAUDE.md <<'EOF'
# CLAUDE.md

Use careful, minimal changes in this repository.

## Rules

- Clarify ambiguous requirements before editing.
- Prefer the smallest implementation that solves the task.
- Do not change unrelated code, formatting, comments, or names.
- Add or update tests when behavior changes.
- Run the relevant checks before reporting completion.
EOF

如果要使用现成的 andrej-karpathy-skills 文件,可以直接下载到当前项目:

curl -L -o CLAUDE.md \
  https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md

已有 CLAUDE.md 时,不要直接覆盖,可以追加后再手动合并:

printf "\n\n" >> CLAUDE.md

curl -L \
  https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md \
  >> CLAUDE.md

Claude Code 也支持插件方式使用该规则集,常见命令形态如下:

/plugin marketplace add forrestchang/andrej-karpathy-skills
/plugin install andrej-karpathy-skills@karpathy-skills

项目级文件更适合团队协作,因为规则会跟随仓库版本管理。插件或全局配置更适合个人偏好,比如“回答要简洁”“默认先运行测试”“不要主动新增依赖”。

如何判断规则是否真的生效

CLAUDE.md 不是写完就结束,它需要通过实际 diff 来检验效果。可以观察四类信号。

观察点好的表现需要调整的表现
需求不明确时AI 先列假设或提问AI 直接开始写实现
diff 范围只出现和任务相关的文件大量格式化、重命名、无关重构
代码复杂度实现直接,抽象数量少小需求引入多层封装
验证行为主动运行测试、lint 或说明未运行原因只说“已经完成”,没有检查证据

如果 AI 仍然经常乱改,可以把规则写得更具体。例如不要只写:

Make clean changes.

改成:

Do not reformat files, rename variables, reorder imports, or refactor nearby code unless the user explicitly asks for it.

模型更容易执行具体禁令,而不是抽象价值观。

常见坑

1. 规则太多,反而稀释重点

CLAUDE.md 不是越长越好。模型上下文有限,规则越多,冲突和遗忘的概率越高。行为规则最好保持短而硬,项目命令和业务约束再单独分区。

推荐结构:

CLAUDE.md
├── 工作方式:澄清、简单、精准、验证
├── 项目命令:安装、测试、lint、构建
├── 代码规范:语言、目录、命名、依赖限制
└── 完成标准:必须说明改动和验证结果

2. 只写原则,不写验证命令

“写完要测试”不够具体。Claude 需要知道运行哪个命令。

更好的写法:

Before reporting completion, run the most relevant command:

- Backend unit tests: `pytest tests/api`
- Frontend unit tests: `pnpm test`
- Type check: `pnpm typecheck`
- Lint: `pnpm lint`

如果某个命令很慢,也可以说明触发条件:

Run `pnpm test user-profile` for user profile changes.
Run the full `pnpm test` only when shared utilities or global state are changed.

3. 规则和任务互相冲突

例如 CLAUDE.md 写了“不要重构无关代码”,但用户要求“顺便整理整个模块”。这时 AI 应该以用户当前明确要求为准,并说明会扩大修改范围。

可以加入一条冲突处理规则:

If a user request conflicts with this file, follow the explicit user request and mention the conflict before editing.

4. 把 CLAUDE.md 当成测试替代品

行为约束只能减少 AI 乱写代码,不能证明代码正确。真正可靠的闭环仍然是测试、类型检查、静态分析和代码审查。

flowchart LR
    A[CLAUDE.md 行为约束] --> B[更干净的修改过程]
    C[自动化测试] --> D[验证功能正确性]
    E[代码审查] --> F[检查设计和可维护性]
    B --> G[更容易审查的 PR]
    D --> G
    F --> G

CLAUDE.md 的定位是让 AI 少制造噪音,而不是替团队承担工程质量体系。

哪些场景适合用 CLAUDE.md

场景是否适合原因
多人维护的中大型仓库适合能统一 AI 修改边界,减少无关 diff
经常让 Claude Code 修 bug适合可以强制先复现、再修复、再验证
代码风格严格的项目适合可以写清楚格式、依赖、目录约定
快速一次性脚本一般规则可能比任务本身还重
没有测试的旧项目仍然适合,但效果有限能减少乱改,但无法替代验证体系
需求频繁探索的原型项目需要放宽过强的谨慎规则可能拖慢试验速度

对于严肃工程项目,推荐把 CLAUDE.md 纳入版本控制,让它和代码一起演进。每当团队发现一种新的 AI 误操作,就把它改写成明确规则补进去。

核心价值:把 AI 从“会写代码”约束成“会协作”

AI 编程工具真正改变开发流程的地方,不只是生成代码更快,而是把开发者的工作重心推向了任务定义、边界设定和结果验证。

CLAUDE.md 的价值就在这里:它把团队对“好协作”的要求写成可重复读取的规则。

四条规则可以浓缩成一句话:

先澄清,再做最小实现;只改相关代码,并用可验证标准确认完成。

只要这句话稳定地影响每一次 AI 编码会话,PR 就会更小,审查会更轻松,AI 造成的返工也会少很多。


评论