Sonnet 5.5 出来之后,我第一时间就开始在重度重构场景里压榨它。试了大半个月,结论很明确:这个模型的代码生成能力确实强,尤其在理解项目整体结构方面,能同时跟踪多个文件的依赖关系。但能力越强,翻车的方式也越隐蔽。它不是不会写,而是经常“太会写”——自己觉得文件该挪就挪、接口该变就变,结果一个本来只打算改两三个文件的小重构,被它扩散成十个文件的大手术。
后来我用 CLAUDE.md 把它的行为硬约束了一轮,局面才彻底改观。这篇就记录一下我是怎么设计这份约束文件、怎么让它约束住 Sonnet 5.5 的多文件重构行为,以及在实操中踩过哪些坑。
1. 核心需求解析与项目背景
1.1 从“能改”到“改得对”
先说清楚这个实践的背景。我手上有一个维护了挺久的 Python 服务项目,代码量不算特别大,但模块边界已经有点模糊。某次功能迭代需要同时改动核心计算层、数据访问层和两个业务流程模块,属于典型的多文件重构。这种重构的难度不在“写代码”,而在“改完之后整个系统还能不能跑”。
用 Sonnet 5.5 做这件事,模型本身没有任何问题。它对上下文的理解深度、对跨文件改动的连贯性,都比之前的版本强不少。问题出在“边界感”上。你告诉它“帮我重构配置文件加载逻辑”,它可能会顺手把相关工具的接口也改掉,因为从代码层面看,那些地方“确实有改进空间”。
这种“自发扩散”的行为,放在小型 Demo 上无所谓,放在生产项目里就是灾难。我需要的是让模型按我的意图改,而不是按它的审美改。CLAUDE.md 恰好是干这个的——它相当于给模型定义了一套“项目内宪法”,让模型在动任何文件之前先读一遍规则,再决定怎么动手。
1.2 多文件重构为什么容易失控
多文件重构的失控,通常不是某个单点错误,而是“蝴蝶效应”式的连锁扩散。我总结下来,主要的诱因有三个:
- 模型的局部优化冲动:看到某个函数写得不够优雅,就会顺手“改善”它,哪怕这个函数跟当前任务毫无关系。
- 依赖关系感知不全:模型在改 A 文件时,未必能完全记住 B、C、D 文件里对 A 的引用方式,尤其是项目比较大、上下文塞满的时候。
- 验证闭环缺失:模型写完代码,如果没有人为要求它跑测试,它不会主动验证改动是否破坏了其他模块。
CLAUDE.md 要解决的,本质上就是这三个问题。它通过规则化的指令,把“不要动无关文件”“改之前先列出影响面”“改完必须验证”这些要求写进模型的决策过程,相当于给模型装了一个“行为刹车”。
把模型的输出和既定规则做比对,你就能快速定位是约束本身失效了,还是模型压根没读规则。前者需要调整规则措辞,后者需要在提示词里重述关键约束。
2. 项目核心方案与总体设计
2.1 核心方案:CLAUDE.md 作为“项目内宪法”
CLAUDE.md 是 Claude Code 以及相关工具链在项目启动时自动读取的规则文件。它存在于项目根目录,本质上是一份 Markdown 格式的“行为契约”。模型每次执行任务前,都会先加载这个文件,把里面的规则当作项目背景来理解。
我的核心方案很简单:把 CLAUDE.md 从“可有可无的说明文档”升级成“约束 Sonnet 5.5 重构行为的核心控制面”。所有多文件重构任务,都必须先在 CLAUDE.md 里定义清楚“哪些事能做、哪些事不能做、做事的顺序是什么”,然后才允许模型动手。
听起来很绕,但执行起来就是一件事——写规则。只不过这份规则要比普通项目文档严厉得多,必须包含动词、边界和验证方式,不能只写“注意保持代码质量”这种空话。
举个例子,我见过不少人写的 CLAUDE.md 是这样的:
# 项目说明 这是一个 Web 服务项目。 请保持代码整洁。 修改时注意不要影响其他模块。这种文件写了等于没写。因为它只给了“目标”,没给“路径”。Sonnet 5.5 不是不听话,而是根本不知道“保持代码整洁”在你这个项目里具体指什么。是函数不能超过 50 行?是禁止使用全局变量?是必须写类型注解?规则不量化、不聚焦,模型就只能靠猜。
真正有效的规则,应该长这样:
- 修改 src/core 下的接口时,必须先在对话中列出所有受影响文件的清单。 - 禁止修改与当前任务无关的文件;如果确实需要修改,必须暂停并说明理由。 - 每个函数必须保持无状态,禁止使用模块级可变变量。这种规则一看就能执行,因为它有明确的动词和边界,模型不需要“理解你的意图”,只需要“按规则办事”。
2.2 CLAUDE.md 的内容设计原则
设计 CLAUDE.md 时,我遵循了四条原则,这里依次展开说。
第一,规则要“面向行为”,不“面向态度”。任何只描述心境、不描述动作的规则,都是无效规则。与其写“要认真处理依赖关系”,不如写“修改 public API 后,必须用全局搜索确认所有调用方已同步更新”。前者是形容词,后者是可执行的检查项。
第二,规则要“分优先级”。CLAUDE.md 不是越长越好,模型对规则的注意力也是有限的。我把规则分成三级:红线级(违反会导致编译失败、接口破坏)、常规级(影响代码风格、可维护性)、建议级(属于偏好,不强制)。Sonnet 5.5 能理解优先级,但前提是你得在文件里明确标出来。我的做法是加粗红线规则,并在句首写上“禁止”或“必须”,让它们从视觉到语义都被强化。
第三,规则要“可验证”。每一条规则,模型做完任务后都应该能自己检查。比如“测试必须通过”是可验证的,“代码要清晰”是不可验证的。如果条件允许,我会把检查命令直接写进规则里,比如“修改 src/core/ 下的文件后,必须运行 pytest tests/test_core.py”。这相当于给模型配了一把“自查尺”。
第四,规则要“少而精”。一开始我写过 40 多条的 CLAUDE.md,效果反而不好。因为规则太多,模型的上下文注意力被稀释,重点反而抓不住。后来精简到 15 条以内,并且把最关键的 5 条放在文件开头,实测效果明显提升。
2.3 规则文件的结构与设计
基于上面的原则,我的 CLAUDE.md 采用三段式结构:
- 项目概述:用 3~5 行说明项目是什么、代码分几层、依赖关系如何。这段的目的是让模型快速建立“全局地图”。
- 工作流规则:定义任务执行的步骤顺序,包括理解、规划、执行、验证四个阶段各自的行为要求。
- 技术约束:定义编码规范、接口稳定性要求、禁止事项等。多文件重构时,这一部分最有用,因为它直接告诉模型“不许这样做”。
这个结构的好处是,模型在任务启动时能先建立“项目全貌”,再进入具体操作,而不是一上来就陷进某个文件的细节里。对于多文件重构这种需要全局视野的任务,这个顺序非常关键。
3. 工程组织与实操准备
3.1 三个核心问题
在动手写 CLAUDE.md 之前,我先把“当前重构到底要解决什么问题”想清楚了。这比急着写规则重要得多。我习惯用三个问题来收敛需求:
- 这次重构的边界在哪里?是只换实现方式,还是要改接口设计?
- 哪些文件是“稳定核心”,不允许随意动?哪些文件是“外围扩展”,可以灵活调整?
- 验收的标准是什么?是测试全部通过,还是某个性能指标达标?
这三个问题想明白之后,CLAUDE.md 里的规则才有具体的指向性。否则你写出来的规则就是通用的“好代码指南”,跟项目实际需求毫无关系。
拿我的项目举例:核心计算层是“稳定核心”,接口设计近期内不会动;数据访问层可以改,但必须保证现有调用方不受影响;工具函数层最自由,但必须保持无状态。基于这个划分,CLAUDE.md 里就能写出“禁止修改接口签名”“修改数据访问层的调用逻辑时,必须同步更新所有调用点”这样有实际约束力的规则。
3.2 工具选择与环境准备
有了规则思路,接下来就是工具链的搭配。这个实践用到的东西不多,但每一件都有明确的用途。
首先是 Claude Code 环境,这是读取 CLAUDE.md 和执行任务的基础,没有它整个方案就不成立。其次是版本控制工具,我习惯在重构前单独拉一个分支,这样模型改坏了也有后悔药可吃。最后是自动化测试框架,我用的是 pytest,因为项目本身就是 Python 工程,pytest 跑起来快,输出也直观,适合作为模型的“验证回执”。
环境层面的准备有一个容易被忽略的点:确保 CLAUDE.md 在当前工作目录下,并且是 UTF-8 编码,不要带 BOM。有些工具链对编码很敏感,文件一旦带 BOM,模型读取时可能会在开头多一个不可见字符,影响后续规则解析。这个坑我踩过一次,排查了半天。
3.3 项目目录与文件划分策略
为了让约束有“锚点”,我把项目目录按“内核稳定、外围可变”的原则重新梳理了一遍。Sonnet 5.5 在动文件之前,我会要求它先看目录结构,明确自己处在哪一层。目录结构类似这样:
project/ ├── src/ │ ├── core/ │ │ ├── engine.py # 核心引擎,逻辑稳定,接口冻结 │ │ ├── config.py # 配置加载,改动频繁,但边界清晰 │ │ └── registry.py # 组件注册表,扩展频繁 │ ├── features/ │ │ ├── auth.py # 登录模块,依赖核心接口 │ │ └── dashboard.py # 仪表盘模块,依赖核心接口 │ └── utils/ │ └── helpers.py # 纯工具函数,无状态,可自由扩展 ├── tests/ │ ├── test_engine.py │ └── test_auth.py └── CLAUDE.md这样划分之后,每一条 CLAUDE.md 规则都能挂在具体的目录或文件上,模型在改动时就能快速判断“当前文件属于哪一层,这一层允许我做什么”。比如对 utils 下的文件,规则可以是“允许自由重构,但禁止引入有状态的设计”;对 core 下的文件,规则则要严格很多,“接口签名不得修改,如确需修改必须提前列出全部调用方”。
4. 实操过程与核心环节实现
4.1 起草 CLAUDE.md:从空文件到行为契约
第一步,建立一个最简可用的 CLAUDE.md。这个文件不用一次写完美,先搭骨架,跑一轮再迭代。我习惯用三段式组织内容:项目概述、工作流规则、技术约束。下面是初版的骨架节选:
# 项目概述 - 这是一个基于 Python 的模块化数据处理服务,核心功能是数据清洗与指标计算。 - 代码分三个层次:core(核心算法)、features(业务功能)、utils(工具函数)。 - 依赖关系:core 和 utils 不依赖 features;features 可以依赖 core 和 utils。 # 工作流规则 - 理解阶段:开始任务前,先阅读 CLAUDE.md,再浏览 src 下相关文件。 - 规划阶段:多文件改动必须先在对话中输出改动计划,列出每个文件的变更点。 - 执行阶段:遵循“一次最少文件”原则,先改依赖树底层的文件,再改上层的文件。 - 验证阶段:每完成一个模块的改动,先跑测试,再继续下一个模块。 # 技术约束 - 项目使用 Python 3.11,不允许引入新的第三方依赖,除非在对话中明确说明理由。 - 所有函数必须有类型注解,公共函数必须写 docstring。 - 修改 public API 时,必须同步更新调用方,不允许留下悬空的 import。 - 保持现有命名风格:私有函数以下划线开头,常量使用大写蛇形。你可能觉得这些内容“太普通了”——但它就是管用。原因在于,这些规则不是抽象口号,而是可以直接转为行为检查项的指令。Sonnet 5.5 在生成代码前会先“读”这些约束,然后把自己当成一个遵守约定的团队成员,而不是一个自由发挥的生成器。
我在第一轮跑完之后,又往技术约束里追加了几条针对性规则,包括“禁止修改与当前任务无关的文件”“core 层接口变更必须提前声明”等。这些追加的规则,都是第一轮执行时模型暴露出来的问题,属于“用血泪换来的经验”。
4.2 双观察者模式:让模型自己核对规则
执行多文件重构时,我发现一个很有用的技巧,叫“双观察者模式”。具体做法是:
- 第一次对话时,只要求 Sonnet 5.5 输出“改动计划”,不写任何代码。
- 人为审查计划,指出其中不符合 CLAUDE.md 的部分(比如动了不该动的文件)。
- 第二次对话时,把审查意见放回上下文,让模型按修正后的计划执行。
为什么要这样做?因为模型在“规划”和“执行”两种状态下的行为模式是不一样的。规划时它更冷静,能充分考虑依赖关系;执行时它容易“上头”,一旦开始写代码就会想一口气把所有事都办完,这时候最容易被细节带偏。拆成两个阶段,就能把“想”和“做”分开。
实际执行中,我通常这样安排提示词。规划阶段用:“请阅读 CLAUDE.md 和源码,然后输出一份多文件重构计划,包含每个文件的变更点和理由。先不要写代码。”执行阶段用:“请严格按照上面讨论的计划和 CLAUDE.md 的约束,逐个文件实施重构。改完一个文件就跑相关测试,不要一次性修改所有文件。”
两次提示词的核心差异,在于前者把“规则检查”放在输出之前,后者把“规则检查”放在每一步之后。两种方式交替使用,就能持续把模型行为拉回合规区间。
4.3 参数选择与阈值设置:一次靠谱重构的量化标准
这里讲讲怎么给约束定“量”。因为 CLAUDE.md 是自然语言,模型判断“是否符合”时有弹性。我建议在规则里加入可量化的检查标准,减少歧义。常用的量化维度包括:
| 量化维度 | 典型阈值 | 作用 |
|---|---|---|
| 单次修改文件数 | 不超过 5 个 | 防止重构范围无限扩散 |
| 测试通过率 | 每阶段相关测试全绿 | 防止错误累积到最后爆发 |
| 函数长度 | 不超过 60 行 | 防止模型顺手制造“大泥球”函数 |
| 接口影响面 | 修改前必须列出全部调用方 | 防止静默破坏现有 API |
给规则加数字,不是为了显摆指标,而是让模型在做“取舍”时有一个可执行的标准。比如“尽量少改文件”这种模糊指令,模型可能理解为改 3 个,也可能理解为改 10 个;但“单次不超过 5 个”就明确多了。实测下来,加了量化阈值之后,模型在重构时的“越界行为”明显减少。
关于阈值如何定,我的经验是先松后紧。第一轮把阈值设得宽一些(比如允许改 8 个文件),观察模型的行为模式,看看它容易在哪里越界,然后再收紧。这样比一开始就严苛约束更容易让模型“学会”你的偏好。
4.4 小步提交与重构验证闭环
执行重构时,我坚持用“小步提交”来配合 CLAUDE.md 约束。具体流程是:
- 按计划修改第一个底层文件。
- 运行该文件相关的测试。
- 通过后,提交一次,commit message 里写明“refactor:”。
- 再进入下一个文件。
这个流程看起来简单,但价值极大。它有两个好处:一是每次出问题,影响范围都很小,定位快;二是模型在“单文件修改 + 立即验证”的节奏下,不太容易产生大范围的连锁改动。我甚至会让模型在每次提交前,自己用一句话说清楚“改了什么、为什么这么改”。这一步能强迫模型梳理自己的决策逻辑,很多隐藏的错误在这一步就会暴露。
补充一个细节:测试命令要写进 CLAUDE.md。不要在提示词里临时告诉模型“你跑一下 pytest”,而是直接在规则里写“修改 src/core 下的文件后,必须运行 pytest tests/test_engine.py 或相关用例”。这样模型在执行时不会犹豫,也不会尝试用别的命令碰运气。
5. 常见问题与排查技巧实录
5.1 CLAUDE.md 约束被忽略:规则与上下文的博弈
我在实践中遇到过好几次“约束失效”的情况——明明 CLAUDE.md 里写了规则,模型还是该干嘛干嘛。排查到最后,原因不外乎三个方向:
- CLAUDE.md 内容太靠后,或者被其他长文本淹没。模型对上下文的注意力不是均匀分布的,中后段的内容容易被忽略。解决方法是把最关键的三条规则放在文件最前面,用短句强调。
- 规则本身模糊,缺少动词。比如“注意模块边界”就不如“features 模块不允许 import core 内部私有函数”来得有效。
- 当前任务与规则产生冲突时,模型倾向于顺从任务。如果你说“不管其他,先快速改完”,模型就可能把 CLAUDE.md 抛在脑后。这时需要把规则重新嵌入提示词,而不是只靠文件。
一个有效的补救手段是:在提示词末尾固定加一句“请最后检查一遍,你的改动是否符合 CLAUDE.md 中的全部规则。逐条核对后再输出。”这句话能让模型在生成后做一次“规则复核”,相当于人为加了一道质检关卡。
5.2 上下文窗口被撑爆:多文件任务的资源管理
多文件重构的另一个痛点,是上下文很容易被源码撑爆。模型注意力窗口再大,也架不住把十几个源文件和测试文件全部塞进去。我的应对策略是“分层喂入”:
- 第一轮:只喂 CLAUDE.md + 文件树 + 依赖关系说明,让模型先输出计划。
- 第二轮:喂计划 + 第一个待改文件的完整内容,执行该文件的修改。
- 第三轮:喂计划 + 第二个待改文件,依此类推。
这样做的好处是,每一轮模型看到的都是“够用的上下文”,而不是“一锅炖”。实测下来,采用分层喂入后,模型的输出质量显著提高,漏改、漏同步 import 的情况大幅度减少。
另外,我建议在 CLAUDE.md 里明确写出“不要一次性读取所有文件,按需读取相关文件”。很多模型在没有约束时,会试图把所有文件都读一遍来“求稳”,结果反而导致上下文混乱。
5.3 接口变更后悬空 import 的排查
重构中最常踩的坑,是改了 core 层的接口,但 features 层还引用旧的名字。这个问题的根源在于:模型在修改单个文件时,没有全局视野。排查思路我整理成了一张速查表:
| 症状 | 排查步骤 | 解决方案 |
|---|---|---|
| 修改接口后,其他模块报 ImportError | 全局搜索旧接口名,确认引用位置 | 在下一次提示中要求模型同步更新所有引用 |
| 模型“忘记”改调用方 | 查看 git diff,检查所有出现旧名的地方 | 在 CLAUDE.md 中追加“改 public API 后必须全局搜索”的规则 |
| 逻辑改对了,但测试还是不过 | 运行全量测试,忽略语法级错误,重点看业务断言 | 人工 review diff,核对参数顺序与边界条件 |
这几招配合起来,基本能把悬空 import 杜绝在提交之前。不过要提醒一句:全量测试只能发现“语法级”或“运行级”错误,对于逻辑语义错误(比如用错了参数顺序)还是要靠人工审查兜底。所以我的建议是,重构完成后,至少人工审核一遍 diff,不要完全交给自动化检查。
6. 进阶玩法:CLAUDE.md 与项目约束的持续迭代
你可能会问:CLAUDE.md 写好了就一劳永逸吗?当然不是。项目在演进,约束也要跟着演进。我有一个常规做法:每次重构项目结束时,回头看一眼 CLAUDE.md,把本次踩坑中暴露出的新约束补进去。比如有一次重构中,模型反复把工具函数写得有状态,我就在 CLAUDE.md 里加了一条:“utils 下所有函数必须是无状态的,禁止使用模块级可变变量。”这个规则一旦写进去,后续模型就不会再犯同样的错。
还有一个思路值得尝试:把 CLAUDE.md 视为“团队规范文档”而不是“任务说明书”。这意味着你可以把代码评审意见、命名规范、模块设计原则都沉淀进去。时间一长,这个文件就会变成项目的“活的架构文档”,价值远不止约束一次重构。
如果你在做更复杂的跨项目重构,还可以考虑为不同目录配置不同的行为约束。比如在 core 目录下要求更严格的接口稳定,在 features 目录下允许更多的灵活性。这种“目录级规则”比全局规则更精细,能让模型的行为在不同上下文中自适应。
最后分享一个小技巧:把 CLAUDE.md 第一行写成一个“身份锚点”。比如“你是本项目的高级工程师,负责维护模块化架构,所有重构必须维护现有接口稳定性。”这一行虽然不会魔法般改变模型的能力,但它能在整个对话中持续提供行为基准,让后续所有规则都更容易被遵循。我实测下来的感受是,有身份锚点和没有,模型的行为稳定性有明显差别。
7. 写在最后:约束的目标是让 AI 更像协作伙伴
回到标题本身:借助 CLAUDE.md 约束 Sonnet 5.5 的多文件重构行为,这里面的关键词不是“CLAUDE.md”,也不是“Sonnet 5.5”,而是“约束”。AI 工具的能力边界正在快速扩展,但真正决定项目成败的,往往是使用者如何设定边界、如何定义“正确行为”。
我个人在实际操作中的体会是:不要指望模型一次就给你理想的结果,也不要因为一次返工就否定它的价值。把 CLAUDE.md 当成一个可以随时打磨的“行为契约”,每次重构后迭代它,你会发现自己和模型之间的“默契”会越来越强。这种默契积累到一定程度,多文件重构就不再是提心吊胆的冒险,而是可以放心交出去的标准流程。
如果你正在用 Sonnet 5.5 做类似的模块化改造,或者正为多文件重构的失控而头疼,我建议你先花半天时间把 CLAUDE.md 写好,再按照“双观察者 + 小步提交”的流程跑一轮。效果可能会让你意外。