关于 qa-refactoring
面向开发者和编码 Agent 的重构技能,以行为保持、风险评估和测试证据组织重构过程。强调小步修改、边界级保护措施,以及高风险改动的发布控制。
主要功能
- 01建立基线并定义不变量
- 02围绕边界添加测试
- 03按风险选择保护措施
- 04采用小步、易审查的重构
- 05规划发布与回滚
适用场景
- 01重构认证或迁移路径
- 02跨模块移动逻辑
- 03规划大规模 codemod
- 04在交付期限内进行重构
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vasilyu1983/ ai-agents-public --skill qa-refactoring安装后在 Claude Code 中输入/qa-refactoring确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
learnings.consolidated.mdlearnings.mdagents/openai.yamlassets/process/code-review-quality.mdrefactoring-checklist.mdassets/quality-gates/javascript/eslint-config.jsassets/quality-gates/platform-agnostic/sonarqube-setup.mdassets/tracking/tech-debt-register.mddata/sources.jsonreferences/automated-refactoring-tools.mdbrownfield-agent-loop.mdcharacterization-testing.mdcode-smells-guide.mdcross-language-rewrite.mdfeature-flag-retirement.mdlegacy-code-strategies.mdmikado-method.mdmutation-testing.mdoperational-patterns.mdrefactoring-catalog.mdstrangler-fig-migration.mdtech-debt-management.md详细介绍
QA 重构
使用此技能安全地进行重构:保留行为、降低风险,并在提高可维护性和交付速度的同时确保 CI 保持绿色。
默认原则:先建立基线,再迈出最小的安全一步,并通过测试、契约和可观测性来验证,而非凭直觉判断。
快速开始(10 分钟)
- 如果缺少关键信息,请询问:哪些内容绝不能更改(不变量)、风险级别(金钱、身份验证、迁移、并发)、部署限制,以及可通过测试保护的最小边界。
- 确认目标分支为绿色;复现必须保留的行为。
- 选择一个边界:API 表面、模块边界、数据库边界、请求处理程序或代码修改工具的影响范围。
- 加上安全网:在该边界编写特征测试、契约测试或集成测试。
- 通过微小步骤重构:每个提交或 PR 片段只做一项保留行为的更改。
- 验证:将每项要保留的行为映射到验证依据和执行层;在可行时,证明验证依据会在行为出错或被移除时失败,然后在重构后通过。先在本地运行最小的相关测试套件,再运行完整 CI;确保失败可确定地复现,并提供丰富的测试产物。
工作流
- 建立安全网,明确边界和回滚方案。
- 选择重构策略和最小的更改范围。
- 执行更改并验证行为;如果安全保障低于可接受的风险水平,则停止。
- 记录下一个更改范围,不要在本轮中扩大范围。
核心 QA(默认)
安全重构循环(行为优先)
- 在目标分支上建立基线,并复现必须保留的行为。
- 定义不变量:输入/输出、错误模式、权限、数据结构和性能预算。
- 加上安全网:围绕即将改动的边界编写特征测试、契约测试或集成测试。
- 创建接缝:引入注入点和适配器,以隔离副作用和外部依赖。
- 通过微小步骤重构:每次只做一项保留行为的更改;保持差异易于审查。
- 验证:区分编译、类型和静态检查成功,与实际执行行为成功;区分定向回归测试成功,与发布就绪。记录数据、并发、时序或发布敏感路径仍需进行的运行时或类生产环境检查。
- 安全发布:如果重构涉及生产关键路径,则采用金丝雀发布、影子模式、迁移标志或抽象分支模式。
风险级别(选择安全网)
| 风险 | 示例 | 最低要求的安全网 |
|---|---|---|
| 低 | 重命名、提取方法、仅调整格式 | 单元测试 + lint/类型检查 |
| 中 | 在模块间移动逻辑、依赖倒置、影响范围较小的代码修改工具 | 边界处的单元测试 + 集成/契约测试 |
| 高 | 身份验证/权限路径、并发、迁移、金钱/数据丢失路径、大规模自动重写 | 集成 + 契约测试、可观测性检查、发布 + 回滚计划 |
重构测试策略
- 优先在边界处使用契约测试和集成测试来保留行为。
- 只有在输出稳定且经过审查时才使用快照/黄金主测试(避免反复“全部批准”)。
- 对于不变量,可考虑基于属性的测试或表驱动用例(输入、边缘情况、错误模式)。
- 避免将端到端/UI 测试作为重构的主要安全网;大部分安全保障应放在 UI 以下。
- 对于不稳定的区域:先确保结果可确定(种子、时间、顺序、网络),再相信测试结果。
- 对于 API 和服务边界,优先使用显式契约测试,而非临时编写的端到端覆盖。
- 对于自动重写,先手动抽查差异,再扩展到整个代码库。
CI 成本与调试体验
- 保持重构 PR 小而易于评审;避免在同一个 PR 中同时进行重构和功能开发。
- 要求为守护重构的测试保留失败产物(日志、跟踪 ID、确定性种子、复现步骤)。
- 减少差异噪声:将仅格式调整的变更隔离开(或在取得共识后一次性对整个仓库应用格式调整)。
- 保持
git bisect可用:除非必要,否则避免混合“机械变更 + 语义变更”。 - 对于 codemod,先进行试运行,再批量执行,并设置失败即停止的路径。
应做 / 避免
应做:
- 在重构高风险区域前补上缺失的测试。
- 添加防护措施(linter、类型检查、契约检查、静态分析 / 安全检查),避免重构悄无声息地破坏接口。
- 需要安全替换实现时,优先采用“通过抽象实现分支”/适配器。
- 涉及多个文件时,优先使用了解编译器和 AST 的重构方式,而不是正则表达式编辑。
- 将“手动修改超过约 500 行”视为第二个行数触发条件,与 references/automated-refactoring-tools.md 中的文件数表针对文件数给出的自动化与手动操作决策相同——重构可能超过一个阈值而未超过另一个(一个 2,000 行的文件仍然只是一个文件;对 600 个文件进行机械重命名,每个文件可能只改几行),所以要同时检查两个维度,而不只是文件数。
避免:
- 将大规模结构性重构与行为变更合并。
- 将不稳定的 E2E 测试作为重构的主要安全保障。
- 将任意规模阈值视为铁律;仅将上述文件数和行数阈值作为评审启发式,用来提醒“这是否应该用 codemod”,而不是作为硬性门槛——真正要判断的是,在这种规模下自动化是否比手动编辑更安全,而不是是否符合某个数字。
行数触发条件改编自 addyosmani/agent-skills(MIT),提交 7676817,日期为 2026-08-09。
专家判断:清单遗漏了什么
何时不应重构
重构是一项投资决策,而非道德义务——应像评估投资一样权衡它。
- 开始前,先衡量重构的收益(开发速度、减少缺陷、解除某项具体功能的阻碍)与成本(时间、评审负担、回归风险)。“这段代码很难看”本身并不是商业理由。
- 不要重构计划退役或替换、且稳定而少有改动的代码——清理即将删除的代码是浪费。投入前先查看迁移路线图。
- 根据热点(变更频率 × 复杂度)确定优先级,而非依据原始 LOC 或审美上的不适。没人碰过的杂乱函数,优先级低于每个迭代都会变更的普通函数。
- 采用 Kent Beck 在 Tidy First?(2023)中的思路:只有当整理能让你即将进行的变更物有所值时,才先整理——它能缩小差异、降低风险,或揭示工作的真实范围。如果整理无法解锁你实际需要的变更,就将其推迟到单独的低风险提交中进行评审,而不是顺手把它合并进来。
- 如果当前路线图周期内将通过绞杀者模式迁移替换此模块,就把投入放到迁移所需的接缝和特征化测试上,而不是深入重构即将淘汰的代码内部。
先编写特征测试的纪律
- “我理解这段代码”不是安全保障——要写测试,而不只是相信自己的判断。如果不运行代码就无法说出某个函数对最棘手的三个输入会返回什么,那你对它的理解还不足以跳过特征测试。
- 特征测试要捕捉实际行为,包括缺陷。进行特征测试时不要悄悄修复缺陷——应单独记录发现的缺陷,由产品负责人决定修复的时机,以及相对于重构的先后顺序。
- Chesterton's Fence 原则尤其适用于删除代码:删除看似未使用或有误的代码之前,先用
git blame/git log -p查看相关行,找到引入它们的提交及关联的工单/PR,并检查是否有注释说明原因。只有在能说清楚为何设下这道围栏后,才能删除,而不能仅仅因为目前看不出原因就删掉。
保持行为不变的验证策略
- 分层验证,先做成本最低的检查:类型检查 → lint → 单元测试/特征测试 → 契约测试/集成测试 → 对涉及边界设置变异测试得分门槛 → 对生产关键路径进行金丝雀测试/影子测试。一旦较低成本的某一层能够发现同类回归,就不要继续扩大验证范围。
- 对于不打算改变行为的重构,评审要问的不是“代码写得好吗”,而是“我能否证明可观察到的行为没有变化”。黄金样本测试、属性测试和契约测试可以提供证明;仅靠代码评审只能确认变更看起来安全。
- 明确测试并发顺序、浮点舍入,以及其他系统会解析的错误消息文本——这些是看似干净的重构中最常见的隐性行为变化来源。
截止期限压力下的重构(分诊)
- 时间紧迫时,应缩小范围,而不是放弃安全保障。围绕涉及边界编写一个 30 分钟内可完成的特征测试,比它能避免的事故成本低得多。
- 如果确实没有时间编写测试,就只做最机械的变更(重命名、提取代码但不改变逻辑),并把任何会改变控制流或数据形态的工作推迟到后续工单。不要把“快”和“有风险”捆在一起。
- 在同一个 PR 中将这项权宜之计记入技术债务登记表——没有记录下来的截止期限债务,之后就不会得到偿还。
- 遇到高风险路径(资金、身份验证、数据、迁移)时,如果截止期限迫使你跳过安全保障,应主动上报,而不是默默承担:明确告知评审者或产品负责人,不要悄悄发布。
绞杀榕模式与大爆炸式重写
- 只要系统已有活跃用户或流量,且无法接受数月暂停功能交付,就默认采用绞杀榕模式——考虑到数月后一次性切换的风险,渐进式路径的成本通常比重写所获得的认可要低得多。
- 只有在以下情况下,大爆炸式重写才有充分理由:尚无实际流量(真正的全新替换项目);领域逻辑足够简单,一个团队能够完全掌握;或者遗留系统已严重损坏(运行时不受支持、许可证过期、安全漏洞无法修补),以至于无法继续部分运行。
- 警惕“我们已经迁移了 80%,一次性完成吧”的陷阱。剩余部分往往集中着不成比例的未文档化边缘情况;最后一部分也要坚持与第一部分相同的逐功能规范(一次处理一个接缝,扩大推广范围前先验证对等性指标)。
- 如果确实决定重写,仍然要分步进行:先交付最小的端到端垂直切片,并在构建其余部分之前,将真实流量(即使价值较低)导入其中,以获取交付反馈,同时避免大爆炸式重写的全部风险。
- 如果重写涉及更换语言或框架(例如,从 PHP 换成 Next.js、从 COBOL 换成 Java、从 AngularJS 换成 React),就无法在两个技术栈上运行进程内特征测试。请参阅 references/cross-language-rewrite.md:黑盒对等性判定器是准入门槛;切片开始前,它必须在遗留系统上通过、在尚未迁移的新技术栈上失败,并由另一位编写者独立编写。
LLM Agent 与“重构”期间细微的行为变化
以“重构一下”为目标的提示词会以一种特定方式让 Agent 出错:Agent 发现局部改进机会后便会着手处理,悄悄将范围从“行为不变,结构更好”扩大为“行为不变,结构更好,顺便修复几个我注意到的问题”。请留意并防范以下失误模式:
- 错误处理漂移 — 收窄或放宽
except/catch,将静默失败改为抛出异常(或反过来),或更改错误路径上的默认值。这类问题在审查中几乎看不出来,因为“重构后”的代码看起来更整洁。 - 边界漂移 — 在合并近似重复的条件判断时,
>变成>=(或反过来)。 - 舍入/精度漂移 — 用库函数替换手动累加循环,但两者在边缘情况下的浮点行为不同;对金额和科学计算代码而言风险很高。
- 顺序/并发漂移 — 调换看似互不相关的语句顺序,或缩小锁的作用范围,可能改变可观察到的顺序,或引入只在高负载下出现的竞态。
- 测试修补反模式 — 当特征测试或现有测试在 Agent 修改后失败时,Agent 往往会默认放宽断言或删除失败用例,让测试套件转绿。对于“纯重构”PR 中的任何测试文件变更,都应视为需要人工明确说明理由的警示信号,而非例行整理。
- 多文件重写中的范围蔓延 — SWE-Bench ProMax(arXiv 2608.09802)报告称,在其 170 个多语言重构实例中,最佳模型的解决率为 41.2%。应将其视为基准测试结果,而非对本仓库的预测。应限制影响范围,并抽样审查实际差异,再决定是否信任多文件 Agent 操作。
如果要重写为另一种语言,还应加载 references/cross-language-rewrite.md(语言对的语义风险、副作用清单、大规模转换):上面的漂移清单仅适用于同一种语言。
合并 Agent 编写的“重构”前,请设置以下关卡:
- 先检查测试文件的差异,再查看源代码差异——任何被削弱、删除或新近跳过的断言,在人工解释原因之前都不能接受。
- 重新运行预先存在的(而非由 Agent 编写的)特征测试/契约测试套件;Agent 编写的测试套件全部通过,本身并不能证明行为得到了保留。
- 如果安全网也是由 Agent 生成的,请先对已修改的边界进行变异测试,再信任这些测试——参见 references/mutation-testing.md。代码行覆盖率恰恰是 Agent 学会钻空子的指标。
- 亲自抽样审查实际差异,检查上述漂移模式;不要用编写该差异的同一个 Agent 提供的差异摘要代替阅读差异本身。
- 绝不允许 Agent 在任务中途扩大范围(“我顺手还……”);应将这类变更拆分为独立的 PR 并单独审查。
快速参考
| 任务 | 工具/模式 | 命令/方法 | 适用场景 |
|---|---|---|---|
| 过长或涉及多种关注点的函数 | 提取方法 | 拆分为更小的函数 | 单个函数混合了验证、编排和副作用 |
| 过大或内聚性低的类/模块 | 拆分类 / 提取模块 | 创建职责更聚焦、范围更明确的单元 | 一个类型负责无关的工作流,或依赖项过多 |
| 重复代码 | 提取函数/类 | DRY 原则 | 多处存在相同逻辑 |
| 复杂条件判断 | 用多态替换条件判断 | 使用继承/策略模式 | 根据类型执行 switch 语句 |
| 参数列表过长 | 引入参数对象 | 创建 DTO/配置对象 | 函数的参数超过 3 个 |
| 现代化遗留代码 | 特征测试 + 绞杀者模式 | 先编写测试,再逐步迁移 | 没有测试、代码库陈旧 |
| 大规模机械式重写 | Codemod / AST 转换 | 先试运行、抽样审查差异,再分批推出 | 在许多文件中重命名、迁移 API、重复编辑 |
| Java 框架迁移(Spring Boot、Java 版本) | 通过 Moderne CLI 或 MCP 使用 OpenRewrite recipe | 从已安装的目录中选择完全限定的 recipe 并试运行 | Recipe 名称会随迁移目标变化 |
| 自动化质量门禁 | 编译器 + 代码检查工具 + 合约检查 | 使用快速失败检查和构建产物的 CI 流水线 | 防止重构过程中出现静默回归 |
| 技术债跟踪 | 债务登记表 + 静态分析 | 跟踪趋势、问题热点和负责人 | 确定重构工作的优先级 |
导航
- 基准流程见
## Workflow和## Core QA (Default) - 更深入的材料见
## Operational Deep Dives、## Templates和## Resources - 测试、架构和代码审查交接见
## Related Skills
相关技能
| 技能 | 用途 |
|---|---|
| qa-debugging | 调试生产问题和测试偶发故障 |
| software-code-review | 代码审查流程和检查清单 |
| software-architecture-design | 架构设计和重新设计决策 |
| qa-testing-strategy | 测试策略和覆盖率规划 |
| data-sql-optimization | 性能调优、SQL 和查询计划 |
操作深入指南
共享基础
- ../software-clean-code-standard/references/clean-code-standard.md - 用于引用的规范化整洁代码规则(
CC-*) - 遗留操作手册:../software-clean-code-standard/references/code-quality-operational-playbook.md - 操作流程(遗留的
RULE-*集已废弃,并纳入CC-*映射表;审查和重构决策树现归于 software-code-review 和本技能) - ../software-clean-code-standard/references/refactoring-operational-checklist.md - 重构异味到操作的映射、安全重构防护措施
- ../software-clean-code-standard/references/working-effectively-with-legacy-code-operational-checklist.md - 接缝、特征测试、增量迁移模式
技能专属
详细的重构目录、codemod 推出模式、质量门禁、技术债操作手册和遗留代码现代化步骤,请参阅 references/operational-patterns.md。
模板
使用 assets/ 中的复制粘贴模板来创建检查清单和质量门禁配置:
- 重构:assets/process/refactoring-checklist.md;如需通用代码质量审查,请参阅 software-code-review(assets/process/code-review-quality.md 现在是指向该文档的链接)
- 技术债务:assets/tracking/tech-debt-register.md
- 质量门禁:assets/quality-gates/javascript/eslint-config.js、assets/quality-gates/platform-agnostic/sonarqube-setup.md
资源
使用 references/ 中的深度指南(只加载所需内容):
- 操作模式:references/operational-patterns.md - 先复用、后编写的阶梯(目录/质量门禁/遗留系统现代化相关内容现已移至下列文件)
- 重构目录:references/refactoring-catalog.md - 存在隐性行为变更风险的重构(并非 Fowler 的完整复述——相关内容请参阅 refactoring.com/catalog)
- 代码异味指南:references/code-smells-guide.md - 基于名称的异味索引、检测工具和死代码清理分级
- 技术债务管理:references/tech-debt-management.md
- 遗留代码现代化:references/legacy-code-strategies.md
- 棕地 Agent 循环:references/brownfield-agent-loop.md - 让编码 Agent 处理遗留代码库:绘制代码库图谱、选择接缝、构建不可变的验收门禁、限定单项任务并运行循环
- 特征化测试:references/characterization-testing.md - 黄金主测试和审批测试模式
- 绞杀者无花果迁移:references/strangler-fig-migration.md - 渐进式遗留系统迁移策略(包含扩展-收缩/并行变更说明)
- 跨语言重写:references/cross-language-rewrite.md - 目标语言或框架与源语言或框架不同时加载:一致性判定基准、语言对特有风险、副作用、身份验证/SEO/安全性一致性、数据检查
- 自动化重构工具:references/automated-refactoring-tools.md - Codemod、AST 转换、配方测试和 IDE 重构
- Mikado 方法:references/mikado-method.md - 用于相互纠缠的遗留代码变更的前置条件树;从叶节点开始执行
- 变异测试:references/mutation-testing.md - Stryker/mutmut/cosmic-ray/PIT;变异分数、CI 阈值、增量运行
- 功能标记退役:references/feature-flag-retirement.md - 分步流程:查找引用、移除无效分支,最后删除定义
可选:AI / 自动化
应:
- 仅在能通过测试和契约证明行为保持不变时,使用 AI 提出机械式重构(重命名/提取/移动)。
- 使用 AI 总结差异和风险热点;通过运行有针对性的特征测试进行验证。
- 在可用时,优先使用工具辅助的重构(IDE/编译器感知工具、codemod),而非自由形式的文本编辑。
- 将 Agent 生成的重构视为草稿补丁,直到人工审查了有代表性的差异且安全网验证通过。
- 对于 Java 项目,优先使用 OpenRewrite recipes(通过
modCLI 或 MCP 服务),而非手写 codemod——recipes 是确定性的,且能感知版本;AI Agent 可以直接将其作为工具调用。 - 在将 AI 生成的特征测试视为重构安全网之前,使用变异测试验证;仅有行覆盖率并不能证明测试质量。参见 references/mutation-testing.md。
- 在让 Agent 接手遗留仓库之前,先构建验收门槛,并且绝不允许 Agent 修改它;绘制仓库图谱来选择接缝,而不是让 Agent 选择它最熟悉的部分。参见 references/brownfield-agent-loop.md。
- 对于大型多文件 Agent 重构,在执行前界定影响范围;参见上文“LLM Agents and Subtle Behavior Changes During 'Refactors'”下的范围蔓延条目。
避免:
- 接受未经明确要求且没有回归测试的行为变更型重构。
- 让 AI 通过弱化断言来“修复测试”,以使 CI 变绿。
- 未经试运行和样例审查,就在整个仓库范围内推行 AI 生成的多文件编辑。
- 让 Agent 在重构过程中自主扩大范围;在 Agent 开始前明确边界和影响范围。
精选外部参考资料见 data/sources.json。
经验循环
如果以往的决策或陷阱与当前任务相关,请在 learnings.consolidated.md 存在时查阅;仅在需要历史记录或它是可用的备用文件时使用 learnings.md。否则,跳过这两个文件。
应用后,如果遇到了值得记住的模式、值得避免的错误,或令你意外的领域事实,请通过 agents-skills-feedback-loop/scripts/append_learning.py 在 learnings.md 中追加一条带日期的项目。不要修改 SKILL.md 本身。