Published on

写了几十条规则,为什么还管不住 AI?

你可能也经历过这个过程:规则越写越多,心里却越来越没底。

AI 做了件离谱的事,比如自创规范、改了你没让它改的代码,你就往 CLAUDE.md 里补一条;过两天又出新问题,再补一条。几个月后,文件里塞了几十条规则,结果反而更糟:该听的不听,不该脑补的全补。

看起来你在“加强控制”,本质上你可能只是在“增加噪声”。

瓶颈从来不是模型能力,而是行为

问题不在“它不会写”,而在“它不会收”。

模型经常在判断上掉链子,常见表现有:

  • 替你做未经确认的假设,并直接按假设执行。
  • 不管理自己的不确定性:不提问、不展示取舍、该反问时不反问。
  • 过度设计:100 行能解决的问题,写成 1000 行。
  • 在无关任务中悄悄修改或删除自己并不真正理解的代码。

注意,这些都不是“能力不够”的问题。模型能写代码,但不总是知道什么时候该停、开始前该问什么、到底该改多少、怎样才算完成。说到底,这是行为问题,不会因为你把规则写得更长就自动消失。

模型早就具备“能写”的能力了,但离“能放心交给它”还有距离。这个距离,本质上是行为差距,而那 4 条刚好打在靶心上。

配置悖论:为什么规则越多越容易翻车

为什么几十条规则常常会适得其反?核心就两点。

1. 上下文稀释(Context dilution)

CLAUDE.md 会在每轮对话进入代理上下文,而上下文预算是有限的。模型在“信息相关、密度高”的情况下表现最好;噪声一多,表现就会下滑。对一个具体任务来说,几十条里往往大半都不相关。无关指令越多,真正关键的几条越容易被冲掉。

每加一条规则,都要问一句“删掉会不会出错?”如果不会,就删。这个文件不是越全越好,而是越短越有效。

2. 可迁移性(Transferability)

多数新增规则都很场景化,比如“必须 TypeScript strict”“不要分号”“优先函数式组件”。它们只在特定技术栈、特定场景生效,所以规则清单只会越滚越大、越滚越碎。

而“不要假设,先提问”这类行为原则,几乎放到任何语言、任何项目都成立。前者是永远补不完的清单,后者是可以长期复用的底层逻辑。

四个行为原则(含代码示例)

这四条规则分成两类:前三条是“刹车”,防止常见失误;第四条是“油门”,把模型本来就有但默认不主动用的能力调动起来。

行为 1:不要假设,不要掩饰困惑,要显式给出权衡

先看第一条。它命中的,是最常见也最致命的问题:该问的时候不问。

当你对一个无约束代理说“给这个端点加缓存”,它常会直接开写,并在背后替你做完一串关键决策:

# 没有规则的话,会得到以下四个隐藏决策
cache = {}
def get_user(user_id):
    if user_id in cache:
        return cache[user_id]
    user = db.query(User).get(user_id)
    cache[user_id] = user # 从不超时
    return user

这段代码默认了内存缓存、无过期、无容量上限、按 id 缓存。每一条都可能在生产里踩坑。

加上行为 1 后,它会先停下来提问:

Before I add caching, a few choices affect the approach:
1. Store: in-memory (per-process) or Redis (shared across instances)?
2. Expiry: how long should an entry stay valid?
3. Invalidation: should updates to a user purge their cache entry?
4. Scale: roughly how many users / how hot is this path?

先问清楚,再动手。

为什么有效:模型天生偏向“继续往下写”,而不是主动停下来说“这里我不确定”。这条规则,本质上是在重置它的默认动作。

行为 2:只写解决当前问题的最小实现,不做猜测性扩展

第二条,专治“过早抽象”。

当需求只是“写个函数解析配置文件”,无约束代理往往会先搭一大堆架子:

# 没有规则,原本 6 行代码能解决的问题,却写成了 40 行脚手架
class ConfigSource(ABC):
    @abstractmethod
    def load(self) -> dict: ...
class JSONConfigSource(ConfigSource):
    def __init__(self, path): self.path = path
    def load(self) -> dict: ...
class ConfigSourceFactory:
    _registry = {}
    @classmethod
    def register(cls, ext, source): cls._registry[ext] = source
    @classmethod
    def create(cls, path): ...
    # ...并且你会有一个永远不会用的插件系统

加上行为 2 后,产出通常会收敛成:

# 有了规则的话,会得到以下最小实现
import json
def load_config(path: str) -> dict:
    with open(path) as f:
        return json.load(f)

为什么有效:过度设计不一定“技术上错误”,但时机通常不对。它会增加 bug 面、提高审查成本,也会让后续调整更笨重。今天就能解决的问题,别借明天的复杂度来处理。

行为 3:只改必须改的部分,只清理自己引入的问题

第三条,保护的不是“看起来整洁”,而是“改动可核对、可追溯”。

任务明明只是修复 email 缺失导致的崩溃,但无约束时 diff 往往会越改越大:

- if not user["email"]:
+ email = user.get("email", "").strip()
+ if not email:
      raise ValueError("Email required")
- if "@" not in user["email"]:
+ if "@" not in email or "." not in email.split("@")[1]:
      raise ValueError("Invalid email")
+ # while I'm here... (nobody asked for any of this)
+ if len(user.get("username", "")) < 3:
+     raise ValueError("Username too short")
+ if not user["username"].isalnum():
+     raise ValueError("Username must be alphanumeric")

实际请求只有“补上 email 缺失检查”,其余改动都在增加审查负担。

行为 3 的目标很明确:把改动限制为只改必须的部分。

- if not user["email"]:
+ if not user.get("email", "").strip():
      raise ValueError("Email required")

为什么有效:如果 40 行改动里只有 3 行和需求直接相关,你就得硬着头皮审完剩下 37 行,才能放心合并。每一行“顺手优化”,都在给评审增加负担。

行为 4:定义可验证的成功标准,并循环直到验证通过

第四条,是放大器。 前三条解决的是“别出事”,这一条解决的是“把事做成”。

模糊指令:

"Make the search endpoint faster."
→ Agent: "I'll review the code, find inefficiencies, and optimize."
(changes something, declares victory, no way to know if it worked)

可验证指令:

"Get /search p95 latency under 200ms.
Success =
- a benchmark script exists and reports p95
- p95 < 200ms on the 10k-row fixture
- every existing test still passes
Loop until all three are green."

这时代理会自己跑起闭环:写基准、跑结果、看到 450ms、加索引、重跑到 180ms、再跑全量测试,直到条件全部满足。

为什么有效:约束只能减少坏的行为,杠杆才能放大好的行为。它把代理擅长的“朝目标反复迭代”真正激活。你不用盯每一步,只要盯最终验证条件。

除了这 4 条规则,还应该加什么,不该加什么

这 4 条是底座,不是全部。在这之上,只补那些代理无法从代码里直接看出来的信息,比如:

## Project
- Build: npm run build
- Test: npm test
- Lint: npm run lint -- --fix
## Conventions
- API errors return { error, code } — never throw across the boundary
- Dates stored UTC, displayed in the user's timezone
## Watch out
- Payments service timeout is 30s, not the default 5s
- Don't import from /internal — it breaks the public build

每加一行前,都先过一遍这个问题:删掉它会不会导致代理犯下难以自我恢复的错误?如果不会,就先别加。

记住这句筛选原则:能从代码里看出来的,就别在配置里重复。

不该写的包括:

  • 代理从代码结构就能读到的架构说明。
  • 代理能从现有文件推断的风格规则。
  • package.json 已明确列出的依赖信息。

什么时候 4 条规则不够用

当然,这 4 条规则不是银弹,有明确的边界:

  • 大型多文件重构需要架构上下文,仅靠行为原则不够。
  • 受监管领域需要硬约束(如禁止记录 PII、认证改动必须安全评审)。
  • 团队一致性是协作问题,不只是配置问题;可检入、工具无关的 AGENTS.md 仍有价值。
  • 这些表述是按 Claude Code 调过的,对 Cursor/Copilot 大体可迁移,但具体措辞和响应强度需要自行实测。

延伸阅读/参考链接: