功能开关
用特性标志实现渐进启用、A/B 与紧急关闭;指导 Agent 在代码与运维两侧对齐命名、默认值、审计与清理策略。
SKILL 应区分发布开关(release toggle)、实验开关与运维开关(kill switch),并规定评估上下文:按用户、租户、百分比或环境分支,避免在热路径做远程同步阻塞。
要求记录谁可改、变更是否需审批、是否与配置中心或 LaunchDarkly 等系统同步,以及关闭后旧代码路径的测试义务,防止「永远开着的临时开关」。
类型与评估上下文
与发布流程结合:新功能默认 off,灰度阶段观测错误率与延迟,全量后设定移除日期或技术债工单,由 Agent 在 PR 中提示过期标志位。
- 默认值须对「配置服务不可用」有安全退化(通常保守关闭新行为)。
- 禁止用开关隐藏未完成的鉴权或支付等合规路径。
- 文档中列出当前生产关键开关清单与负责人。
// LaunchDarkly Node.js SDK 集成示例
import * as ld from '@launchdarkly/node-server-sdk';
const client = ld.init(process.env.LD_SDK_KEY!);
await client.waitForInitialization();
// 评估上下文:按用户 + 组织 + 环境
const context: ld.LDContext = {
kind: 'multi',
user: { key: userId, email: user.email, plan: user.plan },
org: { key: orgId, tier: org.tier },
};
// Release Toggle:新功能默认 off
const newCheckout = await client.variation(
'billing.new-checkout-flow', // flag key(规范化命名)
context,
false // 降级默认值(配置服务不可达时)
);
if (newCheckout) {
return handleNewCheckout(cart);
} else {
return handleLegacyCheckout(cart); // 旧路径必须保留至 flag 移除
}
// Ops Toggle(Kill Switch):紧急熔断,无需重新部署
// 在控制台/API 将 flag 设为 false 即可立即生效
// Unleash Node.js SDK 集成示例(开源替代)
import { initialize } from 'unleash-client';
const unleash = initialize({
url: 'https://unleash.internal/api/',
appName: 'myapp',
customHeaders: { Authorization: process.env.UNLEASH_TOKEN! },
});
// 功能开关类型示例
// release:新功能上线控制
const isNewUI = unleash.isEnabled('myapp.new-dashboard-ui', {
userId: String(user.id), // 按用户 ID 分桶(稳定哈希)
});
// experiment:A/B 测试,返回变体值
const variant = unleash.getVariant('myapp.checkout-button-color', {
userId: String(user.id),
});
const buttonColor = variant.enabled ? variant.payload?.value : 'blue';
// ops:运维控制,按环境区分
const rateLimitEnabled = unleash.isEnabled('ops.strict-rate-limit');
渐进发布(Rollout)
从内部/金丝雀到百分比或分桶全量:每一步都要有可观测指标与回滚预案;评估应带稳定上下文(用户 id、租户、请求特性),避免每次随机抖动导致体验不一致。
[ 定义标志:默认 off、命名空间、负责人 ]
│
▼
┌─────────────┐ 内测 / 固定 allowlist;验证功能与降级路径
│ 小范围启用 │
└─────────────┘
│
▼
┌─────────────┐ 按百分比或分桶;盯错误率、延迟、业务漏斗
│ 灰度扩大 │──── 异常:降百分比或一键回 off(见 Kill switch)
└─────────────┘
│
▼
┌─────────────┐ 全量 on 后:定移除日期或跟进工单,删死代码路径
│ 稳定与退役 │
└─────────────┘
Agent 生成调用点时,应显式默认分支(off 时行为)、缓存/批处理对一致性的影响,以及是否与实验曝光去重逻辑冲突。
紧急熔断(Kill switch)
运维开关用于事故或异常负载时快速关闭新路径:变更须可审计、传播延迟可预期(客户端缓存 TTL、CDN、多区域),并验证关闭后核心链路仍可用。
[ 告警 / 人工判断:需立即止血 ]
│
▼
┌─────────────┐ 在配置中心或控制台将标志置 off(或安全默认值)
│ 关闭新行为 │──── 记录操作者、工单号、时间戳
└─────────────┘
│
▼
┌─────────────┐ 等待缓存/实例刷新;抽样验证关键用户旅程
│ 传播与验证 │
└─────────────┘
│
▼
┌─────────────┐ 根因修复前保持 off;复盘是否需永久移除该开关
│ 事故跟进 │
└─────────────┘
- 关键路径禁止「只有新实现、无旧路径」——熔断后不能 500 或空白页。
- 与 on-call 手册对齐:哪个开关对应哪条产品能力、谁有权限改生产。
治理、默认值与清理
统一命名前缀(团队/域)、环境与层级(默认 / 覆盖),避免同一语义多个键。PR 审查检查:是否新增永久 if、是否缺少测试覆盖 off 与 on 双路径。
- 长期存在的标志应有业务 owner 与季度盘点;实验类须有过期时间。
- 与 CI:可对过期注释或 TODO(
FLAG_REMOVE_BY)做 lint 或定期报表。
// 测试中设置功能开关状态(Jest 示例)
import { FeatureFlagClient } from '../lib/feature-flags';
// 方式 1:mock 整个 SDK 客户端
jest.mock('../lib/feature-flags');
const mockClient = FeatureFlagClient as jest.Mocked;
describe('Checkout with new-checkout-flow flag', () => {
beforeEach(() => jest.clearAllMocks());
it('使用新版本当 flag=ON', async () => {
mockClient.prototype.isEnabled.mockResolvedValue(true);
const result = await processCheckout(cart);
expect(result.flow).toBe('new');
});
it('回退到旧版本当 flag=OFF(降级路径必须测试)', async () => {
mockClient.prototype.isEnabled.mockResolvedValue(false);
const result = await processCheckout(cart);
expect(result.flow).toBe('legacy');
});
it('配置服务不可达时使用默认值 false', async () => {
mockClient.prototype.isEnabled.mockRejectedValue(new Error('timeout'));
const result = await processCheckout(cart);
expect(result.flow).toBe('legacy'); // 降级默认值
});
});
标志键规范化
将草稿名称转为稳定键:小写、连字符分词、仅保留 a-z、0-9、.、-(便于与多数 SDK 及配置中心兼容)。下方输入即会规范化,空输入表示非法键。
规则:trim → 小写 → 空白与下划线合并为单个 - → 去掉非法字符 → 合并连续 - → 去掉首尾 -。
- 规范化结果
若结果为空,说明去掉非法字符后无有效键;团队可在此基础上加固定前缀(如 billing.)以区分域。
---
name: feature-flags
description: 功能开关类型、SDK 集成、测试与生命周期治理
version: 2.0
---
# 开关类型与命名约定
- release: team.feature-name (默认 off,发布后移除)
- experiment: team.exp-button-color (有过期日期,配套统计分析)
- ops: ops.strict-rate-limit (运维熔断,需审计变更)
- permission: billing.enterprise-only (按角色/租户授权)
# SDK 评估最佳实践
- 评估上下文:user.key + org.key + env(保证稳定分桶)
- 降级默认值:配置服务不可达时保守关闭新行为
- 热路径:用本地缓存(TTL 30s)避免同步远程调用阻塞
- 禁止:在循环或 ORM hook 中高频调用远程 SDK
# 生命周期管理
- 创建:PR 中添加 FLAG_OWNER 和 FLAG_REMOVE_BY 注释
- 激活:灰度 → 观测指标 → 推进百分比
- 清理:全量后 30 天内提 PR 删除 flag 代码与 SDK 调用
- CI Lint:检查代码中超过 FLAG_REMOVE_BY 日期的 flag 调用
# 测试双路径
- flag=true 路径:新功能行为验证
- flag=false 路径:降级/旧路径必须有独立测试用例
- SDK 不可达:confirm 默认值行为符合预期