大模型应用
在不敢改动的老旧代码库中安全偿还技术债(不重写、持续交付)
当面对庞大陈旧、人人害怕触碰的遗留代码库,想就地偿还技术债、避免大爆炸式重写时,按八个阶段循序渐进地改造。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在不敢改动的老旧代码库中安全偿还技术债(通用
解决思路
- 先恢复进度:动手前先读取 docs/REMOVE-TECHNICAL-DEBT-PLAN.md 和 Journey Map 中的所有产物;若追踪文件已存在,用 3-5 行总结当前进度并询问用户从哪个阶段进入,有追踪文件的旅程是恢复而非重启。
- 仅在首次运行时做 Intake:若无追踪文件,先问 Intake 问题,再创建 docs/REMOVE-TECHNICAL-DEBT-PLAN.md,为每个阶段标注状态 pending | in-progress | awaiting-evidence | done | deferred: reason | skipped: reason,直到追踪文件存在且用户确认阶段计划。
- 阶段进入:宣布该阶段做什么、强制做什么决策、产出什么产物、大致工作量;提供 proceed / skip / defer 选项——标记为 GATE 的阶段可 defer 但不可 skip;用户选择 proceed 后把该阶段标为 in-progress。
- 技能调用与回退:加载该阶段的技能并使用它(每个阶段的 Invoke 行按 slug 指定技能);若不可用,提供 npx skills add wondelai/skills/<slug> --global;若用户拒绝,则按该阶段的 Brief(最小可行方法)执行,并说明当前处于哪种模式。
- 阶段内决策:对「Decide with the user」下的每个问题都提问,给出具体选项和你的推荐,并把选择记录到追踪文件的 Key Decisions;静默做出的决策视为缺陷。
- 阶段退出:写入前先展示产物草稿供签字确认;批准后写入或扩展 docs/ 文件,更新追踪文件(状态、Key Decisions、Next Actions);文件写入且阶段行显示 done 才算完成。
- 产物纪律:写前先读;文件缺失才创建,否则扩展——添加或更新你的章节,保留他人的内容;docs/ 下文件名用大写;每条建议都落为复选框或带 owner 和 priority 的表格行;首次创建 docs/ 文件时参考 references/artifact-templates.md,按完整骨架(所有章节标题)创建后再填充本阶段涉及的章节。
- Phase 1 是 GATE;提交保持单一目的;发现的 bug 只固定不修复:任何未列入 Safety Net Map 的代码都不得改动——先固定它(未列入 Pinned behaviors 即视为缺失,Gaps 列中的条目也不可动);结构性与行为性变更绝不共用一个提交:重构在测试全绿时以纯结构提交完成,随后行为变更单独提交;重构中途测试变红意味着回滚而非调试;安全网测试新增和 docs/ 更新各自作为单一目的提交;特征化过程中发现的 bug 只固定并在 Debt Ledger 中开票,绝不静默修复——调用方可能依赖该怪癖。
- Intake 问题(创建追踪文件前询问):(1) 系统做什么、多大、多老,坏了最坏后果是什么(界定风险并决定阶段优先级);(2) 你下一个要改哪个文件,哪些文件在 git log 中改动最频繁或是核心领域(用三轴启发式选出 Phase 1 起始模块);(3) 是否已有测试套件、今天在 CI 中是否全绿,或因不稳定被禁用(界定 Phase 1 安全网范围);(4) 技术栈是什么——框架、ORM、数据库——业务逻辑是否纠缠在 controller 或 ORM model 中(决定 Phase 5 边界工作和 Phase 8 上下文映射);(5) 是否已在生产环境有真实用户,调用哪些外部依赖——第三方 API、支付、邮件、队列(决定 Phase 7 集成点审计);(6) 是否有人提议大爆炸式重写(引出本旅程要替代的决策);(7) 现在想做多少(Phase 1-3 最快改变团队与代码的关系;5-8 在边界与拆分成为瓶颈时做)。
- 阶段跳过启发式:当代码库小到单一模型仍能舒适容纳时跳过 Phase 8;仅当偿还过程暴露出真实的扩展或数据层限制时才添加可选的 system-design 或 ddia-systems——从需求出发而非方案;一旦有真实用户,Phase 7 不可选——超时和熔断器是基本要求(可保持 deferred: reason,但绝不 skipped);绝不跳过 Phase 1,它是门禁。然后按模板创建追踪文件并确认计划。
- Phase 1——建立安全网并找到起点(working-with-legacy-code)——GATE:在改动点固定当前行为并绘制影响范围,使后续每个阶段都可验证;任何阶段不得触碰未列入 Safety Net Map 的代码。Brief(回退):遗留代码即无测试的代码——cover and modify,绝不 edit and pray;运行 Legacy Code Change Algorithm:识别改动点、找到测试点、用侵入性最小的接缝打破依赖(Parameterize Constructor 带生产默认值;Extract Interface)、编写固定实际行为的特征化测试(断言一个错误值、读取失败信息、固定真实值),然后改动;用 effect sketch 限定影响范围,找到少量测试覆盖最多行为的 pinch point;来不及覆盖的紧急改动用 Sprout/Wrap,并把未测试的宿主记为债务。Invoke:用 working-with-legacy-code 技能处理 intake 时选定的起始模块,要求给出从入口方法出发的 effect sketch、pinch points、要打破的接缝,以及固定当前行为的最小特征化测试集。Decide with the user:(1) 用三轴启发式确认起始模块——下一个要改的、高改动(git log)、核心领域;(2) 特征化时发现的 bug:固定错误行为并记入 Debt Ledger,绝不静默修复——调用方可能依赖该怪癖,确认用户接受。Artifact:创建 docs/TESTING.md,含 ## Test Strategy、## Safety Net Map(module | pinned behaviors | test files | gaps)、## Characterization Backlog;创建 docs/TECH-DEBT.md,含 ## Debt Ledger(item | location | type | risk | effort | priority | status)和 ## Sprout / Wrap Register,登记任何 sprout 或 wrap 的代码;把 effect-sketch 的 pinch points 记到 ## Test Strategy 下;更新追踪文件。Done when:目标模块行为已固定、测试套件全绿、两个文件存在、追踪文件显示 Phase 1 done——此时才解锁 Phase 2。
- Phase 2——用命名的重构手法重组结构(refactoring-patterns):把「清理一下」变成一次一小步、保持行为的命名变换。Brief(回退):重构不是重写:小的保持行为的变换,每个都有测试支撑;每种坏味道对应一个命名重构——Extract Method 是主力(若你要写注释解释某块代码,就提取它并以注释命名);还有 Replace Nested Conditional with Guard Clauses、Replace Conditional with Polymorphism、Introduce Parameter Object、Extract Class;流程:测试全绿、一次变换、测试全绿、提交;测试变红意味着回滚而非调试;Branch by Abstraction 在生产中迁移大型结构;Preparatory Refactoring 先让改动变容易;Rule of Three 防止过早抽象。Invoke:用 refactoring-patterns 技能处理有坏味道的模块和 Phase 1 的测试,要求它命名每种坏味道、引用对应变换、一次应用一个并在每次之间运行测试;对大型迁移要求给出 Branch by Abstraction 计划。Decide with the user:本轮处理哪些坏味道;即将到来的功能是否值得先在其插入点做 Preparatory Refactoring;大型迁移是否放到 Branch by Abstraction 之后。Artifact:扩展 docs/TECH-DEBT.md 的 ## Smell Inventory(smell | location | refactoring | status):每种坏味道一行,记录所应用的命名重构及其状态;更新追踪文件。Done when:目标坏味道都有命名重构和 done / ticketed 状态、测试全绿、每个结构性变更都在纯结构提交中落地。
- Phase 3——在你触碰的代码处提升可读性(clean-code):针对你已在改动的区域优化读者体验——命名、小而单一职责的函数、安全的错误处理。Brief(回退):代码被读的次数远多于被写(10:1+);命名揭示意图(elapsedTimeInDays 而非 d);布尔值读作谓词;每个概念一个词;函数在一个抽象层级只做一件事,参数 0-2 个(flag 参数等于两个函数);Command-Query Separation:改变状态或返回值,绝不两者兼有;错误处理是遗留事故高发区:优先异常而非返回码、捕获具体类型、绝不返回或传入 null(空集合、Optional、Null Object)、把嘈杂的第三方 API 包在适配器后、每个错误都带上操作 + 状态;Boy Scout Rule:离开时让代码比你发现时更干净。Invoke:用 clean-code 技能处理目标模块,要求给出六个维度的 0-10 评分、按优先级排序的前十项修复,以及错误处理审计(裸 catch、返回 null、无上下文错误、未包装的第三方 SDK)。Decide with the user:哪些修复现在做、哪些记为债务;团队今后采用的命名 / 错误处理约定。Artifact:扩展 docs/TECH-DEBT.md:为每个命名 / 函数 / 错误坏味道添加 ## Smell Inventory 行,并把约定规则记到 ## Adopted Conventions;更新追踪文件。Done when:模块评分 8+ 或每个低于 8 的缺口都是带修复方案的 Smell Inventory 行、约定已记录、Phase 1 测试仍通过。
- Phase 4——用深模块降低复杂度(software-design-philosophy):直击复杂度本身——把真正的机器藏在简单接口后,而不是无监督 agent 造成的 classitis。Brief(回退):复杂度是敌人;用每个改动是提高还是降低整体复杂度来评判;症状:change amplification、cognitive load、unknown unknowns;模块深度 = 功能 ÷ 接口复杂度;深模块把机器藏在小型接口后,浅模块则不然(classitis)——合并总是一起出现的浅类;警惕 information leakage(一个决策反映在多个模块中)和 temporal decomposition(按执行顺序而非知识组织);这是战术→战略的转变:投入 10-20% 保持设计干净。Invoke:用 software-design-philosophy 技能处理目前触碰过的模块集,问哪些类是浅的、哪个设计决策跨模块泄漏、如何整合为接口更简单的深模块——每个改动都标注为提升或降低复杂度。Decide with the user:哪些整合现在做、哪些推迟,防止过度合并真正无关的关注点。Artifact:扩展 docs/TECH-DEBT.md 的 ## Smell Inventory,加入 classitis / 浅模块 / information leakage 条目及所应用的整合;更新追踪文件。Done when:每个浅模块簇已整合或带修复方案记录、接口数量没有为「模块化」而增长、测试全绿。
- Phase 5——划出依赖边界(clean-architecture):让框架和数据库依赖业务规则,逐模块进行——而非相反。Brief(回退):Dependency Rule:源码依赖指向内层——Entities、Use Cases、Interface Adapters、Frameworks/Drivers;内层不得命名任何外层;数据库和 web 是细节,是你规则的插件;用 Dependency Inversion 强制:Use Case 拥有 repository 接口,Postgres/Stripe 实现放在外层适配器;SOLID 是中观工具;Common Closure 和 Acyclic Dependencies 找出真实边界;共享一个数据模型的微服务是分布式单体——先在单体内部应用该规则。Invoke:用 clean-architecture 技能处理当前模块图和 intake 得到的技术栈,要求它绘制依赖图、列出业务逻辑 import ORM 或框架的每处违规、先选改动最多的模块、展示如何抽取为框架无关、位于自有接口后的 Use Cases。Decide with the user:本轮把边界推进多远;先包装哪些供应商(支付、存储);任何提议的服务拆分是真实边界还是只会增加分布式单体。Artifact:扩展 docs/ARCHITECTURE.md:在 ## Layer Map & Dependency Rule(violation | location | fix | status)记录分层与违规,在 ## Decision Log 记录边界选择;更新追踪文件。Done when:每处 Dependency Rule 违规都是带修复方案的追踪行、至少最高风险的供应商已包装、业务规则测试无需框架即可运行、测试全绿。
- Phase 6——固化习惯(pragmatic-programmer):设定元原则,防止本旅程结束后代码库悄悄重新累积债务。Brief(回退):Broken Window Theory:一个未修复的 hack 会拉低下一个的标准——立即修复或用追踪工单封住,绝不留下未追踪的 // TODO;DRY 关乎知识而非文本——去重两处相同的规则(客户端和服务端的校验),放过巧合的相似;Orthogonality:改一个组件不应影响另一个;Reversibility:把供应商包在自己的接口后;Design by Contract + crash early:在加固边界守护前置条件和不变式,让非法状态在源头大声失败。Invoke:用 pragmatic-programmer 技能扫描整个代码库,要求它标出重复的知识(忽略巧合重复)、要封住的破窗和未追踪 TODO、以及需要 Design-by-Contract 守护子句的边界。Decide with the user:每次迭代的债务预算和破窗政策——什么现在修、什么开票。Artifact:扩展 docs/TECH-DEBT.md:把重复知识和破窗项记入 ## Debt Ledger,把约定政策记入 ## Debt Budget & Broken-Windows Policy 和 ## Adopted Conventions;更新追踪文件。Done when:重复知识命中已入账或修复、没有未追踪的 hack、债务预算政策已写下。
- Phase 7——加固集成点(release-it):让每个集成点优雅降级,使缓慢或失败的依赖无法拖垮整个系统。Brief(回退):通过 QA 的软件不等于能在生产中存活的软件;集成点是头号杀手——慢响应比没有响应更糟;不可妥协项:每个出站调用都要有 connect + read 超时;对失败依赖加 Circuit Breaker(跳闸打开、快速失败、半开恢复);用 Bulkheads 隔离资源池;Retry 带指数退避 + jitter;Steady State 清理累积的杂物;限定每个查询——无界结果集在规模上会崩溃,所以加 LIMIT 和分页;用 feature flags 和 expand-contract 迁移把部署与发布解耦;加深度健康检查、RED 指标、基于症状的告警。Invoke:用 release-it 技能处理 intake 得到的出站依赖,要求审计无超时的调用、无界查询和列表端点、熔断器 + 舱壁的位置、针对高风险 schema 的 expand-contract 迁移计划,以及深度健康检查 + RED 指标 + 告警设计。Decide with the user:熔断阈值、哪些依赖获得专用资源池、告警症状与阈值(错误率、延迟)。Artifact:扩展 docs/RELIABILITY.md,含 ## Integration-Point Audit(dependency | timeout | circuit breaker | bulkhead | retry policy | status)、## Query & Resource Findings、## Health Checks & Metrics、## Deploy vs Release;更新追踪文件。Done when:每个出站调用都有超时、关键依赖有熔断器和舱壁、无界查询已限定、存在深度健康检查 + RED 指标 + 症状告警、审计中关键路径无未决行。
- Phase 8——切分为限界上下文(domain-driven-design):把大泥球拆解为团队可拥有、最终可抽取的上下文——无需重写。Brief(回退):模型即代码;从 Ubiquitous Language 开始:把纯技术命名(DataManager、Helper)改为领域术语;一个难以命名的概念意味着模型错误;从现状出发映射 Bounded Contexts(同一个词在不同上下文中含义不同),与团队边界对齐;Anti-Corruption Layer 让干净的新上下文与遗留核心对话而不让旧模型泄漏进来——Strangler Fig 的基础;上下文内部:小 Aggregate 只有一个根、其他以 ID 引用、不可变 Value Objects、过去时 Domain Events;在 Core Domain 上投入最多。Invoke:用 domain-driven-design 技能处理纠缠的模块和 Phase 5 得到的边界,要求它构建统一语言、映射当前与目标限界上下文、为新干净上下文设计防腐层、把任何 god aggregate 缩小到真正的 consistency boundary。Decide with the user:先切哪个上下文(价值所在的 Core Domain);哪些边界与团队结构对齐;跨上下文调用是否改为 Domain Events。Artifact:扩展 docs/ARCHITECTURE.md:在 ## Bounded Contexts & Context Map 记录映射,在 ## Domain Glossary (Ubiquitous Language) 记录术语,在 ## Decision Log 记录拆分选择;更新追踪文件。Done when:当前上下文图已绘制、第一个目标上下文及其防腐层已定义、关键领域术语已入词汇表、任何重塑的 aggregate 保持测试全绿。
- 可选阶段:system-design 在偿还过程暴露需要重新架构的真实扩展限制时加入(扩展 docs/ARCHITECTURE.md 的 ## System Context);ddia-systems 在数据层决策(隔离、复制、存储适配)属于债务时加入(扩展 docs/ARCHITECTURE.md 的 ## Data & Storage Decisions);team-topologies 在债务聚集于团队边界与架构冲突处时加入(扩展 docs/OPERATIONS.md 的 ## Team Structure);可选阶段遵循同样的操作规则——像核心阶段一样加载并使用所列技能;在 Add-when 条件首次成立处插入——扩展和数据阶段在 Phase 5 之后,团队拓扑阶段与 Phase 8 的上下文边界并行。
- 常见错误与修复:提议大爆炸式重写→承诺就地偿还债务,每个阶段都让系统更好且仍在交付,最坏情况快速回滚,先展示一个小而快的胜利;还没写一个测试就开始清理→先在 Phase 1(working-with-legacy-code)用特征化测试固定行为,覆盖率跟随你实际改动的路径;试图一次修好所有东西→用三轴启发式分诊——在改动频繁且 Core Domain 所在处投入,一次性地点用 sprout 而非重构;让 agent「模块化」成微小类→用 software-design-philosophy 的深模块规则约束它——合并总是一起出现的浅类,那是 classitis 不是架构;调用外部服务没有超时→在 Phase 7(release-it)为每个出站调用加 connect + read 超时,并对关键依赖加熔断器;把微服务误当架构→先在单体内部应用 Dependency Rule 并找出真实的限界上下文(clean-architecture、domain-driven-design),共享一个数据库的服务是分布式单体。
- 完成旅程的退出清单(每项都绑定产物):改动过的模块有全绿的特征化测试(TESTING.md 的 Safety Net Map 覆盖你触碰的路径);每个出站调用都有超时,关键依赖有熔断器和舱壁(RELIABILITY.md 的 Integration-Point Audit 无未决项);重构过的模块满足 Dependency Rule——业务逻辑不 import 任何框架或 ORM(ARCHITECTURE.md 的 Layer Map,违规已关闭);单体有当前上下文图,且至少有一个位于防腐层后的干净上下文(ARCHITECTURE.md 的 Bounded Contexts & Context Map);没有未追踪的 hack,债务预算和破窗政策已写下(TECH-DEBT.md 的 Debt Budget & Broken-Windows Policy)。关闭追踪文件:每个阶段为 done 或 skipped: reason,剩余 Next Actions 转入 TECH-DEBT.md 的 Debt Ledger 以免丢失;然后路由后续:当起点是较年轻的原型且想要生产就绪版本时,继续用 improve-code-quality 技能;当代码健康恢复且下一步是产品体验时,继续用 improve-app 技能。
适用边界 · 注意事项
- 代码库很小、单一模型仍能舒适容纳时,不需要做 Phase 8 的限界上下文拆分。
- 没有真实用户、尚未上生产时,Phase 7 的集成点加固可以推迟(但一旦有真实用户就不可跳过)。
- 如果目标只是优化一个已能正常工作的代码库的速度,应改用 architecture-optimization;如果是全新原型的代码质量,应改用 improve-code-quality;如果是绿地项目的结构设计,应改用 design-code-architecture。