功能开关(Feature Flags)是“稳定性”和“迭代速度”的折中方案。本文给出一套可直接复用的代码范式:本地默认值、远程配置、缓存与回滚。
1. 定义统一 Flag 列表
enum FeatureFlag {
newProfile,
chatV2,
experimentalFeed,
}
2. 本地默认值
const defaultFlags = {
FeatureFlag.newProfile: false,
FeatureFlag.chatV2: true,
FeatureFlag.experimentalFeed: false,
};
3. 统一管理器
class FeatureFlags {
final Map<FeatureFlag, bool> _flags;
FeatureFlags(this._flags);
bool isEnabled(FeatureFlag flag) => _flags[flag] ?? false;
static Future<FeatureFlags> load() async {
// 先加载本地默认值
final flags = Map<FeatureFlag, bool>.from(defaultFlags);
// 再加载远程配置
final remote = await RemoteConfig.fetch();
flags.addAll(remote);
return FeatureFlags(flags);
}
}
4. 远程配置(示例)
class RemoteConfig {
static Future<Map<FeatureFlag, bool>> fetch() async {
// TODO: 用真实 API 替换
await Future.delayed(const Duration(milliseconds: 200));
return {
FeatureFlag.experimentalFeed: true,
};
}
}
5. 缓存与回滚
远程配置应该缓存,以便离线可用:
class FlagCache {
static Future<void> save(Map<FeatureFlag, bool> flags) async {
final prefs = await SharedPreferences.getInstance();
final map = flags.map((k, v) => MapEntry(k.name, v));
await prefs.setString('flags', jsonEncode(map));
}
static Future<Map<FeatureFlag, bool>> load() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString('flags');
if (raw == null) return {};
final map = jsonDecode(raw) as Map<String, dynamic>;
return map.map((k, v) => MapEntry(FeatureFlag.values.byName(k), v as bool));
}
}
如果远程失败,就使用缓存或默认值。
6. 使用方式
final flags = await FeatureFlags.load();
if (flags.isEnabled(FeatureFlag.newProfile)) {
showNewProfile();
} else {
showOldProfile();
}
7. 实践清单
- Flag 列表统一定义
- 默认值 + 远程配置合并
- 缓存兜底
- 远程失败可回滚
总结
功能开关不是“开关按钮”,而是一个可靠的工程能力:默认值、远程下发、缓存回滚缺一不可。