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.mdrefactor-with-safety/SKILL.md
GitHub安全重构助手
中文简介
安全重构助手把“代码更整洁”约束为可证明的结构演进:先锁定现有可观察行为和公共契约,再以可回滚的小步改变内部结构。它不把测试绿灯、格式化通过或文件变短当成行为保持的证据。
使用背景
拆分大文件、抽取模块、去重、改名或移动边界时,风险常藏在未写下的调用方、序列化数据、事件、日志、权限、时序和性能上。更危险的是把新功能混入重构:两类变化互相遮蔽,使失败无法定位或回滚。此 Skill 先建立行为基线和变化分类,随后每一步只改变一个可验证边界。
核心原则
- 重构默认保持外部行为;任何新用户可见行为、数据语义或兼容策略都是独立功能变更,须单列验收、提交和验证。
B-*是调用方可观察的不变量,先由现有测试或特征化测试锁定;不从期望的新架构推断旧行为。C-*覆盖公共 API、事件/队列 payload、持久化数据、配置、权限、错误语义和外部副作用;“内部改动”不免除消费者和兼容检查。- 先识别依赖边界和可替换缝隙,再抽取;不要为拆文件而引入循环依赖、隐藏全局状态或跨层回调。
- 机械变更与语义变更分开:可自动验证的移动/重命名/格式化先独立完成,行为逻辑、默认值、错误或时序变化另起步骤。
- 每个
S-*都有验证、回滚点和停止条件;不能证明行为、性能或契约时停止扩大范围并记录GAP-*。
适用场景
- 拆分职责过多的大文件,同时保持页面、服务或作业的现有行为。
- 重命名或迁移公共 API、事件、配置或数据字段,并需要明确旧消费者、兼容窗口和废弃路径。
- 提取依赖、模块或共享逻辑,减少重复、循环依赖、全局耦合或难以测试的副作用。
- 在可运行仓库中为维护性、可测试性、性能可观测性或边界清晰度制定小步重构计划。
不适用场景
- 目标是新增用户能力、改变业务规则或修复未定位 Bug 时,先用
issue-to-pr或bug-debugging-playbook建立验收与证据;不得将其伪装为重构。 - 没有仓库入口、调用方、测试拓扑或允许命令时,先用
codebase-onboarding;不能凭文件名设计宽泛重构。 - 已知公共契约、数据迁移或外部副作用需要跨团队发布协调时,本 Skill 只给出边界和停止条件;结合实施计划与发布门禁执行。
- 只想统一风格、全仓格式化或升级依赖而无明确行为基线时,不启动结构重构;将机械维护工作独立排期。
输入要求
未提供的材料标为 [未知],不可补造路径、测试、消费者、性能数字或命令结果。
- 重构目标、受影响边界、非目标和“不应改变”的用户/调用方结果。
- 入口、调用方、公共 API/schema、存量数据、事件、配置、权限、错误语义与副作用资料。
- 现有测试、相邻测试约定、可运行命令、失败历史和可补的特征化测试位置。
- 模块依赖图或可读代码范围:输入输出、状态拥有者、I/O、全局状态、循环依赖和可抽取缝隙。
- 兼容与发布约束:旧客户端/消费者、版本窗口、数据迁移、开关、外部系统、owner 和废弃计划。
- 性能基线或测量条件:关键路径、规模、延迟/吞吐/内存/查询预算、采样方法和允许波动。
- 工作区、基线 commit、可独立提交方式、回滚能力和不可逆副作用。
信息不足时的处理
- 行为或非目标未知: 只输出澄清问题、候选观察点和停止条件;不开始宽泛重构,不把“更易维护”写成充分理由。
- 测试不足: 先读相邻测试、日志、公开响应和调用方,提出最小
B-*特征化测试。无法运行时记录GAP-*与人工/生产观测替代证据,不称行为已锁定。 - 公共消费者或兼容窗口未知: 将
C-*标为阻断,搜索消费者、schema、事件订阅和迁移记录;不得直接删除旧字段、事件或默认值。 - 性能没有基线: 在热路径、批量、缓存、查询或资源释放可能变化时,先测量或约定采样条件;不能用“感觉更快”通过。
- 功能请求混入: 分成重构轨与功能轨。功能轨没有独立验收、失败测试和提交时,只交付拆分建议,不能实施或声称安全。
工作流
- 建立重构卡。 记录目标、非目标、范围、事实/推断/未知、工作区基线和停止条件;将保持行为编号为
B-*,公共契约编号为C-*。 - 读取相邻责任。 只扩展到入口、调用方、数据/副作用、错误路径、配置和测试;画出依赖方向、状态拥有者和可抽取缝隙,标出不应跨越的边界。
- 锁定行为基线。 对每个
B-*找现有测试,缺失时先写特征化测试:使用代表性输入和成功对照,断言公开输出、状态转换、副作用顺序或契约,而非私有函数或调用次数。 - 登记公共契约与性能。 为每个
C-*写消费者、兼容窗口、迁移/双写/适配策略和废弃条件;对可能退化的路径建立P-*性能基线或显式GAP-*。 - 拆分变化类型。 将格式化、无语义移动和纯重命名列为机械步骤;将逻辑、默认值、错误、时序、数据和副作用变化列为语义步骤。功能轨另开验收和提交,不与重构步骤混合。
- 排可逆小步。 每个
S-*只跨一个边界,写预期 diff、受保护的B-*/C-*、验证、回滚点和触发停止的信号;优先先加适配层、再迁消费者、最后删除旧路径。 - 执行并逐步验证。 每个
S-*后运行最小相关特征化/契约测试,再运行受影响相邻套件、类型/构建和性能检查中适用项,记录实际E-*命令、退出码和结果。 - 复核与交接。 比较基线、契约和性能,检查 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 输出;任何未知、未运行或失败都保留标签:
- 重构卡:目标、非目标、事实/推断/未知、范围、基线和停止条件。
B-*行为与特征化测试表:观察、对照、现有/新增测试和证据状态。C-*公共契约与依赖边界表:消费者、数据/副作用、兼容和迁移/废弃条件。P-*性能基线:路径、负载、指标、方法、预算和不可测缺口。S-*小步序列:机械/语义/功能分类、预期变更、验证、回滚点和停止信号。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 重命名、重构混入功能的三个场景。