ALM Rangers

采用功能切换进行软件开发

Bill Heys

下载代码示例

作为一个软件开发概念,功能切换可让您进行并行、并发的功能开发,将其作为并行开发分支(也称为功能分支)的替代方法。 功能切换有时称为功能标志、功能开关、功能翻转器或条件功能。 通过功能切换,您可以在功能开发过程中持续集成功能。 您可以使用功能切换在运行时隐藏、禁用或启用各个功能。

与所有软件开发技术一样,您可以将功能切换与版本控制(例如 Microsoft Team Foundation Server)结合使用。 使用功能切换本身并不意味着消除全面版本控制计划中的所有分支。 功能切换的一个区别在于,所有更改都签入主分支(主线),而不是开发分支。

在您开始开发功能之前,该功能对于所有用户而言,为禁用或隐藏状态。 在开发过程中,您可以启用功能以进行开发和单元测试,而其他所有用户禁用该功能。 质量保证 (QA) 测试人员也可以启用想要测试的功能。 在完成功能开发、按照完成标准 (DoD) 进行全面测试并通过质量测试之前,已发布的软件中将隐藏或禁用该功能。 您无需创建和部署新的版本即可动态更改功能切换的值。

我将从功能分支和功能切换的比较开始讨论,推荐在代码中实施功能切换的替代技术。 我将通过使用示例 Windows 计算器应用程序,介绍如何使用功能切换在运行时隐藏或显示功能。

持续集成

通过使用功能切换,您可以进行持续集成 - 所有开发工作都签入主分支,并持续与该分支中的所有其他代码集成。 每次签入时,将使用自动版本验证测试 (BVT) 在生成服务器上生成主分支中的代码并进行测试。

正如有关持续交付的 Microsoft 模式和实施方案书籍(“使用 Team Foundation Server 2012 构建发布管道”[2013])中所述,“持续交付是指,通过版本控制、持续集成、自动化及环境管理等技术,您将能够缩短从初具想法到想法实现为生产中软件的时间”(bit.ly/1kFV0Kx)。

您可以使用自动化单元测试来测试您的代码,然后再签入主分支。 如果签入中断了生成服务器上的生成过程,您将需要首先修复代码,然后才允许签入。 如果生成的某个功能未通过 QA 测试,您可以将其隐藏以免部署或发布,直至通过测试。

功能切换可对正在开发的功能进行运行时隔离,持续至功能开发完成、通过测试且准备发布。 通过将功能切换与持续集成结合使用,您可以直接在主分支中工作。 代码将签入主分支,同时保持此分支能够稳定地进行生成和部署工作(请参见图 1)

Feature Toggles Support Continuous Integration of Parallel Development
图 1 功能切换支持并行开发的持续集成

使用中的功能切换

图 2 显示了功能切换在示例 Windows 计算器窗体中运行的示例。 此图还说明了开发并行并发功能所面临的挑战。

Sample Windows Calculator Showing Three New Features
图 2 显示三种新功能的示例 Windows 计算器

此示例将使用功能分支管理并行开发的情况与使用功能切换管理并行开发的情况进行了比较。 由于部署的是基本计算器,因此有三种新功能(小键盘、高级函数和内存函数)正在并行开发。

功能切换模式

有大量使用模式可让您在其中使用功能切换,从而允许持续集成所有代码,包括待处理或不完整的代码。 使用功能切换可以提高团队的开发速度,因为它能够减少或消除对并行开发分支及随后分支和合并任务的需要,该过程会非常耗时且容易出错。

以下是可以考虑使用功能切换的典型方案列表:

  • 在 UI 中隐藏或禁用新功能
  • 在应用程序中隐藏或禁用新组件
  • 对接口进行版本控制
  • 扩展接口
  • 支持组件的多个版本
  • 将新功能添加到现有应用程序
  • 增强现有应用程序中的现有功能

功能隔离分支

通过功能分支策略,您的团队可以将特定功能(或功能组)的并发或并行开发隔离到单独的功能分支中。 就何时将发布转为生产而言,功能分支提供了很大的灵活性。 您不必等到所有功能都准备就绪才进行完整部署。

您可以在按照 DoD 完成各个功能或功能组后,将其轻松合并到主分支中。 您可以根据需要创建新的功能分支,并在功能完成后将其立即删除。 直到功能符合 DoD 标准后,才会合并到主分支中。 在下面的示例中,图 3 显示了在并行开发过程中提供隔离的三个新功能分支。

Branching for Feature Isolation
图 3 功能隔离分支

有关各种功能分支方案的更多指导,您可以参阅 ALM Rangers 电子书中有关分支策略的内容,位于 aka.ms/vsarsolutions

功能切换与 功能分支

通过使用功能切换,您和您的团队可以采用持续集成、持续部署及持续发布实施方案。 与功能分支相比,这些实施方案允许在并行开发时更频繁地进行功能代码集成。 同时,功能切换还支持在功能正在开发且尚未准备发布时对其进行运行时隔离。

采用功能切换时,所有待处理更改都会签入主分支。 每次签入都会被挂起,直到自动生成流程生成来自生成服务器上主分支的所有代码,并成功运行自动化 BVT。 此过程称为持续集成。

运行时,功能切换会隐藏或跳过正在生成但尚未准备发布的功能。 根据您计划实施功能的方式,使用功能切换所需的工作有时会极其复杂。 另一方面,从 UI 中隐藏功能会很简单。

使用功能分支时,所有开发都签入关联的功能分支,并与主分支或其他功能分支中的代码隔离。 在功能完成代码、通过所需质量测试或符合 DoD 标准之后,您才能将新增或增强的功能与主分支或其他功能分支合并。 只有在此时,功能才会与主分支集成(合并)。 因此,代码隔离和持续集成在某种程度上与“集成范围”完全相反。

在部署中启用新功能相当简单且没有风险。 只需激活关联的功能切换,即可使其可见并启用。 使用功能分支发布新功能的工作要复杂得多。 您需要将功能合并到主分支中。 这通常是一个既耗时又艰巨的过程。

删除功能

在某些项目中,您需要删除或取消未完成的功能。 回滚各个变更集以回滚某个功能(随意选取的更改)也很复杂、费力,并且可能需要大量代码重写。

很有必要将功能与主分支隔离,直至决定发布这些功能。 无论如何,在安排发布前一刻取消发布某个功能都会有风险且容易出错。 通过使用功能切换,您可以将所有功能的所有代码都签入主分支中。 功能切换只会从运行时或发布中隐藏或禁用所选功能。

实现选项

有多种方法可保持应用程序功能切换的状态。 下面是两种非常简单的方法:

  1. 使用“应用程序设置”定义功能切换(构建应用程序时部署在 XML 应用程序配置文件中)。
  2. 将功能切换保存为数据库表中的行。

定义功能切换 不仅可以使用设置设计器来更轻松地在配置文件中定义功能切换,而且您无需在应用程序中执行任何操作即可在应用程序结束时保存功能切换的状态。 图 4 中显示的示例设置文件定义了功能切换。

This Sample ConfigSettings File Stores Feature Toggle State for the Windows Calculator
图 4 此示例 ConfigSettings 文件存储了 Windows 计算器的功能切换状态

将功能切换保存为数据库行 若要使用功能切换数据库表来存储功能切换状态,应用程序将需要两个设置文件。 CommonSettings 设置文件定义应用程序使用的选项。 DatabaseSettings 设置文件控制功能切换是在数据库表(如果为 True)还是设置文件(如果为 False)中进行定义和保留。 FeatureToggleSettings 文件为将保存在数据库中的每个功能切换提供名称(请参见图 5)。

Feature Toggle Names Stored in the Database Table As Rows
图 5 在数据库表中保存为行的功能切换名称

示例 Windows 计算器中使用的方法为动态创建功能切换行。 应用程序首次运行时,表为空。 运行时,将检查所有功能切换名称以查看是否已启用。 如何有任何名称尚未列出,应用程序会将其添加到表中。 图 6 中的示例 FeatureToggle 表显示了所有添加的功能切换行。

FeatureToggle Table After Initial Run
图 6 首次运行后的 FeatureToggle 表

独立于实现的概念

使用功能切换来禁用功能时,应用程序中的代码会根据与该功能关联的功能切换值 (True/False) 隐藏或禁用控件。 对于正在开发的功能,您可以选择将其完全从 UI 中隐藏,或选择将功能显示为禁用。 当您发布功能时,该功能将可见且已启用。

图 7 显示了仅小键盘功能可见且已启用的 Windows 计算器。 内存函数和高级函数功能可见,但被禁用。

Windows Calculator with Keypad Feature Visible and Enabled, and the Memory and Advanced Functions Features Visible and Disabled
图 7 小键盘功能可见且已启用、内存函数和高级函数功能可见但被禁用的 Windows 计算器。

此示例应用程序中的第一步创建了新的 FeatureToggleDemo 数据库,紧接着创建了简单的 FeatureToggle 表。 下载示例应用程序时,您将看到三个 SQL 文件,用于:创建 FeatureToggle 数据库、创建 FeatureToggle 表,以及创建应用程序用于从 FeatureToggle 表中加载值并将值保存回该表中的两个存储过程。

创建 FeatureToggle 表后,您无需在首次运行应用程序之前添加任何 FeatureToggle 行。 应用程序使用 FeatureToggle 类库来封装用于访问和更新功能切换的所有应用程序逻辑。

下面的代码显示了用于保留功能切换状态的私有字段:

private Boolean _IsKeyPadFeatureEnabled = false;
private Boolean _IsKeyPadFeatureVisible = false;
private Boolean _IsMemoryFeatureEnabled = false;
private Boolean _IsMemoryFeatureVisible = false;
private Boolean _IsAdvancedFeatureVisible = false;
private Boolean _IsAdvancedFeatureEnabled = false;

图 8 中的代码说明如何通过存储在 ConfigSettings 文件中的功能切换值设置私有字段。

图 8 存储在 ConfigSettings 文件中的功能切换值

private void GetFeatureTogglesFromSettings()
{  
  _IsKeyPadFeatureEnabled = 
    Properties.ConfigSettings.Default.KeyPadFeatureEnabled;
  _IsKeyPadFeatureVisible = 
    Properties.ConfigSettings.Default.KeyPadFeatureVisible;
  _IsMemoryFeatureEnabled = 
    Properties.ConfigSettings.Default.MemoryFeatureEnabled;        
  _IsMemoryFeatureVisible = 
    Properties.ConfigSettings.Default.MemoryFeatureVisible;
  _IsAdvancedFeatureEnabled = 
    Properties.ConfigSettings.Default.AdvancedFeatureEnabled;
  _IsAdvancedFeatureVisible = 
    Properties.ConfigSettings.Default.AdvancedFeatureVisible;
  tbMode.Text = Constants.configSettings;
}

图 9 中的代码说明如何通过存储在 FeatureToggle 数据库表中的功能切换值设置私有字段。

图 9 通过存储在数据库表中的已存储功能切换值设置私有字段

{
  _IsKeyPadFeatureEnabled = FeatureToggles.FeatureToggles.IsEnabled(
    Properties.FeatureToggleSettings.Default.KeyPadFeatureEnabled);
  _IsKeyPadFeatureVisible = FeatureToggles.FeatureToggles.IsEnabled(
    Properties.FeatureToggleSettings.Default.KeyPadFeatureVisible);
  _IsMemoryFeatureEnabled = FeatureToggles.FeatureToggles.IsEnabled(
    Properties.FeatureToggleSettings.Default.MemoryFeatureEnabled);
  _IsMemoryFeatureVisible = FeatureToggles.FeatureToggles.IsEnabled(
Properties.FeatureToggleSettings.Default.MemoryFeatureVisible);
  _IsAdvancedFeatureEnabled = FeatureToggles.FeatureToggles.IsEnabled(
    Properties.FeatureToggleSettings.Default.AdvancedFeatureEnabled);
  _IsAdvancedFeatureVisible = FeatureToggles.FeatureToggles.IsEnabled(
    Properties.FeatureToggleSettings.Default.AdvancedFeatureVisible);
  tbMode.Text = Constants.databaseSettings;
}

图 10 中的代码说明应用程序如何评估功能切换,以及如何使用包装方法设置 Windows 计算器功能的 Vsible 和 Enabled 属性。

图 10 使用包装方法设置 Windows 计算器的 Vsible 和 Enabled 属性

private void EvaluateFeatureToggles()
  {
    this.tbVersion.Text = Properties.CommonSettings.Default.Version;
    if (Properties.CommonSettings.Default.DatabaseSettings)
    {
      GetFeatureTogglesFromStore();
    }
    else
    {
      GetFeatureTogglesFeatureToggleFromSettings();
    }
    if (_IsAdvancedFeatureEnabled)
    {
      _IsAdvancedFeatureVisible = true;
    }
    SetAdvancedFeatureEnabled(_IsAdvancedFeatureEnabled);
    SetAdvancedFeatureVisible(_IsAdvancedFeatureVisible);
  }

下面的代码说明包装函数如何隐藏或显示与高级函数功能相关的 UI 元素:

private void SetAdvancedFeatureVisible(bool state)
{
  this.btnBkspc.Visible = state;           
  this.btnSqrt.Visible = state;
  this.btnPercent.Visible = state;
  this.btnReciprocal.Visible = state;
  this.btnPlusMinus.Visible = state;
}

Windows 计算器利用可重用类库来封装所有与 FeatureToggle 表关联的处理。 通过使用此类库,使用 ConfigSettings 文件的代码几乎与使用 FeatureToggle 数据库表的代码完全相同。

总结

功能切换的优势在于,新增或增强的功能会签入主分支,从而能够在生成时通过现有的基本代码对其进行持续集成和测试。 之前,在临近发布期限前,新增或增强的功能的代码通常不会与基本代码集成,这样会有风险且充满挑战性和危险。 功能切换和功能分支都是可行方法,可用于实现仅具有通过必要 QA 测试的已完成功能的稳定版本。 选择使用哪种方法取决于您的需求及准备就绪的开发流程。

Bill Heys 是设计流程及解决方案方面的资深 ALM 顾问。他之前是 Microsoft 的资深 ALM 顾问。现在是 Microsoft Visual Studio ALM Rangers 的成员、Rangers 分支和合并指南的首席开发人员,并且是 Rangers 版本控制指南发布的撰稿人。您可以通过 bill.heys@live.com 与他联系。

衷心感谢以下技术专家对本文的审阅:Michael Fourie(独立顾问)、Micheal Learned (Microsoft)、Matthew Mitrik (Microsoft) 和 Willy-Peter Schaub (Microsoft)