Feature Toggle 是一种在运行时切换应用程序功能的机制,允许开发人员在不更改代码和重新部署的情况下管理功能的可用性。与条件编译(ifdef)不同,toggle 在运行时级别工作,并且可以动态更改。根据 Martin Fowler(2024) 的说法,feature toggles 是主干开发和持续交付的关键要素。Feature toggle 为团队提供了管理发布和实验的灵活性。
要点
Feature Toggle(功能开关)——是一种将新功能代码包裹在检查配置参数值的条件结构中的技术。如果参数为 true ——新功能激活,如果为 false ——执行旧代码。与 feature flag 的关键区别在于,toggle 是一个二进制开关,按照“开/关”原则工作,没有复杂的目标规则和流量分配规则。
Feature toggle 作为围绕新功能的普通 if 结构实现。Toggle 的值存储在应用程序配置中——环境变量、JSON 文件或数据库中。启动时,应用程序加载配置并使用它来决定功能的可见性。在最简单的情况下,更改 toggle 值需要重新启动应用程序,但在生产系统中,toggles 通常通过外部配置服务器或 API 支持热重载。
我们来看看在 JavaScript(Node.js) 中 feature toggle 的实现。开关存储在 JSON 配置中,并在服务器启动时加载。中间件在将请求定向到新或旧处理程序之前检查 toggle 值。这种实现允许在不中断当前 API 版本工作的情况下向主代码分支添加新功能。
const config = require("./config.json");
const toggles = {
get(name) {
return config.features[name] ?? false;
},
isEnabled(name, context) {
const toggle = config.features[name];
if (!toggle) return false;
if (toggle.enabled === true) return true;
if (toggle.percentage && context.userId) {
return hashCode(context.userId) % 100 < toggle.percentage;
}
return false;
}
};
const app = express();
app.use("/api/checkout", (req, res, next) => {
if (toggles.isEnabled("new_checkout", req)) {
return newCheckoutHandler(req, res);
}
return legacyCheckoutHandler(req, res);
});
ThoughtWorks 的 Pete Hodgson 根据生命周期和使用目的将 feature toggles 分为三种主要类型。正确确定 toggle 类型有助于选择合适的存储机制和管理流程。我们来看看每种类型在移动开发中的情况。
Business toggles ——寿命最长的开关。它们管理仅对特定用户类别(高级功能、区域特性)可用的业务规则。此类 toggles 可能持续数年,并且通常比二进制开/关具有更复杂的逻辑。Release toggles ——用于隐藏未完成功能的临时开关。它们的生命周期从几天到几周不等。功能完成后,release toggle 会从代码中删除。这些 toggles 是主干开发的基础,允许开发人员在不等待整个功能完成的情况下提交到主分支。
Experiment toggles 用于 A/B 测试和逐步发布。与 release toggles 不同,experiment toggles 支持用户百分比分配和与分析系统的集成。它们可能比 release toggles 存活更长时间(长达几个月),但实验结束后也必须删除。Infrastructure toggles ——用于管理基础设施变更的开关:数据库迁移、切换到新的 API 提供商、更改缓存算法。这些 toggles 需要特别关注测试,因为它们的切换会影响整个服务的稳定性。
| Toggle 类型 | 持续时间 | 受众 | 示例 |
|---|---|---|---|
| Business | 月-年 | 按角色/区域 | 高级功能 |
| Release | 天-周 | 开发人员/QA | 未完成屏幕 |
| Experiment | 周-月 | 百分比用户 | 界面 A/B 测试 |
| Infrastructure | 天-周 | 内部 | 数据库迁移 |
尽管“feature toggle”和“feature flag”这两个术语经常互换使用,但它们之间存在概念上的差异。理解这些差异有助于为特定任务选择合适的工具并避免团队混淆。我们来看看每种方法的关键区别和应用领域。
Feature toggle ——首先是一种技术机制:嵌入应用程序代码中的二进制开关。Toggle 通过配置管理,不需要外部基础设施。Feature flag ——是一个更广泛的概念,包括管理平台:用于配置的 UI、用于集成的 SDK、使用监控、分析和审计。标志支持复杂的目标规则(按区域、版本、设备)、A/B 实验和自动删除。可以说 feature flag 是 feature toggle 的进化:团队从简单的配置开关开始,随着规模增长迁移到专门平台。
对于小团队和拥有单个服务或单体架构的项目,简单的配置 toggles 完全足够。如果您有 5-10 名开发人员和 1-2 个同时活动的 toggles——外部平台将多余。Feature flag 平台(LaunchDarkly、Unleash)在活跃标志超过 20-30 个、团队有 20 多名开发人员、或者需要对不同用户群体进行精细功能访问管理时才变得必要。对于客户端更新需要数天的移动应用程序,feature flag 平台提供了额外优势——无需发布新版本即可更改应用程序行为的能力。
选择管理 feature toggles 的工具取决于团队规模、技术栈和安全要求。我们来看看从简单配置文件到工业管理平台的选项,包括开源替代方案。
Feature toggles 应该是 CI/CD 管道的一等公民。在构建阶段,管道检查当前冲刺中计划删除的所有 release toggles 是否确实已从代码中删除。在测试阶段,使用不同的 toggle 组合运行矩阵测试。在部署阶段,系统自动将 toggle 配置与生产环境同步。与 PagerDuty 或 Opsgenie 集成可以在检测到废弃 toggles 或超过允许的活动 toggles 数量时创建警报。
对于简单场景,Git 中的 JSON 配置加上对更改进行代码审查就足够了。更高级的选项——Togglz(Java)或 Gofeature(Go)——添加了用于管理 toggles 的最小 UI 的库。对于生产系统,推荐使用具有所有语言 SDK 并支持激活策略的 Unleash(开源),或具有内置 A/B 测试的 Flagsmith。LaunchDarkly 仍然是具有高审计和合规性要求的企业项目的标准。对于移动应用程序,所有解决方案都提供带有缓存和离线模式的原生 SDK。
Feature toggles 是一把双刃剑。没有管理纪律,它们就会变成技术债务,拖慢开发速度并增加代码复杂性。根据 CodeScene(2024)的研究,35-50% 的代码库包含废弃 toggles——在发布完成后仍留在代码中的开关。我们来看看预防和消除此类债务的策略。
删除 feature toggle 的过程包括四个步骤。第一:确保 toggle 对 100% 的受众开启或对 0% 的受众关闭(取决于哪个代码分支应该保留)。第二:从代码中删除所有条件性 toggle 检查,只保留应该是生产行为的分支。第三:从存储系统(配置、数据库或平台)中删除 toggle 定义。第四:运行测试以确认删除没有破坏功能。每个 toggle 都应该有一个所有者和计划删除日期,在创建开关时确定。
在超过 50 个开关的规模下,手动审计 toggles 是低效的。自动化基于三个原则:CI 检查(废弃 toggles 的存在会阻止合并)、监控(显示每个 toggle 的存续时间和状态的仪表板)、警报(如果 toggle 在 N 天内没有变化,通知所有者)。静态代码分析工具(SonarQube、ESLint 插件)可以检测代码中始终开启或始终关闭的 toggles——这显然是废弃 toggle 的标志。最终检查是代码审查,审查者必须确保新 toggle 确实必要,并且旧代码分支将被删除。
package toggles
type Toggle struct {
Name string
Enabled bool
Owner string
CreatedAt time.Time
TTL time.Duration
}
type ToggleManager struct {
store map[string]*Toggle
}
func NewToggleManager() *ToggleManager {
return &ToggleManager{store: make(map[string]*Toggle)}
}
func (m *ToggleManager) IsEnabled(name string) bool {
t, ok := m.store[name]
if !ok {
return false
}
return t.Enabled
}
func (m *ToggleManager) GetStaleToggles() []string {
var stale []string
for name, t := range m.store {
if t.Enabled && time.Since(t.CreatedAt) > t.TTL {
stale = append(stale, name)
}
}
return stale
}
常见问题
这两个术语经常互换使用,但从技术上讲,feature toggle 是代码中的二进制开关(检查配置的 if 条件)。Feature flag 是更广泛的概念,包括具有 UI、SDK、分析和复杂目标规则的管理平台。Toggle 不需要外部基础设施,flag 通常需要。
Release toggles 应在发布完成后1-2 周内删除。Experiment toggles ——在 A/B 测试完成后立即删除。Business toggles 需要定期审计(每季度一次)。建议设置 CI 检查,如果 PR 中添加了新 toggle 但没有在任务跟踪器中设置删除任务,则阻止合并。
是的,feature toggles 在移动开发中被广泛使用。主要工具是 Firebase Remote Config,它允许在无需发布新版本应用程序的情况下动态管理开关。替代方案:适用于 iOS/Android 的 LaunchDarkly SDK、Unleash SDK、带有 REST API 的自定义 toggle 服务器。实现值缓存以支持离线工作模式很重要。
主要方法是矩阵测试:在 toggle 开启和关闭的情况下运行所有测试。对于 N 个 toggles,完整的矩阵测试需要 2^n 次运行,因此实践中会选择关键组合。单元测试应该模拟 toggle 值。集成测试检查特定场景。在 CI 中添加一个步骤,使用随机 toggle 组合运行测试以发现意外的交互。
主要风险:1) 废弃 toggles——同时包含两个分支(开/关)的代码变得复杂且难以维护;2) 测试的组合复杂度——每个 toggle 使状态数量翻倍;3) 死代码——在 toggle 永久开启后,旧分支仍保留在代码中;4) 安全性——控制访问的开关在配置错误时会产生漏洞。所有风险都可通过纪律和自动化来管理。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。