mcpField
R
remove-technical-debt

remove-technical-debt

github.com/wondelai开发2.5k2.3k 次安装7 周前更新
代码仓库

关于 remove-technical-debt

remove-technical-debt 是面向大型老旧代码库维护者的八阶段渐进式治理技能,目标是在旧系统持续交付的同时逐步偿还技术债。它先为改动路径建立测试安全网,再依序推进重构、架构边界和可靠性工作,并记录进度以便续做。

主要功能

  1. 01八阶段渐进式治理流程
  2. 02先为改动路径建立测试安全网
  3. 03以小步重构保持行为可验证
  4. 04记录技术债与架构决策
  5. 05支持暂停后从项目文档续做

适用场景

  1. 01在不重写系统的前提下偿还技术债
  2. 02为高变更区域补充特征测试
  3. 03逐步梳理单体的架构边界
  4. 04在持续交付中改善生产可靠性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wondelai/skills --skill remove-technical-debt

安装后在 Claude Code 中输入/remove-technical-debt确认已加载。

附带文件references/artifact-templates.md

详细介绍

清除技术债务

面对一个规模庞大、年代久远、盘根错节、人人都不敢触碰的代码库,在原地逐步偿还技术债务——不进行大爆炸式重写,让旧系统始终保持交付。重写的冲动,是把一个艰难但仍在交付的产品变成艰难且无法交付的可靠途径;这段旅程提供了另一种选择。整个过程分为八个阶段,可交互进行,也可随时继续:Agent 会在每次决策前征求你的意见,并将结果记录在项目的 docs/ 文件夹中,因此你可以在任何阶段结束后停下,之后再接着进行。第一阶段会在你要修改的代码点建立安全网;有了这张安全网,后续每个阶段的成果都可以验证。

核心原则

反馈胜于恐惧:先覆盖再修改,绝不修改后祈祷——沿着你实际走过的路径原地逐步偿还债务,并始终保持交付。 此技能负责安排各阶段的顺序、提出决策问题,并将每个选择记录在 docs/ 中。各项组成技能负责提供具体方法——调用它们,而不是临时编造框架。偿还大量技术债务,不是通过重建整座山来实现;而是沿着你已有的修改路径,一次迈出一个安全且经过测试的步骤。跳过阶段——在建立安全网之前清理代码,或在边界明确之前拆分服务——正是这一排序旨在避免的失败模式。

旅程地图

阶段技能要回答的问题产物
1working-with-legacy-code我能在不知情的情况下不破坏代码地修改它吗?应该从哪里开始?创建 docs/TESTING.md + docs/TECH-DEBT.md — GATE
2refactoring-patterns我能在不改变行为的情况下调整结构吗?扩展 docs/TECH-DEBT.md
3clean-code我修改的内容对下一位读者和 Agent 来说清晰易懂吗?扩展 docs/TECH-DEBT.md
4software-design-philosophy复杂性是否隐藏在深层模块之后?扩展 docs/TECH-DEBT.md
5clean-architecture业务规则依赖框架,还是框架依赖业务规则?扩展 docs/ARCHITECTURE.md
6pragmatic-programmer哪些习惯能阻止技术债务再次累积?扩展 docs/TECH-DEBT.md
7release-it它能否经受恶劣的生产环境考验?扩展 docs/RELIABILITY.md
8domain-driven-design如何将单体拆分为有界上下文?扩展 docs/ARCHITECTURE.md

操作规则

  1. 先恢复进度。 在做任何其他事情之前,阅读 docs/REMOVE-TECHNICAL-DEBT-PLAN.md 和 Journey Map 中的每一份工件。如果跟踪文件存在,用 3-5 行总结 Journey 状态,并询问要进入哪个阶段。完成标准:用户已确认入口阶段。存在跟踪文件的 Journey 应恢复,不得重新开始。
  2. 仅首次运行时进行接收。 如果没有跟踪文件:运行下方的接收流程,然后创建 docs/REMOVE-TECHNICAL-DEBT-PLAN.md,将每个阶段的状态设为 pending | in-progress | awaiting-evidence | done | deferred: reason | skipped: reason。完成标准:跟踪文件已存在,且用户已确认阶段计划。
  3. 进入阶段。 说明该阶段的作用、需要作出的决定、产出的工件和大致工作量。提供继续 / 跳过 / 延后选项——标记为 GATE 的阶段可以延后,但不得跳过。用户选择继续后,将阶段标记为 in-progress。完成标准:用户已作出选择。
  4. 调用技能与备用方案。 加载该阶段的技能并使用它:每个阶段的 Invoke 行都会按 slug 指定技能——使用该技能执行阶段。如果无法使用该技能,提供:npx skills add wondelai/skills/<slug> --global。如果用户拒绝,则根据 Brief 执行阶段——采用最低可行方法。说明当前使用哪种模式。
  5. 阶段内决策。 提出 "Decide with the user" 下的每个问题,并给出具体选项和你的建议。在跟踪文件的 Key Decisions 中记录选择。未明确询问而自行作出的决定属于缺陷。
  6. 阶段退出。 写入之前,先展示工件草稿内容供用户确认。获得批准后:编写或扩展 docs/ 文件,并更新跟踪文件(状态、Key Decisions、Next Actions)。完成标准:文件已写入,且阶段行显示 done。
  7. 工件规范。 写入前先阅读;仅在文件缺失时创建,否则应扩展文件——添加或更新自己的部分,同时保留其他人的内容。docs/ 中的文件名必须为大写。首次创建 docs/ 文件时,请参阅 references/artifact-templates.md——根据完整骨架创建文件(包含所有章节标题),再填写阶段指定的部分。每条建议都必须以复选框或表格行记录,并注明负责人和优先级。
  8. 阶段 1 是 GATE,提交必须保持单一目的,发现的 Bug 只标记,不修复。 安全网映射中未列出的代码不得进行任何转换——先将其标记出来(未列出即表示未出现在 Pinned behaviors 下;Gaps 列中的条目也不得触碰)。结构变更与行为变更不得出现在同一个提交中:先在测试通过的情况下进行重构,并单独提交纯结构变更;然后在另一个提交中单独更改行为;重构过程中测试失败时应还原,而非调试;安全网测试新增和 docs/ 更新也必须各自作为单一目的的提交。特征分析过程中发现的 Bug 必须在 Debt Ledger 中标记并登记为待办,绝不能悄悄修复——调用方可能依赖这些特殊行为。

前期了解

创建跟踪文档前,先询问以下问题:

  1. 系统的功能是什么、规模和历史有多长,以及系统出故障时最糟糕的后果是什么?(评估风险并确定阶段优先级)
  2. 你接下来要修改哪个文件?哪些文件在 git log 中最常出现,或属于核心领域?(选择 Phase 1 的起始模块——三轴启发式)
  3. 是否存在测试套件?目前它在 CI 中能否通过?还是因为不稳定而被禁用?(确定 Phase 1 安全网的范围)
  4. 技术栈是什么——框架、ORM、数据库?业务逻辑是否纠缠在控制器或 ORM 模型中?(决定是否进行 Phase 5 边界工作和 Phase 8 上下文映射)
  5. 系统是否已在生产环境中运行并拥有真实用户?它会调用哪些出站依赖——第三方 API、支付、邮件、队列?(决定是否进行 Phase 7 集成点审查)
  6. 是否有人提议进行大爆炸式重写?(指出这段历程要用渐进式路径取代的决策)
  7. 你现在希望推进这段历程的哪些部分?(Phase 1-3 能最快改变团队与代码的关系;当边界和分解成为瓶颈时,再推进 Phase 5-8)

阶段跳过启发式:如果代码库足够小,单个模型仍能轻松涵盖全局,则跳过 Phase 8。只有当偿还技术债务暴露出真实的扩展性或数据层限制时,才添加可选的 system-design 或 ddia-systems——从需求出发,而不是从解决方案出发。一旦存在真实用户,Phase 7 就不能跳过——超时和断路器是基本要求(状态可以保持为 deferred: reason,但绝不能是 skipped)。绝不跳过 Phase 1;它是关卡。然后从模板创建跟踪文档,并确认计划。

完成条件:docs/REMOVE-TECHNICAL-DEBT-PLAN.md 已创建,所有阶段均已标注状态,且用户已确认计划。

阶段

Phase 1 — 构建安全网并找到起点(working-with-legacy-code)— 关卡

目的: 锁定变更点的当前行为并绘制影响范围图,以便验证之后的每个阶段。安全网地图中未列出的代码不得在任何阶段中触碰。

简要说明(备用): 没有测试的代码就是遗留代码——先覆盖测试再修改,绝不要改完后祈祷。运行遗留代码变更算法:找出变更点,找到测试点,以侵入性最小的接缝打破依赖(为构造函数参数化并提供生产默认值;提取接口),编写锁定实际行为的特征测试(断言一个错误值,查看失败信息,再锁定真实值),然后再修改。通过影响草图限定影响范围;找出能用少量测试覆盖最多行为的瓶颈点。如果时间紧迫,无法为紧急变更补充覆盖:使用 Sprout/Wrap,并将未测试的宿主代码记为技术债务。

调用: 对前期了解时选定的起始模块使用 working-with-legacy-code 技能。要求提供入口方法的影响草图、瓶颈点、要打破依赖的接缝,以及能够锁定当前行为的最小特征测试集。

与用户共同决定: (1) 使用三轴启发式确认起始模块——接下来要修改的模块、高频变更模块(git log)、核心领域模块。(2) 在特征化过程中发现的 Bug:锁定错误行为并记录到债务台账中,绝不悄悄修复——调用方可能依赖这个怪异行为。确认用户接受这一点。

交付物: 创建 docs/TESTING.md,其中包含 ## Test Strategy、## Safety Net Map(模块 | 已锁定的行为 | 测试文件 | 缺口)和 ## Characterization Backlog;创建 docs/TECH-DEBT.md,其中包含 ## Debt Ledger(条目 | 位置 | 类型 | 风险 | 工作量 | 优先级 | 状态)和 ## Sprout / Wrap Register,登记所有采用 Sprout 或 Wrap 方式处理的代码。在 ## Test Strategy 下记录影响草图中的瓶颈点。更新跟踪文档。

完成条件: 目标模块的行为已锁定,测试套件通过,两个文件均已创建,且跟踪文档显示 Phase 1 已完成——只有满足这些条件后,才能解锁 Phase 2。

阶段 2 — 使用命名重构重新组织代码(refactoring-patterns)

目的: 将“整理一下”转化为有明确名称、保持行为不变的转换,并一次只应用一个小步骤。

简要说明(备用): 重构不是重写:重构由一系列保持行为不变的小型转换组成,每一步都有测试作为保障。每种代码异味都对应一种命名重构——提取方法是最常用的手段(如果你想写注释来解释某个代码块,就把它提取出来,并用注释的内容为它命名)。其他方法还包括用卫语句替换嵌套条件、用多态替换条件语句、引入参数对象、提取类。工作流:测试通过,执行一次转换,测试通过,提交;测试失败就回退,而不是调试。生产环境的大型结构迁移使用抽象分支模式;预备性重构先让后续变更更容易;三次法则用于避免过早抽象。

调用: 对有代码异味的模块和阶段 1 的测试使用 refactoring-patterns 技能。要求它指出每种代码异味,说明所用转换,并在每次转换后运行测试,一次只应用一种转换;如果是大型迁移,则要求提供抽象分支模式的计划。

与用户共同决策: 确定范围——本轮处理哪些代码异味;判断即将开发的功能是否需要先在插入点进行预备性重构;以及大型迁移是否应通过抽象分支模式进行。

交付物: 扩展 docs/TECH-DEBT.md 中的 ## Smell Inventory,使用以下字段:代码异味 | 位置 | 重构 | 状态。每种代码异味占一行,记录所应用的命名重构及其状态。更新跟踪表。

完成标准: 目标代码异味均已应用命名重构,且状态为 done / ticketed;测试通过;每项结构变更都已通过仅包含结构调整的提交提交。

阶段 3 — 提升所改代码的可读性(clean-code)

目的: 优化读者体验——命名、小型单一用途函数、安全的错误处理——仅限于你正在修改的代码区域。

简要说明(备用): 代码的阅读频率远高于编写频率(至少 10:1)。名称应体现意图(elapsedTimeInDays,而不是 d);布尔值应读起来像谓词;同一概念只用一个词表示;函数只做一件事,并且只处于一个抽象层级,参数为 0–2 个(带标志参数的函数相当于两个函数)。命令查询分离:要么改变状态,要么返回值,不能两者兼做。旧代码中的问题常藏在错误处理中:优先使用异常而不是返回码;捕获具体类型;绝不返回或传递 null(使用空集合、Optional、Null Object);将嘈杂的第三方 API 封装在适配器后;每条错误信息都包含操作和状态。童子军规则:离开代码时,让它比你发现时更整洁。

调用: 对目标模块使用 clean-code 技能。要求它针对六项准则给出 0–10 分的评分、按优先级排列的前十项修复建议,以及错误处理审计(裸捕获、返回 null、缺少上下文的错误、未封装的第三方 SDK)。

与用户共同决策: 确定哪些修复现在应用,哪些作为技术债务记录;并确定团队今后采用的命名和错误处理约定。

交付物: 扩展 docs/TECH-DEBT.md:针对每种命名 / 函数 / 错误代码异味,在 ## Smell Inventory 中添加条目;并将达成一致的规则记录在 ## Adopted Conventions 下。更新跟踪表。

完成标准: 模块评分达到 8 分以上;或者所有低于 8 分的项目都已在代码异味清单中列出并附有修复方案;已记录约定;且阶段 1 的测试仍然通过。

阶段 4 — 通过深层模块降低复杂度(software-design-philosophy)

目的: 直面复杂性本身——用简单的接口隐藏真正的底层机制,而不是创建缺乏监督的 Agent 所造成的类膨胀。

简述(备用): 复杂性是敌人;评估每项变更时,都要看它是增加还是降低整体复杂性。症状包括:变更放大、认知负担、未知的未知。模块深度 = 功能 ÷ 接口复杂度;深层模块用小型接口隐藏底层机制,浅层模块则不然(类膨胀)——将总是一起出现的浅层类合并。留意信息泄漏(一个决策在多个模块中反复体现)和时间分解(按执行顺序而非知识组织模块)。这是从战术转向战略:投入 10-20% 的精力保持设计整洁。

调用: 使用 software-design-philosophy skill,检查迄今为止涉及的模块集合。询问哪些类是浅层类、哪些设计决策在模块间泄漏,以及如何通过合并来构建更深层、接口更简单的模块——并为每项变更标注其是增加还是降低复杂性。

与用户共同决定: 哪些合并现在进行,哪些推迟,同时避免过度合并真正无关的事项。

产出: 在 docs/TECH-DEBT.md 的 ## Smell Inventory 中补充类膨胀、浅层模块、信息泄漏条目,以及已实施的合并。更新跟踪器。

完成条件: 每个浅层模块簇都已合并或记录了修复方案;接口数量没有为了追求“模块化”而增加;测试全部通过。

阶段 5 — 划定依赖边界(clean-architecture)

目的: 让框架和数据库逐个模块依赖业务规则,而不是反过来。

简述(备用): 依赖规则:源代码依赖指向内层——实体、用例、接口适配器、框架/驱动;任何内层都不能引用外层。数据库和 Web 都只是细节,是可插拔到规则中的组件。通过依赖倒置来落实:用例拥有仓储接口;Postgres/Stripe 实现位于外层适配器中。SOLID 是中层工具;共同闭包原则和无环依赖用于寻找真正的边界。共享同一数据模型的微服务就是分布式单体——先在单体内部应用这条规则。

调用: 使用 clean-architecture skill,结合当前模块图和需求梳理阶段确定的技术栈。要求它绘制依赖图,列出每一处业务逻辑导入 ORM 或框架的违规项,优先处理变更最频繁的模块,并展示如何提取出不依赖框架、通过自有接口提供的用例。

与用户共同决定: 本轮要推进边界到什么程度;优先封装哪些供应商(支付、存储);以及任何拟议的服务拆分究竟是否构成真实边界,还是只会增加一个分布式单体。

产出: 扩展 docs/ARCHITECTURE.md:在 ## Layer Map & Dependency Rule 下记录各层和违规项(violation | location | fix | status),并在 ## Decision Log 下记录边界选择。更新跟踪器。

完成条件: 每项依赖规则违规都已记录并附有修复方案;至少封装了风险最高的供应商;业务规则测试可在不加载框架的情况下运行;测试全部通过。

阶段 6 — 固化习惯(pragmatic-programmer)

目的: 制定元原则,防止这段旅程结束后,代码库悄然再次积累技术债。

简介(备用方案): 破窗理论:一个未修复的临时变通会降低后续修复的标准——立即修复,或用已跟踪的工单将其封存,绝不留下未跟踪的 // TODO。DRY 关乎知识,而不是文本——在两处重复同一规则时(例如客户端和服务端的验证),应消除重复;偶然相似的内容则不必处理。正交性:更改一个组件不应影响另一个组件。可逆性:通过自有接口封装供应商。契约式设计 + 尽早崩溃:在经过加固的边界处检查前置条件和不变量,让无效状态在源头明确失败。

调用: 在整个代码库中使用 pragmatic-programmer 技能。请它标记重复的知识(忽略偶然重复)、需要封存的破窗和未跟踪的 TODO,以及需要添加契约式设计保护子句的边界。

与用户共同决策: 每次迭代的技术债预算和破窗政策——哪些问题立即修复,哪些记录为工单。

产出: 扩展 docs/TECH-DEBT.md:在 ## Debt Ledger 中记录重复知识和破窗事项,并在 ## Debt Budget & Broken-Windows Policy 和 ## Adopted Conventions 中记录商定的政策。更新跟踪器。

完成条件: 重复知识问题均已登记或修复,不再有未跟踪的临时变通,并且已记录技术债预算政策。

阶段 7 — 加固集成点(release-it)

目的: 让每个集成点都能平稳降级,避免缓慢或故障的依赖拖垮整个系统。

简介(备用方案): 通过 QA 的软件,不一定能在生产环境中存活。集成点是导致故障的头号因素——缓慢响应比没有响应更糟。不可妥协的要求:每个出站调用都必须设置连接和读取超时;对故障依赖使用熔断器(触发后打开、快速失败、半开恢复);使用隔舱隔离资源池;采用带指数退避和抖动的重试;通过稳态清理清除不断累积的杂物。限制每个查询——无界结果集会在规模扩大时导致崩溃,因此要添加 LIMIT 和分页。通过功能标志和扩展-收缩式迁移,将部署与发布解耦;添加深度健康检查、RED 指标和基于症状的告警。

调用: 使用 release-it 技能检查收集到的出站依赖。请它审计没有设置超时的调用、无界查询和列表端点、熔断器与隔舱的部署位置、针对高风险架构的扩展-收缩式迁移计划,以及深度健康检查 + RED 指标 + 告警设计。

与用户共同决策: 熔断器阈值、哪些依赖需要专用资源池,以及告警症状和阈值(错误率、延迟)。

产出: 扩展 docs/RELIABILITY.md,添加 ## Integration-Point Audit(dependency | timeout | circuit breaker | bulkhead | retry policy | status)、## Query & Resource Findings、## Health Checks & Metrics 和 ## Deploy vs Release。更新跟踪器。

完成条件: 每个出站调用都已设置超时,关键依赖都配有熔断器和隔舱,无界查询均已限制,深度健康检查 + RED 指标 + 基于症状的告警均已就绪,并且审计中关键路径没有未解决项。

阶段 8 — 划分有界上下文(领域驱动设计)

目的: 将一团乱麻拆解为团队能够负责并最终独立提取的上下文,而无需重写。

简要说明(备用): 模型就是代码。从通用语言入手:将仅供技术使用的名称(DataManager、Helper)改为领域术语;难以命名的概念意味着模型可能有误。基于现有系统绘制有界上下文(同一个词在不同上下文中可能有不同含义),并与团队边界对齐。防腐层让干净的新上下文能够与遗留核心交互,同时避免旧模型渗入其中——这是绞杀者模式的基础。在上下文内部:使用只有一个根的小型聚合,通过 ID 引用其他聚合,不可变的值对象,以及过去时的领域事件。将最多精力投入核心域。

调用: 使用 domain-driven-design 技能处理纠缠不清的模块,以及阶段 5 划定的边界。请它构建通用语言,绘制当前和目标有界上下文,为新的干净上下文设计防腐层,并将任何上帝聚合缩减到其真正的一致性边界。

与用户共同决定: 首先划分哪个上下文(价值所在的核心域);哪些边界与团队结构对齐;以及跨上下文调用是否改为领域事件。

产物: 扩展 docs/ARCHITECTURE.md:在 ## Bounded Contexts & Context Map 下记录上下文映射,在 ## Domain Glossary (Ubiquitous Language) 下记录术语,并在 ## Decision Log 下记录拆分决策。更新跟踪器。

完成标准: 绘制出当前上下文映射,定义第一个目标上下文及其防腐层,将关键领域术语加入术语表,并确保任何调整后的聚合都能通过测试。

可选阶段

技能添加时机产物
system-design还债过程中发现需要重新架构才能解决的实际扩展限制扩展 docs/ARCHITECTURE.md(## System Context)
ddia-systems数据层决策(隔离、复制、存储适配性)属于技术债的一部分扩展 docs/ARCHITECTURE.md(## Data & Storage Decisions)
team-topologies技术债集中在团队边界与架构冲突之处扩展 docs/OPERATIONS.md(## Team Structure)

可选阶段遵循相同的执行规则——像核心阶段一样加载并使用每项列出的技能;在对应的添加条件首次成立时插入阶段:扩展和数据阶段放在阶段 5 之后,团队拓扑阶段与阶段 8 的上下文边界工作并行。

常见错误

错误修正方法
提议大爆炸式重写承诺就地逐步偿还技术债;每个阶段都会让系统变得更好,并继续交付,最坏情况下也能快速回滚。先展示一个小而快的成果。
还没写任何测试就开始清理先在阶段 1(working-with-legacy-code)通过特征测试固定现有行为;覆盖范围应跟随你实际更改的路径。
试图一次性修复所有问题按三轴启发式排序——优先投入到变更频繁且核心域所在之处;偶发问题只需小修,不必重构。
任由 Agent 将代码“模块化”为一堆小类要求其遵循 software-design-philosophy 的深模块规则——合并那些总是一起流转的浅类;那是类泛滥,不是架构。
调用外部服务时未设置超时在阶段 7(release-it)为每个出站调用添加连接和读取超时,并为关键依赖添加断路器。
把微服务误当成架构应用依赖规则,先在单体中找出真正的有界上下文(clean-architecture、domain-driven-design);共享同一个数据库的服务就是分布式单体。

完成这段旅程

退出检查清单 — 每个方框都对应一项产物:

  • 已更改的模块都有特征测试,且测试通过(TESTING.md 中的安全网图已补全你修改过的路径)。
  • 每个出站调用都有超时设置,关键依赖项都有熔断器和隔离舱(RELIABILITY.md 中的集成点审计已通过)。
  • 重构模块遵循依赖规则 — 业务逻辑不导入任何框架或 ORM(ARCHITECTURE.md 中的分层图已列出,违规项均已解决)。
  • 单体应用有最新的上下文映射,并且至少有一个上下文通过防腐层实现隔离(ARCHITECTURE.md 中的限界上下文与上下文映射)。
  • 不再有未跟踪的临时变通方案;债务预算和破窗政策均已记录(TECH-DEBT.md 中的债务预算与破窗政策)。

关闭跟踪器:每个阶段都标记为 done 或 skipped: reason,并将剩余的 Next Actions 纳入 TECH-DEBT.md 债务台账,以免遗漏。然后继续前进:如果起点是较早期的原型,且你希望走这段旅程的生产就绪版本,请继续使用 improve-code-quality 技能;如果代码健康状况已恢复,接下来要改善产品体验,请继续使用 improve-app 技能。

评论

支持 Markdown