安全地从代码中移除功能开关,同时保持生产环境行为不变。当用户想要从代码中移除开关、删除开关引用,或在功能发布完成后创建将获胜变体硬编码的PR时使用。
LaunchDarkly 开关清理
你正在使用一项技能,它将指导你安全地从代码库中移除功能开关,同时保持生产环境行为不变。你的任务是探索代码库以了解开关的使用方式,查询 LaunchDarkly 以确定正确的正向值,干净地移除开关代码,并验证结果。
如果你尚未确定要清理哪个开关,请先使用开关发现技能来审计全局并找到候选开关。
前提条件
此技能需要在你的环境中配置远程托管的 LaunchDarkly MCP 服务器。
必需的 MCP 工具:
check-removal-readiness:详细的安全检查(并行协调开关配置、跨环境状态、依赖关系、代码引用和过期目标)get-flag:获取特定环境的开关配置
可选的 MCP 工具:
archive-flag:在代码移除后归档 LaunchDarkly 中的开关delete-flag:永久删除开关(不可逆,建议优先归档)
核心原则
- 安全第一:始终保留当前生产环境行为。
- 以 LaunchDarkly 为真相源:切勿猜测正向值。查询实际配置。
- 遵循约定:尊重现有代码风格和结构。
- 最小化变更:仅移除与开关相关的代码。不进行无关重构。
工作流程
步骤 1:探索代码库
在接触 LaunchDarkly 或移除代码之前,了解该开关在代码库中的使用方式。
-
查找所有对开关键的引用。 在代码库中搜索开关键字符串(例如
new-checkout-flow)。检查:- 直接 SDK 评估调用(
variation()、boolVariation()、useFlags()等) - 引用该键的常量/枚举
- 抽象 SDK 的包装器/服务模式
- 配置文件、测试和文档
- 参见 SDK 模式 获取按语言分类的完整模式列表
- 直接 SDK 评估调用(
-
理解分支逻辑。 对于每个引用,确定:
- 当开关为
true(或变体 A)时运行哪些代码? - 当开关为
false(或变体 B)时运行哪些代码? - 是否存在副作用、提前返回或嵌套条件?
- 当开关为
-
注意影响范围。 该开关涉及多少文件、组件或模块?一个在单个
if块中使用的开关比贯穿多个层的开关更简单。
步骤 2:运行移除就绪检查
使用 check-removal-readiness 获取详细的安全评估。此单一工具调用并行协调多项检查:
- 开关配置和定位状态
- 跨环境状态
- 依赖开关(前置条件)
- 过期目标
- 代码引用统计
该工具返回就绪判定:
safe:无阻塞或警告。可以继续移除。
caution:无硬阻塞但存在警告(例如,其他仓库中的代码引用、已计划的过期目标、标记为永久的开关)。显示警告并让用户决定。
blocked:硬阻塞阻止安全移除(例如,依赖开关、正在接收请求、定位已开启且有活跃规则)。显示阻塞项:用户必须先解决它们。
步骤 3:确定正向值
使用 get-flag 获取每个关键环境中的开关配置。正向值是替换代码中开关的变体。
| 场景 | 正向值 |
|---|---|
| 所有关键环境 ON,相同 fallthrough,无规则/目标 | 使用 fallthrough.variation |
| 所有关键环境 OFF,相同 offVariation | 使用 offVariation |
| 关键环境 ON/OFF 状态不同 | 不安全:停止并告知用户 |
| 关键环境提供不同的变体 | 不安全:停止并告知用户 |
步骤 4:呈现清理计划
在修改任何代码之前,向用户呈现摘要并等待确认:
- 正向值——将硬编码哪个变体及其原因(基于开关的当前状态)。
- 找到的所有代码引用——步骤 1 中的文件路径和行号。
- 计划变更——对于每个引用,描述将移除什么以及保留什么。
- 就绪判定——
check-removal-readiness的结果(safe、caution 或 blocked)以及任何警告。 - LaunchDarkly 操作——确认在代码更改完成后将归档开关。
在用户明确确认之前,不要进行代码更改。
步骤 5:从代码中移除开关
现在使用步骤 1 中学到的知识执行移除。
-
将开关评估替换为正向值。
- 保留与正向值匹配的代码分支
- 完全移除死分支
- 如果开关值被赋值给变量,将变量替换为字面量或内联它
-
清理死代码。
- 移除仅用于该开关的导入、常量和类型定义
- 移除仅用于死分支的函数、组件或文件
- 检查孤立的导出、钩子、辅助函数、样式和测试文件
- 如果仓库使用了未使用导出工具(Knip、ts-prune、lint 规则),运行它并移除任何与开关相关的孤立项
-
不要过度清理。
- 仅移除直接与开关相关的代码
- 不要重构、优化或“改进”周围代码
- 不要更改未触及代码的格式或样式
示例转换(布尔开关,正向值 = true):
// 之前
const showNewCheckout = await ldClient.variation('new-checkout-flow', user, false);
if (showNewCheckout) {
return renderNewCheckout();
} else {
return renderOldCheckout();
}
// 之后
return renderNewCheckout();
步骤 6:创建拉取请求
使用 references/pr-template.md 中的模板编写结构化的 PR 描述。PR 应清晰传达:
- 移除了哪个开关及其原因
- 正向值是什么以及为什么正确
- 就绪评估结果(来自
check-removal-readiness) - 移除了哪些代码以及保留了哪些行为
- 其他仓库是否仍引用此开关
步骤 7:验证
在认为工作完成之前:
- 代码编译并通过 lint。 运行项目的构建和 lint 步骤。
- 测试通过。 如果开关在测试中使用,应更新测试以反映硬编码的行为。
- 无剩余引用。 再次搜索代码库中的开关键,确保没有遗漏。
- PR 完整。 描述涵盖就绪评估、正向值理由以及任何需要的跨仓库协调。
边缘情况
| 情况 | 操作 |
|---|---|
| 在 LaunchDarkly 中未找到开关 | 告知用户,检查键是否有拼写错误 |
| 开关已归档 | 询问是否仍需要代码清理(开关已从 LD 中消失,但代码可能仍引用它) |
| 代码库中存在多种 SDK 模式 | 搜索所有模式:variation()、boolVariation()、variationDetail()、allFlags()、useFlags() 以及任何包装器 |
动态开关键(flag-${id}) |
警告自动移除可能不完整:需要人工审查 |
| 代码与 LD 中的默认值不同 | 在 PR 描述中标记为不一致 |
| 移除后仍有孤立的导出/文件 | 运行未使用导出检查并移除死文件 |
禁止事项
- 不要更改与开关清理无关的代码。
- 不要重构或优化超出开关移除的范围。
- 不要移除仍在积极发布中的开关。
- 不要猜测正向值:始终查询 LaunchDarkly。
清理之后
一旦 PR 合并并部署:
- 在 LaunchDarkly 中归档开关,使用
archive-flag。归档是可逆的;删除则不可逆。始终先归档。 - 通知其他团队,如果
check-removal-readiness报告了其他仓库中的代码引用。 - 如果开关有未决的定位更改,可以忽略:开关正在被移除。






