بنقرة واحدة
deprecation-and-migration
管理弃用和迁移。在移除旧系统、API 或功能时使用。在将用户从一个实现迁移到另一个实现时使用。在决定是否维护或淘汰现有代码时使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
管理弃用和迁移。在移除旧系统、API 或功能时使用。在将用户从一个实现迁移到另一个实现时使用。在决定是否维护或淘汰现有代码时使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
指导稳定的 RPC、存储协议和接口设计。在设计节点间 RPC、存储协议语义、模块边界或任何公共接口时使用。在定义 gRPC service、对象存储或文件系统语义、节点间的类型契约,或确立组件边界时使用。
自动化 CI/CD 流水线设置。在设置或修改构建和部署流水线时使用。当需要自动化质量门禁、在 CI 中配置测试运行器,或建立部署策略时使用。当需要处理 CI 上不稳定(flaky)的测试时使用。
进行多维度代码审查。在合并任何变更之前使用。在审查由你自己、另一个智能体或人类编写的代码时使用。当需要在代码进入主分支之前评估其跨多个维度的质量时使用。当需要评估变更的正确性、可读性、架构与性能,或审查 PR/diff 时使用。
为清晰性而简化代码。在重构代码以提高可读性而不改变行为时使用。当代码可以正常工作但比应有的更难阅读、维护或扩展时使用。在审查已积累不必要复杂性的代码时使用。当组件过度设计、过于炫技而难以维护时使用。当需要清理越来越难读懂的代码时使用。
验证系统的一致性与持久性承诺。在设计或修改复制协议、写路径、崩溃恢复逻辑时使用。当需要证明已确认的写入不丢失、副本间不发散、崩溃后能恢复到一致状态,或评审 fsync/校验和/事务语义时使用。
优化智能体上下文设置。在开始新会话、智能体输出质量下降、在不同任务之间切换,或需要为项目配置规则文件和上下文时使用。
| name | deprecation-and-migration |
| description | 管理弃用和迁移。在移除旧系统、API 或功能时使用。在将用户从一个实现迁移到另一个实现时使用。在决定是否维护或淘汰现有代码时使用。 |
代码是负债,而非资产。每一行代码都有持续的维护成本——需要修复的缺陷、需要更新的依赖项、需要应用的安全补丁以及需要培训的新工程师。弃用是移除不再值得保留的代码的纪律,迁移是将用户安全地从旧系统转移到新系统的过程。
大多数工程组织擅长构建东西。很少擅长移除东西。本技能填补了这一空白。
每一行代码都有持续的成本:它需要测试、文档、安全补丁、依赖项更新,以及任何在附近工作的人的心智开销。代码的价值在于它提供的功能,而非代码本身。当相同的功能可以用更少的代码、更少的复杂度或更好的抽象来提供时——旧代码就应该消失。
当用户足够多时,每个可观察的行为都会被依赖——包括缺陷、时序怪癖和未记录的副作用。这就是为什么弃用需要主动迁移,而不仅仅是公告。当用户依赖替换系统无法复制的行为时,他们无法"直接切换"。
在构建新东西时,问:"我们在 3 年后将如何移除它?"具有清晰接口、功能标志和最小暴露面的系统比到处泄漏实现细节的系统更容易弃用。
在弃用任何东西之前,回答这些问题:
1. 这个系统是否仍然提供独特的价值?
→ 如果是,维护它。如果不是,继续。
2. 有多少用户/消费者依赖它?
→ 量化迁移范围。
3. 是否存在替代方案?
→ 如果没有,先构建替代方案。不要在没有替代方案的情况下弃用。
4. 每个消费者的迁移成本是多少?
→ 如果可以简单自动化,就自动化。如果是手动且高成本的,与维护成本进行权衡。
5. 不弃用的持续维护成本是多少?
→ 安全风险、工程师时间、复杂度的机会成本。
| 类型 | 何时使用 | 机制 |
|---|---|---|
| 建议性 | 迁移是可选的,旧系统是稳定的 | 警告、文档、提示。用户按自己的时间表迁移。 |
| 强制性 | 旧系统有安全问题、阻碍进展,或维护成本不可持续 | 硬性截止日期。旧系统将在日期 X 被移除。提供迁移工具。 |
默认为建议性。 仅当维护成本或风险证明强制迁移是合理的时候才使用强制性。强制性弃用需要提供迁移工具、文档和支持——你不能只宣布一个截止日期。
在没有可工作的替代方案之前不要弃用。替代方案必须:
## 弃用通知:OldService
**状态:** 自 2025-03-01 起已弃用
**替代方案:** NewService(见下方迁移指南)
**移除日期:** 建议性——尚无硬性截止日期
**原因:** OldService 需要手动扩缩容且缺乏可观测性。
NewService 自动处理这两者。
### 迁移指南
1. 将 `client "example.com/old-service"` 替换为 `client "example.com/new-service"`
2. 更新配置(见下方示例)
3. 运行迁移验证脚本:`go run ./cmd/migrate-check`
逐个迁移消费者,而非一次性全部迁移。对于每个消费者:
1. 识别与已弃用系统的所有接触点
2. 更新为使用替代方案
3. 验证行为匹配(测试、集成检查)
4. 移除对旧系统的引用
5. 确认无回归
变更规则: 如果你拥有正在被弃用的基础设施,你有责任迁移你的用户——或提供不需要迁移的向后兼容更新。不要仅发布弃用公告然后让用户自行解决。
仅在所有消费者都已迁移之后:
1. 验证零活跃使用(指标、日志、依赖分析)
2. 移除代码
3. 移除相关测试、文档和配置
4. 移除弃用通知
5. 庆祝——移除代码是一项成就
并行运行新旧系统。逐步将流量从旧系统路由到新系统。当旧系统处理 0% 流量时,移除它。
阶段 1:新系统处理 0%,旧系统处理 100%
阶段 2:新系统处理 10%(灰度)
阶段 3:新系统处理 50%
阶段 4:新系统处理 100%,旧系统空闲
阶段 5:移除旧系统
创建一个适配器,将调用从旧接口转换到新实现。消费者继续使用旧接口,而你在后台迁移。
// 适配器:旧接口,新实现
type LegacyTaskService struct {
newService *NewTaskService
}
// 旧方法签名,委托给新实现
func (s *LegacyTaskService) GetTask(id int) (*OldTask, error) {
task, err := s.newService.FindByID(strconv.Itoa(id))
if err != nil {
return nil, err
}
return toOldFormat(task), nil
}
使用功能标志逐个将消费者从旧系统切换到新系统:
func getTaskService(userID string) TaskService {
if featureFlags.Enabled("new-task-service", userID) {
return NewTaskService()
}
return NewLegacyTaskService()
}
Schema 变更是风险最高的迁移,因为数据是唯一无法通过回滚部署来撤销的东西。失败模式是将 schema 变更与代码变更耦合:在同一个发布中重命名列并同时开始使用新名称,在滚动发布窗口期间——当新旧代码同时运行时——其中之一会查询一个不存在的列。修复方法是永远不要原地修改列。以增量添加的阶段进行迁移,使新旧代码在每个步骤都有效。
扩展 ──────────────→ 迁移 ──────────────→ 收缩
添加新列,可为空, 回填现有行,从应用 一旦没有代码再读取旧列,
与旧列并存 层双写新旧两列 在稍后的独立部署中删除它
具体示例——将 name 重命名为 full_name:
full_name 列。部署。(旧代码忽略它;没有东西出错。)name 和 full_name。部署。name 复制到 full_name,分批进行,以免锁表。full_name,继续双写。部署并观察。name,然后——在单独的、稍后的部署中——删除该列。每一步都是可独立部署和可逆的:如果步骤 4 出现问题,回滚代码,而 full_name 仍然在写入。将每个阶段视为薄垂直切片——参见 incremental-implementation 技能。
规则:
down。UPDATE 会锁表;分块并限流。CREATE INDEX CONCURRENTLY)。僵尸代码是无人拥有但人人依赖的代码。它没有被积极维护,没有明确的所有者,并累积安全漏洞和兼容性问题。迹象:
应对措施: 要么指定所有者并妥善维护它,要么通过具体的迁移计划弃用它。僵尸代码不能留在无人区——它要么得到投入,要么被移除。
| 合理化借口 | 现实 |
|---|---|
| "它还能用,为什么要移除它?" | 能工作但无人维护的代码会累积安全债务和复杂度。维护成本在无声地增长。 |
| "可能以后有人需要它" | 如果以后需要,可以重新构建。保留"以防万一"的未使用代码的成本高于重新构建的成本。 |
| "迁移太贵了" | 将迁移成本与 2-3 年的持续维护成本进行比较。长期来看,迁移通常更便宜。 |
| "我们完成新系统后再弃用" | 弃用规划始于设计阶段。等到新系统完成时,你会有新的优先级。现在规划。 |
| "用户会自己迁移的" | 他们不会。提供工具、文档和激励——或自己完成迁移(变更规则)。 |
| "我们可以无限期地维护两个系统" | 两个做同样事情的系统意味着双倍的维护、测试、文档和培训成本。 |
| "只是重命名一列,就一行代码" | 在滚动发布期间,新旧代码同时运行——其中之一会查询一个不再存在的列。扩展/收缩,永远不要原地重命名。 |
| "我会在同一迁移中添加列并删除旧列" | 这会将安全的新增与破坏性的删除耦合。删除操作在它们自己的部署中进行,在没有代码再引用旧结构之后。 |
| "需要时我们再写回滚脚本" | 没有 down 路径的迁移就是不可回滚的部署。在合并之前编写并运行 down。 |
完成弃用后:
完成数据库 schema 迁移后: