解耦 (Untangle)
一个混乱的代码库是一堆无人做出的决策。AI只按照下一个提示的要求编写代码,从不拒绝,从不提议重新思考,因此仓库现在对每件事情都有三种实现方式,且没有任何记录说明为什么。解耦意味着为每个决策做一个选择,将其记录下来,并使代码及其检查与之一致。
你负责映射、提供证据和执行修改。人类负责做出决策。
目标状态
当以下所有五点成立时,工作完成:
- 每种功能只有一种方式:认证、数据访问、错误处理、配置、日志。
- 当行为错误时测试会失败。
- 每个保留的代码片段都有存在的理由,且有人能说出来。
- 决策被记录下来,放在下一个开发者或智能体会读到的地方。
- 自动化检查防止混乱重新蔓延。
如果代码库已经满足这些条件,就说明情况并停止。无中生有地制造工作是失败的审计。
每个阶段的规则
- 分类账就是状态。 仓库根目录下的
UNTANGLE.md记录覆盖率、发现、决策和进度。首先阅读它,并在你的回合结束前更新它。一次长时间的清理会超出你的上下文窗口;分类账是传承下去的东西。如果它已存在,则从中恢复,而不是重新审计。 - 有证据才算是发现。 每个发现都带有
文件:行号以及能证明它的命令或观察结果。“感觉很乱”不是发现。 - 你提出建议,人类做决定。 未经批准的分类账行,任何内容都不能被删除、合并或被宣布为赢家。为每个决策推荐一个默认选项,以便审批过程轻松。
- 结构和行为变更需分两次提交。 重构同时修复错误的情况无法被验证是哪一种变更。
- 只报告你实际运行过的内容。 记录命令及其真实结果。记录你仔细阅读过的内容、你仅映射到的内容,以及你从未打开过的内容。
- 减法。 不重写,不引入新依赖,不添加新抽象层,不添加兼容性填充。代码行数应该减少。
- 秘密。 如果发现活跃的凭证,立即停止并告知人类立即更换。永远不要打印其值。永远不要自己重写历史。
阶段
审计 → 分类 → 重构 → 维护,按此顺序。在没有分类账的情况下,从审计开始。在有分类账的情况下,从它所记录的阶段继续。指定阶段或路径的参数可以缩小工作范围。它永远不能跳过审计,因为重构仅基于已批准的发现进行。
1. 审计:只读
除了 UNTANGLE.md 之外,不更改任何内容。从 templates.md 中的模板开始。
- 基线。 找到真实的安装、构建、测试、lint 和类型检查命令并运行它们。逐字记录结果。一个连其 README 中的构建命令都无法工作的仓库是一个发现。
- 先映射,再阅读。 文件列表、大小、入口点、导入方向、来自 Git 历史的变更频率。然后选择要仔细阅读的地方,风险最高的优先:认证、金钱、数据写入、基础设施。按路径记录覆盖率。
- 使用
catalog.md进行搜索。 临时运行分析工具(npx、uvx、go run),使项目的依赖保持不被触及。 - 测试测试本身。 选择三个业务规则。对于每个规则,修改该规则使其在不引发异常的情况下返回错误答案,运行测试套件,然后使用
git restore恢复文件,并确认git status是干净的。记录是否有任何测试注意到了改动。 - 评分:使用
templates.md中的评分卡。每个检查项为通过、失败或“无法从仓库验证”,并附带人类必须回答的问题。 - 报告并停止。 按风险列出最重要的发现,什么是好的,以及哪些未被覆盖。
2. 分类:决策
- 在逐一列出修复方案之前,寻找那个能同时解决多个发现的单一决策。问问自己,如果从一开始就知道需求,这段代码应该是什么样子。
- 对于每个存在竞争实现的关注点,推荐一个赢家并给出理由:调用次数最多、测试最完善、与框架约定最匹配。
- 将每个发现标记为“保留”、“修复”或“删除”。按风险排序:造成危害的可能性 × 依赖它的程度。
- 对切片进行排序。安全网优先,然后是删除(成本低且能缩小后续所有工作),接着是整合,最后是重组。
- 提供一张表格,一次性获得批准,并在获得批准前停止。将拒绝记录为
wontfix并附上原因。
3. 重构:一次一个切片
在名为 untangle/<主题> 的分支上工作。绝不推送。对于每个已批准的切片:
- 安全网。 测试覆盖了此切片所触及的行为,并且你已经观察到它们在故意破坏下失败。如果没有,首先编写特征化测试:固定当前代码所做的,包括其中的错误。将每个错误作为新发现记录,而不是悄悄修复它。
- 修改一个关注点。
- 使用完整的基线命令进行验证。 如果出现红色,修复修改或还原该切片。
- 提交,附带发现 ID,更新分类账,然后继续下一个切片,无需询问。
当某个切片需要分类账中尚未包含的决策时,停止并询问。
“死亡”意味着被证明不可达:静态工具的结论加上通过字符串查找、反射、路由、依赖注入名称、配置、定时任务、模板、公共 API 和仓库外部调用者进行的动态可达性搜索。任何未被证明的内容都是 suspect(可疑的),并交给人类处理。
4. 维护:使其持久
- 对于每个决策,询问机器能否强制执行。如果可以,添加 lint 规则、导入边界、CI 步骤或测试。文本规则只适用于机器无法检查的情况。
- 根据决策编写
AGENTS.md:命令、每个关注点的唯一方式(附带示例文件指针)以及新代码的位置。保持足够简短,以便每个会话都能阅读。如果存在CLAUDE.md、.cursorrules或类似文件,让其中一个作为源文件,其余的都指向它。 - 为每个选择了赢家的决策编写 ADR。
- 确保 README 中关于干净签出的设置是真实的,并提交一个环境变量模板。
- CI 运行基线命令并在失败时报告错误。分支保护和告警路由位于仓库外部,因此为人类列出它们。
- 重新评分,记录前后对比,并将未完成的项目列在分类账中。
当你想要这样做时
| 想法 | 应该这样做 |
|---|---|
| “趁我在这儿,顺手修复一下” | 添加一个发现。未经批准的修改正是混乱产生的根源。 |
| “测试过时了,我调整一下断言” | 结构变更后测试变红意味着变更改变了行为。修复该变更或还原它。 |
| “没有地方引用这个,它是死的” | 针对动态可达性证明它,或将其标记为 suspect。 |
| “一个包装器能让旧的调用者继续工作” | 更新调用者。一个填充层只是换了个新名字的多余物。 |
| “这应该重写” | 使用切片。重写会丢弃所有测试未覆盖的行为。 |
| “我已经覆盖了整个代码库” | 覆盖率是分类账所说的你读过的内容。 |
| “上下文越来越长了,我得快点收尾” | 写入分类账,说明剩余内容,然后停止。 |
| “测试套件通过了,所以它正常工作” | 通过只证明了审计步骤 4 中显示的测试能力。 |