refactor-with-safety

Use when restructuring existing code, splitting large files, renaming public APIs, reducing duplication, or improving maintainability while behavior, compatibility, side effects, performance, and roll

SKILL.md

GitHub

安全重构助手

中文简介

安全重构助手把“代码更整洁”约束为可证明的结构演进:先锁定现有可观察行为和公共契约,再以可回滚的小步改变内部结构。它不把测试绿灯、格式化通过或文件变短当成行为保持的证据。

使用背景

拆分大文件、抽取模块、去重、改名或移动边界时,风险常藏在未写下的调用方、序列化数据、事件、日志、权限、时序和性能上。更危险的是把新功能混入重构:两类变化互相遮蔽,使失败无法定位或回滚。此 Skill 先建立行为基线和变化分类,随后每一步只改变一个可验证边界。

核心原则

  • 重构默认保持外部行为;任何新用户可见行为、数据语义或兼容策略都是独立功能变更,须单列验收、提交和验证。
  • B-* 是调用方可观察的不变量,先由现有测试或特征化测试锁定;不从期望的新架构推断旧行为。
  • C-* 覆盖公共 API、事件/队列 payload、持久化数据、配置、权限、错误语义和外部副作用;“内部改动”不免除消费者和兼容检查。
  • 先识别依赖边界和可替换缝隙,再抽取;不要为拆文件而引入循环依赖、隐藏全局状态或跨层回调。
  • 机械变更与语义变更分开:可自动验证的移动/重命名/格式化先独立完成,行为逻辑、默认值、错误或时序变化另起步骤。
  • 每个 S-* 都有验证、回滚点和停止条件;不能证明行为、性能或契约时停止扩大范围并记录 GAP-*。

适用场景

  • 拆分职责过多的大文件,同时保持页面、服务或作业的现有行为。
  • 重命名或迁移公共 API、事件、配置或数据字段,并需要明确旧消费者、兼容窗口和废弃路径。
  • 提取依赖、模块或共享逻辑,减少重复、循环依赖、全局耦合或难以测试的副作用。
  • 在可运行仓库中为维护性、可测试性、性能可观测性或边界清晰度制定小步重构计划。

不适用场景

  • 目标是新增用户能力、改变业务规则或修复未定位 Bug 时,先用 issue-to-pr 或 bug-debugging-playbook 建立验收与证据;不得将其伪装为重构。
  • 没有仓库入口、调用方、测试拓扑或允许命令时,先用 codebase-onboarding;不能凭文件名设计宽泛重构。
  • 已知公共契约、数据迁移或外部副作用需要跨团队发布协调时,本 Skill 只给出边界和停止条件;结合实施计划与发布门禁执行。
  • 只想统一风格、全仓格式化或升级依赖而无明确行为基线时,不启动结构重构;将机械维护工作独立排期。

输入要求

未提供的材料标为 [未知],不可补造路径、测试、消费者、性能数字或命令结果。

  1. 重构目标、受影响边界、非目标和“不应改变”的用户/调用方结果。
  2. 入口、调用方、公共 API/schema、存量数据、事件、配置、权限、错误语义与副作用资料。
  3. 现有测试、相邻测试约定、可运行命令、失败历史和可补的特征化测试位置。
  4. 模块依赖图或可读代码范围:输入输出、状态拥有者、I/O、全局状态、循环依赖和可抽取缝隙。
  5. 兼容与发布约束:旧客户端/消费者、版本窗口、数据迁移、开关、外部系统、owner 和废弃计划。
  6. 性能基线或测量条件:关键路径、规模、延迟/吞吐/内存/查询预算、采样方法和允许波动。
  7. 工作区、基线 commit、可独立提交方式、回滚能力和不可逆副作用。

信息不足时的处理

  • 行为或非目标未知: 只输出澄清问题、候选观察点和停止条件;不开始宽泛重构,不把“更易维护”写成充分理由。
  • 测试不足: 先读相邻测试、日志、公开响应和调用方,提出最小 B-* 特征化测试。无法运行时记录 GAP-* 与人工/生产观测替代证据,不称行为已锁定。
  • 公共消费者或兼容窗口未知: 将 C-* 标为阻断,搜索消费者、schema、事件订阅和迁移记录;不得直接删除旧字段、事件或默认值。
  • 性能没有基线: 在热路径、批量、缓存、查询或资源释放可能变化时,先测量或约定采样条件;不能用“感觉更快”通过。
  • 功能请求混入: 分成重构轨与功能轨。功能轨没有独立验收、失败测试和提交时,只交付拆分建议,不能实施或声称安全。

工作流

  1. 建立重构卡。 记录目标、非目标、范围、事实/推断/未知、工作区基线和停止条件;将保持行为编号为 B-*,公共契约编号为 C-*。
  2. 读取相邻责任。 只扩展到入口、调用方、数据/副作用、错误路径、配置和测试;画出依赖方向、状态拥有者和可抽取缝隙,标出不应跨越的边界。
  3. 锁定行为基线。 对每个 B-* 找现有测试,缺失时先写特征化测试:使用代表性输入和成功对照,断言公开输出、状态转换、副作用顺序或契约,而非私有函数或调用次数。
  4. 登记公共契约与性能。 为每个 C-* 写消费者、兼容窗口、迁移/双写/适配策略和废弃条件;对可能退化的路径建立 P-* 性能基线或显式 GAP-*。
  5. 拆分变化类型。 将格式化、无语义移动和纯重命名列为机械步骤;将逻辑、默认值、错误、时序、数据和副作用变化列为语义步骤。功能轨另开验收和提交,不与重构步骤混合。
  6. 排可逆小步。 每个 S-* 只跨一个边界,写预期 diff、受保护的 B-*/C-*、验证、回滚点和触发停止的信号;优先先加适配层、再迁消费者、最后删除旧路径。
  7. 执行并逐步验证。 每个 S-* 后运行最小相关特征化/契约测试,再运行受影响相邻套件、类型/构建和性能检查中适用项,记录实际 E-* 命令、退出码和结果。
  8. 复核与交接。 比较基线、契约和性能,检查 diff 是否混入功能或无关整理。未满足项进入 GAP-*,结论为通过、有条件通过或停止;给出回滚动作与残余风险。

专业判断规则

行为不变量与特征化测试

  • 特征化测试记录“当前调用方依赖什么”,而非认可当前实现优雅或正确;若观察到疑似 Bug,保留为待决行为,不能在重构中悄悄修正。
  • B-* 至少写前置状态、公开动作、可观察结果和未受影响对照。对状态机、重试、排序、错误映射、权限、取消、缓存和副作用顺序明确适用或不适用理由。
  • 不用快照整棵 UI、私有 helper、局部变量、组件树或无业务意义的 mock 次数锁定行为;这些会阻止安全内部重构却不能保护消费者。

公共契约、数据与副作用

  • C-* 至少覆盖请求/响应字段、错误码、默认值、持久化 schema、迁移读写、事件 payload、配置名、权限语义和可观测副作用。每项写实际或未知消费者、版本窗口和废弃触发条件。
  • 公共 API 重命名默认采用适配/双读双写/版本协商/明确破坏性发布之一;只有消费者、迁移状态和批准窗口有证据时才删除旧契约。
  • 数据迁移、邮件、计费、消息、文件或第三方写入必须区分代码回滚、数据补偿和不可逆外部副作用;不可逆项不能写成“一键回滚”。

依赖边界、机械变更与性能

  • 抽取模块前写清输入输出、状态拥有者、I/O 与允许依赖方向。新模块不得反向依赖 UI/transport 层、读取隐藏全局状态或把循环依赖移到动态加载。
  • 机械变更独立提交且用编译/导入/引用搜索验证;语义变更必须额外回链 B-*、C-* 和测试。若两者无法分离,解释原因并按语义变更的更高门槛处理。
  • 性能验证使用同一负载、数据规模、环境和测量方法比较 P-*;对查询次数、队列深度、延迟、吞吐、内存或资源释放选择实际相关指标。没有可比基线时不宣称性能保持。

回滚点与停止条件

  • 每个 S-* 完成后应能回退到上一个绿灯 commit、关闭适配/开关或恢复旧调用路径;写明哪些数据或副作用不可回滚及补偿 owner。
  • 触发停止:特征化/契约测试未绿、性能超过预算、出现未知消费者/数据状态、依赖方向恶化、diff 混入功能、无法说明回滚,或工作区改动归属冲突。
  • 停止不是失败掩饰:交付当前证据、未做步骤、GAP-*、风险与重新开始所需材料,避免“顺手继续”。

输出契约

按 references/templates.md 输出;任何未知、未运行或失败都保留标签:

  1. 重构卡:目标、非目标、事实/推断/未知、范围、基线和停止条件。
  2. B-* 行为与特征化测试表:观察、对照、现有/新增测试和证据状态。
  3. C-* 公共契约与依赖边界表:消费者、数据/副作用、兼容和迁移/废弃条件。
  4. P-* 性能基线:路径、负载、指标、方法、预算和不可测缺口。
  5. S-* 小步序列:机械/语义/功能分类、预期变更、验证、回滚点和停止信号。
  6. E-* 证据账本与 GAP-*:真实命令、环境、退出码、关键结果、残余风险、owner 和结论。

质量门槛

  • 不在 B-* 未由实际测试或明确替代证据锁定时开展跨模块宽泛重构。
  • 不删除或重命名公共契约,除非 C-* 的消费者、兼容窗口与迁移/废弃条件可审查。
  • 不把功能、Bug 修复、默认值/错误/时序改变混入纯重构提交;不能拆分时按功能变更门槛执行。
  • 不将机械变更的编译成功当作语义保持;每个语义 S-* 必须有对应行为/契约验证。
  • 不用未运行命令、绿灯名称、性能直觉或“diff 很小”宣布安全;E-* 只记录实际结果。
  • 不在回滚不可说明、性能退化、依赖方向恶化、未知消费者或失败验证出现后继续扩大范围。

常见失败与修正

失败 修正
为拆文件直接移动代码,之后才发现加载、错误或副作用顺序改变 先写 B-* 特征化测试和依赖边界;一次抽取一个缝隙,每步运行证据。
将 getUser 改名为 fetchUser 并删除旧导出 先建立 C-* 消费者清单与兼容窗口,保留适配导出或版本策略,再迁移和废弃。
“重构时顺便加筛选功能” 新功能单列验收、失败测试、提交和回滚;没有这些资料时只完成结构轨。
大型格式化、移动和逻辑修改同一次提交 先独立机械提交;若无法分开,逐项以语义门槛验证并解释耦合原因。
测试绿了却在热路径增加 N+1 或内存缓存 用同负载 P-* 比较查询、延迟或内存;无基线时将性能标为阻断缺口。
遇到失败继续“把重构做完再看” 在最后绿灯 S-* 回滚,记录失败与 GAP-*,缩小范围或补材料后再开始。

参考资料

  • references/usage-guide.zh.md:准备材料、调用方式、阅读产出与上下游交接。
  • references/templates.md:重构卡、行为/契约/性能、小步与证据账本模板。
  • references/checklists.md:范围、基线、契约、依赖、步骤、回滚和交接检查。
  • references/examples.md:拆大文件、公共 API 重命名、重构混入功能的三个场景。