功能开关
功能开关设计模式参考,了解不同类型的Feature Toggle及其最佳实践
🏷️ 开关类型
📐 设计模式
🔄 生命周期
💻 实现代码
1. Release Toggle (发布开关)
控制未完成功能的发布,允许代码合并到主分支但不对外暴露
// 隐藏未完成功能
if (featureToggle.isEnabled('new-search-algorithm')) {
return newSearch(query);
}
return legacySearch(query);
// 发布后应尽快移除2. Ops Toggle (运维开关)
运行时控制功能,用于降级、限流、紧急关闭
// 紧急降级
if (!featureToggle.isEnabled('payment-service')) {
return maintenancePage();
}
processPayment(order);
// 长期存在,需要监控3. Experiment Toggle (实验开关)
A/B测试,按用户分组展示不同功能
// A/B测试
const variant = featureToggle.getVariant('checkout-flow', userId);
if (variant === 'new-design') {
renderNewCheckout();
} else {
renderOldCheckout();
}
// 实验结束后移除4. Permission Toggle (权限开关)
基于用户权限控制功能访问,如VIP功能、付费功能
// 权限控制
if (featureToggle.isEnabled('premium-analytics', { user })) {
showAdvancedAnalytics();
} else {
showBasicAnalytics();
showUpgradePrompt();
}
// 长期存在策略模式 (Strategy Pattern)
将开关逻辑封装为策略,便于扩展和测试
class FeatureStrategy {
constructor(name) { this.name = name; }
isEnabled(context) { return false; }
}
class BooleanStrategy extends FeatureStrategy {
constructor(name, enabled) {
super(name);
this.enabled = enabled;
}
isEnabled() { return this.enabled; }
}
class PercentageStrategy extends FeatureStrategy {
constructor(name, percentage) {
super(name);
this.percentage = percentage;
}
isEnabled(context) {
const hash = hashCode(context.userId + this.name);
return Math.abs(hash) % 100 < this.percentage;
}
}
class UserSegmentStrategy extends FeatureStrategy {
constructor(name, rules) {
super(name);
this.rules = rules;
}
isEnabled(context) {
return this.rules.every(rule =>
evaluateRule(context.user, rule)
);
}
}装饰器模式 (Decorator Pattern)
用装饰器包装功能,开关控制是否启用
function withFeatureToggle(featureName, fn, fallbackFn) {
return function(...args) {
if (featureToggle.isEnabled(featureName)) {
return fn.apply(this, args);
}
return fallbackFn ? fallbackFn.apply(this, args) : null;
};
}
// 使用
const search = withFeatureToggle(
'new-search',
newSearchAlgorithm,
legacySearchAlgorithm
);
const results = search(query);功能开关生命周期
1
创建
定义开关名称、描述、类型和默认值
2
开发
在代码中添加开关判断,默认关闭,持续集成
3
测试
在测试环境开启开关验证功能
4
灰度发布
逐步增加流量百分比,监控指标
5
全量发布
确认稳定后开启100%流量
6
清理
移除开关代码和配置,避免技术债务
开关技术债务警告
⚠️ 过多的功能开关会导致: • 代码复杂度指数增长 (2^n 组合) • 测试覆盖困难 • 代码难以理解和维护 建议: • Release Toggle: 1-2周内移除 • Experiment Toggle: 实验结束后移除 • Ops Toggle: 长期保留但需文档化 • Permission Toggle: 长期保留 • 定期审计未使用的开关
Node.js 简易实现
class FeatureToggle {
constructor() {
this.flags = new Map();
}
register(name, config) {
this.flags.set(name, {
enabled: config.enabled ?? false,
strategy: config.strategy || 'boolean',
percentage: config.percentage || 0,
segments: config.segments || [],
});
}
isEnabled(name, context = {}) {
const flag = this.flags.get(name);
if (!flag) return false;
if (!flag.enabled) return false;
switch (flag.strategy) {
case 'boolean': return flag.enabled;
case 'percentage':
if (!context.userId) return false;
const hash = this._hash(context.userId + name);
return Math.abs(hash) % 100 < flag.percentage;
case 'segment':
return flag.segments.some(seg =>
this._matchSegment(context, seg)
);
default: return flag.enabled;
}
}
_hash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
return hash;
}
_matchSegment(context, segment) {
return Object.entries(segment).every(
([key, value]) => context[key] === value
);
}
}
// 使用
const features = new FeatureToggle();
features.register('new-ui', { enabled: true, strategy: 'boolean' });
features.register('dark-mode', { enabled: true, strategy: 'percentage', percentage: 50 });
features.register('premium', { enabled: true, strategy: 'segment', segments: [{ plan: 'premium' }] });
if (features.isEnabled('new-ui')) { /* ... */ }
if (features.isEnabled('dark-mode', { userId: 'user123' })) { /* ... */ }
if (features.isEnabled('premium', { plan: 'premium' })) { /* ... */ }React Hook
function useFeatureFlag(name, context = {}) {
const [enabled, setEnabled] = useState(
features.isEnabled(name, context)
);
useEffect(() => {
// 订阅开关变更
const unsubscribe = features.subscribe(name, (value) => {
setEnabled(value);
});
return unsubscribe;
}, [name]);
return enabled;
}
// 使用
function MyComponent() {
const showNewUI = useFeatureFlag('new-ui');
return showNewUI ? : ;
}