关于 refactor-clean
引导编码 Agent 将重构方向定为清晰的概念归属和更简单的代码结构。它强调删除冗余机制,只保留有具体需求支撑的兼容处理,并通过消费者验证行为。
主要功能
- 01明确代码概念的归属
- 02删除冗余输入与适配器
- 03按职责拆分过载模块
- 04在消费者层验证行为
- 05对照行为审视改动规模
适用场景
- 01合并重复实现
- 02拆分职责混杂的模块
- 03清除过时的兼容路径
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dzhng/ skills --skill refactor-clean安装后在 Claude Code 中输入/refactor-clean确认已加载。
详细介绍
简洁重构
用更简单的结构替换旧结构:如果今天重新设计代码库,它会采用这种结构。重构不是在问题旁边添加兼容层,而是调整归属,直到每个概念都有且只有一个明确的归属。这有两种情况:将 N 个重复的归属合并为一个,或者把一个负担过重的模块拆分成它掩盖的多个归属。
工作流程
- 检查所有涉及路径中的名称,即使不需要结构性重构也要如此。询问每个名称是否准确描述了该概念当前的范围。立即重命名含义不明的文件、符号、导入绑定和共享导出;在同一轮中更新所有使用方、测试、文档和 Agent 指令。仅在特定场景代码中保留场景名称,然后再次搜索过时引用。
- 找出缺少明确归属的概念——可能是多个归属重复实现了它,也可能是多个概念被揉进一个负担过重的模块。确定各自应有唯一归属的对象:环境、定价规则、几何数据源、状态机、数据契约、渲染器阶段、API 结构、UI 状态或测试判定标准。
- 找出所有现有归属和使用方。在证实其必要性之前,将包装器、别名、流程局部常量、复制的结构体、迁移注释和“临时”分支都视为沉积物。
- 在放宽检查或将输入改为可选之前,先进行删除检查。对于受影响路径中的每个输入、标志、保护条件、回退方案和适配器,都要问:如果移除它,会破坏哪些必需行为?追踪其写入方、传输过程、读取方、验证器和使用方;并与权威归属进行比较。仅仅让冗余输入变为可选,不算完成。如果移除它仍能保留必需行为,就删除该机制,以及调用点、测试和文档中的残留。
- 将任何剩余概念提升到其自然归属位置。选择从头设计时会负责该概念的模块,然后让旧调用点直接使用该归属。在同一轮中删除或合并过时路径。
- 仅当确认存在具体使用方,且移除会破坏具体行为时,才保留兼容性。可以安全忽略的旧输入,无需仅因它们仍可能到达就添加解析器、验证器或适配器。如果确实需要桥接层,就让它尽可能精简,并注明移除条件;假设中的调用方不算。
- 通过使用方验证行为,包括受影响的失败和重试路径。再次搜索代码、测试、文档和 Agent 指令,确认已删除的机制不再出现。只有当受影响路径中每个保留的机制都有明确的使用方,且能说明它保留了什么行为时,才算完成;同时报告保留的桥接层及其移除条件。
规则
-
每一行代码都是负担——最好的机制,就是不必写下的机制。 在构建任何防护、验证器、收敛协议或清理路径之前,先通过两项检查:
- 平台是否已经处理了这件事? 在围绕组件或框架编写代码之前,先阅读你要应对的组件或框架——了解它的定时任务、重试、默认设置和生命周期。在一个已经会自行清理的组件旁手写清道夫,纯属负担;而且,调试它的人可能不知道这个组件本来就不需要清道夫。
- 它防护的故障会改变任何实际结果吗? 沿着受防护的条件追踪到它的使用方。如果使用方并不在乎——比如,顺序检查的结果传给了不关心顺序的使用方,或验证的数据只由我们自己的代码生成——那么这项防护毫无作用,不该编写。常见的情况是:某项不变量在读取时被重新检查,但唯一的写入路径早已保证了它,因此只有被手动篡改过的行才会触发这个分支。问问哪个调用方可能产生这种错误状态;如果诚实的答案是“没有,除非有人手动修改数据库”,就删除这个分支。“它可能不一致”不是理由;“这会导致使用方做出错误行为”才是。 标准不是“这样做正确吗?”——防御性代码通常都没错。标准是“如果没有这一行,谁会遇到什么问题?”如果没有具体答案 → 不写这一行。
-
衡量改动,并将不合理的规模与行为比例视为结构缺陷。 接受改动之前,统计它实际花费了多少——生产代码与测试和文档分开统计,注释与逻辑分开统计,因为由解释性文字主导的差异,与由机制主导的差异并不相同:
git diff --numstat <base> -- ':!specs' ':!*.test.*' ':!**/test-harness'然后阅读新增的代码行,而不只是看总数。小小的行为变更却耗费大量代码行,是一个危险信号——这最可靠地表明,修复方案是在对抗现有结构,而不是融入其中:在该放通用逻辑的地方叠加特殊情况,在真正的所有者旁边引入第二个所有者,或者用包装层连接本该合并的两个事物。正确的做法是根据代码本身所需的结构重新设计,而不是附上一段解释为什么它不得不这么庞大的文字就提交;那段解释才是异味,而非补救措施。 两条诚实原则,否则数字毫无意义:变更未涉及的文件若因格式化而产生差异,就不属于这次变更,必须排除在提交之外;净增减接近零,也可能掩盖实际负担——新增的 cron、表列、索引、端点、依赖项或配置标志,都是现在有人需要负责维护的部分,因此应将它们单独列出,而不是只看数量。
-
不要过度看重现有架构的沉没成本。“它已经存在而且能用”不是保留某种结构的理由——编码 Agent 可以低成本地进行大规模架构切换,因此应根据最终状态的质量来评估重构规模,而不是看它替换了多少代码。必须保留行为时,应在消费方表面用测试固定行为,再替换底层架构——不过大多数时候,甚至不该原样保留旧的接缝:大型重构正是重新划定接缝的机会,应让它们符合代码库当下所需的形态,而不是在新基础上忠实重建旧接口。
-
优先使用一个共享的基本构件,而不是 N 个适配器。适配器只有在外部边界或作为短期迁移接缝时才可接受。
-
拆分承载过多概念的模块——拆分也是重构。 这是合并重复内容的另一面:当一个文件或函数累积了互不相关的职责时,应沿着归属边界将其拆开,让每个部分负责一块连贯、读者能说出名称的内容,原模块则精简为组合层。文件超过约 1000 行是异味,不是定论——有些模块的规模有其合理性(连贯的状态机、生成的表格、内部耦合紧密的单一算法),但一个注册几十条路由或在行内处理多个领域的巨型文件,体现的是职责累积,而非内聚。只有在每个新模块都负责一项可命名的职责,且依赖随职责迁移、导入压力因此降低时,拆分才是正确的;如果拆分只是为了减少行数,却把一个概念散布到读者必须重新拼起来的多个文件中,那就是错误的。
-
构建新东西之前,先了解现有内容。 在编写新机制——形状、计算、资源、状态机、数据契约——之前,应先搜索代码库,看看是否已有实现此功能或足够相近、可以共用的实现。只有了解现状后,才能做出真正的决定:复用它、合并两个近似副本,或将共享核心提取为独立模块供双方调用——而且这项决定应在动手之前做出,而不是事后补上。复用并非总是答案;现有实现可能有误,或确实不同,此时就应有意构建新实现。这样做是为了避免在不了解现有内容的情况下重复实现:两个必须保持一致的实现会悄然偏离,各自重新推导对方早已确定的细节(方向、单位、边界情况、顺序)。
-
默认不要保留仅供开发使用的兼容性。尚未发布的脚手架应立即迁移到干净的契约上。
-
完全杜绝沿革暗示:名称应描述事物本身,绝不描述它的来源。 编码历史的名称——以前缀标明拆分来源巨型文件的切片文件、
foo-v2/foo-new/foo-legacy、以产出模块的实验命名的模块、以其取代的 API 命名的包装器——对不了解历史的读者毫无信息,而且旧事物消失后,还会积极误导了解历史的人。检验方法:一个今天加入、对历史一无所知的人,会选这个名称吗?如果名称只有结合背景故事才说得通,就重命名;历史存在于 git 中,而不在标识符里。同样的规则也适用于沿革注释(“以前这里是……”“从 X 移来”)——它们描述的是差异,而非代码,并会在所指对象消失时立即过时。 重命名是日常维护,不是独立项目,也不是请求许可的理由。应主动按消费者所需的范围进行;机械性差异再大,也不是保留误导性名称的理由。应区分内部标识符与持久化键、外部契约:只有在有明确消费者时,才根据上述兼容性规则保留后两者。不要留下保留旧词汇的内部别名。 -
优先采用幂等契约,而非拒绝请求。 当某项操作可能被请求两次时——响应丢失后的重试、用户再次点击同一按钮、重放的 webhook——达到请求的最终状态就应成功,而不是报错。如果 X 已安装在其应有的位置,那么“安装 X”就是成功:返回该对象。只有当第二次请求与第一次请求确实意味着不同的事情时,拒绝才是正确的——已有不同的对象占用了该名称,满足请求会破坏或遮蔽它。反例的迹象是:调用方必须针对你的错误代码编写特殊处理才能恢复正常行为,或者重试路径需要先读取“它是否已存在?”,从而引入竞态。这一原则不只适用于写入:删除不存在的对象、取消订阅和“标记完成”切换,本质上都是幂等操作;让它们返回 404 或 409,会把簿记负担推给每个调用方。
-
将模型约束在生产环境实际写入的范围内。 如果某个架构允许的数据多于所有写入方实际产生的数据——例如每个流程都只存一个元素的列表,或永远不会到达的状态——就会让每个消费者都承担一般情况的成本。迹象是:数据只能包含一个值时,消费者却要问“但到底是哪一个?”。应在写入路径上强制执行约束;等真实需求出现时再放宽。
-
当差异问题属于此类时,应在统计信息、测试或调试输出中明确显示归属。
-
如果某项检查重新推导代码已经算出的值,它就会与代码产生偏差。 独立重新计算几何、状态或派生量的测试或第二个消费者,可能因纸面上看不出的差异(如运算顺序或舍入细节)而与代码结果不一致,并给出错误判定。应导出归属方计算出的值,让检查使用该值,以准确验证代码的产出:计算只有一个归属方,而不是两个大体一致的实现。
-
对称或无特征的占位内容,可能掩盖其所替代对象中的方向或坐标错误。 无论坐标系是否翻转,对称的替代物渲染效果都相同,因此缺陷会一直隐藏,直到真实的非对称资源暴露问题。用真实资源替换占位内容时,不要只验证能否渲染,还要重新验证方向和构图。
-
更新规范或交接文档,记录新的不变量,而不是机械地列出文件清单。
-
如果重构开始扩展到无关行为,应将其拆分:先提交共享契约,再通过便于评审的多个阶段迁移消费者。