ALM Rangers

Softwareentwicklung mit Funktionsschaltern

Bill Heys

Codebeispiel herunterladen

Das Konzept der Softwareentwicklung mit Funktionsschaltern ermöglicht Ihnen, parallele, gleichzeitige Funktionsentwicklung als Alternative zur Verzweigung für parallele Entwicklungen zu verfolgen (auch als Funktionszweige bezeichnet). Funktionsschalter werden manchmal auch als Funktions-Bitschalter, Funktionswechsler, Funktionsumschalter oder bedingte Funktionen bezeichnet. Mit Funktionsschaltern können Sie fortlaufend Funktionen integrieren, die sich noch in der Entwicklung befinden. Sie können die Funktionsschalter zum Ausblenden, Deaktivieren oder Aktivieren einzelner Funktionen zur Laufzeit verwenden.

Wie bei allen Softwareentwicklungstechniken können Sie Funktionsschalter zusammen mit Versionskontrolle verwenden (z. B. Microsoft Team Foundation Server). Die alleinige Verwendung von Funktionsschaltern bedeutet nicht die Eliminierung aller Verzweigungen aus einem umfassenden Plan zur Versionskontrolle. Ein Unterscheidungsmerkmal von Funktionsschaltern ist, dass alle Änderungen in den Hauptzweig (Hauptlinie) statt in einen Entwicklungszweig eingecheckt werden.

Eine Funktion ist deaktiviert oder für alle Benutzer ausgeblendet, bis Sie mit der Funktionsentwicklung beginnen. Während der Entwicklung können Sie die Funktion für Entwicklungs- und Komponententests aktivieren und sie für alle anderen Benutzer deaktivieren. QA-Tester (Qualitätssicherung) können ebenfalls Funktionen aktivieren, die sie testen möchten. Bis die Funktion abgeschlossen ist und vollständig entsprechend der DoD-Definition getestet wurde und die Qualitätstests besteht, bleibt sie in der veröffentlichten Software ausgeblendet oder deaktiviert. Sie können den Wert eines Funktionsschalters dynamisch ändern, ohne einen neuen Build zu erstellen und bereitzustellen.

Beginnend mit einem Vergleich zwischen Funktionszweigen und Funktionsschaltern schlage ich alternative Techniken zur Implementierung von Funktionsschaltern im Code vor. Anhand des Beispiels einer Windows-Rechneranwendung erläutere ich, wie Sie Funktionen mithilfe von Funktionsschaltern zur Laufzeit aus- oder einblenden können.

Kontinuierliche Integration

Mithilfe von Funktionsschaltern erreichen Sie kontinuierliche Integration – alle Entwicklungen werden in den Hauptzweig eingecheckt und kontinuierlich in den anderen Code in diesem Zweig integriert. Bei jedem Einchecken wird der Code im Hauptzweig mithilfe von automatisierten Buildüberprüfungstests (BVTs) auf einem Build-Server erstellt und getestet.

Die Definition zu kontinuierlicher Bereitstellung finden Sie im praktischen Microsoft-Handbuch („Erstellung einer Versionspipeline mit Team Foundation Server 2012 [2013]“): Mit „kontinuierlichen Bereitstellungen“ meinen wir, dass Sie mittels Techniken wie Versionierung, ständiger Integration, Automatisierung und Umgebungsverwaltung die Zeit zwischen der ersten Idee und der Umsetzung dieser Idee als Software in der Produktion verkürzen können (bit.ly/1kFV0Kx).

Sie können automatisierte Komponententests durchführen, um den Code vor dem Einchecken in den Hauptzweig zu testen. Wenn beim Einchecken ein Build auf dem Build-Server nicht mehr funktioniert, müssen Sie den Code korrigieren, bevor ein Einchecken möglich ist. Sofern eine Funktion im Build den QA-Test nicht besteht, können Sie sie bis zur Korrektur aus der Bereitstellung bzw. Version ausblenden.

Funktionsschalter bieten Ihnen die Laufzeitisolierung einer zu entwickelnden Funktion, bis sie vollständig getestet und bereit zur Veröffentlichung ist. Durch die Verwendung von Funktionsschaltern mit kontinuierlicher Integration arbeiten Sie direkt im Hauptzweig. Der Code wird in den Hauptzweig eingecheckt, während dieser Zweig für Builds und Bereitstellungen stabil bleibt (siehe Abbildung 1).

Feature Toggles Support Continuous Integration of Parallel Development
Abbildung 1: Funktionsschalter unterstützen die kontinuierliche Integration zur parallelen Entwicklung

Verwendung von Funktionsschaltern

Abbildung 2 zeigt ein Beispiel für Funktionsschalter bei der Arbeit in einem Windows-Rechnerformularbeispiel. Dies veranschaulicht außerdem die Herausforderungen der parallelen gleichzeitigen Funktionsentwicklung.

Sample Windows Calculator Showing Three New Features
Abbildung 2: Windows-Beispielrechner mit drei neuen Funktionen

In diesem Beispiel wird die Verwaltung der parallelen Entwicklung mithilfe von Funktionszweigen und Funktionsschaltern verglichen. Es gibt drei neue Funktionen (Tastenfeld, erweiterte Funktionen und Speicherfunktionen), die parallel entwickelt werden, während der Basisrechner schon bereitgestellt wurde.

Funktionsschalter-Muster

Es gibt viele Nutzungsmuster, mit denen Sie Funktionsschalter verwenden können, um die kontinuierliche Integration aller Codes, einschließlich ausstehender oder unvollständiger Codes, zu ermöglichen. Die Verwendung von Funktionsschaltern kann die Entwicklungsgeschwindigkeit eines Teams erhöhen, da parallele Entwicklungszweige und die nachfolgenden, extrem zeitaufwendigen und fehleranfälligen Verzweigungs- und Zusammenführungsaufgaben, nicht mehr oder kaum noch erforderlich sind.

Dies ist eine Liste der typischen Szenarien, in denen Sie Funktionsschalter verwenden können:

  • Ausblenden oder Deaktivieren neuer Funktionen in der Benutzeroberfläche
  • Ausblenden oder Deaktivieren neuer Komponenten in der Anwendung
  • Versionierung einer Benutzeroberfläche
  • Erweiterung einer Benutzeroberfläche
  • Unterstützung von mehreren Versionen einer Komponente
  • Hinzufügen neuer Funktionen zu einer vorhandenen Anwendung
  • Erweitern einer vorhandenen Funktion in einer vorhandenen Anwendung

Verzweigung zur Funktionsisolierung

Mit einer Funktionsverzweigungsstrategie kann das Team die gleichzeitige oder parallele Entwicklung bestimmter Funktionen (oder Funktionsgruppen) in separaten Funktionszweigen isolieren. Funktionszweige bieten erhöhte Flexibilität beim Zeitrahmen bis zur Produktionsbereitschaft einer Version. Sie müssen nicht warten, bis alle Funktionen fertig sind, um eine vollständige Bereitstellung zu ermöglichen.

Sie können ganz einfach einzelne Funktionen oder Funktionsgruppen in den Hauptzweig integrieren, sobald sie laut DoD vollständig sind. Sie können bei Bedarf neue Funktionszweige erstellen und diese nach Vervollständigung der Funktion löschen. Funktionen werden erst dann in den Hauptzweig integriert, wenn sie der DoD-Definition entsprechen. In dem Beispiel in Abbildung 3 werden drei neue Funktionszweige dargestellt, die während der parallelen Entwicklung isoliert sind.

Branching for Feature Isolation
Abbildung 3: Verzweigung zur Funktionsisolierung

Ausführlichere Anleitungen zu den verschiedenen Verzweigungsszenarien für Funktionen finden Sie im ALM Rangers-E-Book zu Verzweigungsstrategien unter aka.ms/vsarsolutions.

Funktionsschalter im Vergleich zu Funktionszweigen

Mithilfe von Funktionsschaltern können Sie und Ihr Team kontinuierliche Integration, kontinuierliche Bereitstellung und kontinuierliche Versionsveröffentlichung durchführen. Diese Vorgehensweise ermöglichen häufigere Funktionscodeintegrationen, da sie im Gegensatz zu Funktionszweigen parallel entwickelt werden. Gleichzeitig unterstützen Funktionsschalter auch die Laufzeitisolierung von Funktionen, die sich noch in der Entwicklung befinden und noch nicht zur Veröffentlichung bereit sind.

Bei Funktionsschaltern werden alle ausstehenden Änderungen in den Hauptzweig eingecheckt. Jeder Eincheckvorgang bleibt ausstehend, bis ein automatisierter Buildprozess den gesamten Code aus dem Hauptzweig auf einem Build-Server erstellt und die automatisierten Buildüberprüfungstests (BVTs) erfolgreich durchgeführt hat. Dieser Prozess wird auch als kontinuierliche Integration bezeichnet.

Zur Laufzeit blenden Funktionsschalter die Funktionen, die sich zwar im Build befinden, aber noch nicht zur Veröffentlichung bereit sind, aus bzw. umgehen sie. Je nachdem, wie eine Funktion implementiert werden soll, kann der Aufwand bei der Verwendung von Funktionsschaltern übermäßig komplex sein. Andererseits kann das Ausblenden von Funktionen in der Benutzeroberfläche ganz einfach sein.

Bei der Verwendung von Funktionszweigen werden alle Entwicklungen in den zugehörigen Funktionszweig eingecheckt und vom Code im Hauptzweig oder anderen Funktionszweigen isoliert. Sie können eine neue oder erweiterte Funktion nicht mit dem Hauptzweig oder anderen Funktionszweigen zusammenführen, bevor der Code vollständig ist und die erforderlichen Qualitätstests besteht bzw. der DoD-Definition entspricht. Erst dann wird die Funktion in den Hauptzweig integriert (zusammengeführt). Deshalb sind Codeisolierung und kontinuierliche Integration in gewisser Weise die entgegengesetzten Enden des Integrationsspektrums.

Das Aktivieren einer neuen Funktion in einer Bereitstellung ist relativ einfach und risikolos. Sie aktivieren ganz einfach die zugehörigen Funktionsschalter, sodass sie sichtbar und aktiv sind. Der Aufwand zum Veröffentlichen einer neuen Funktion mithilfe von Funktionszweigen ist sehr viel komplizierter. Sie müssen die Funktion mit dem Hauptzweig zusammenführen. Dies ist ein zeitaufwendiger und häufig schwieriger Prozess.

Entfernen einer Funktion

In einigen Projekten müssen Sie unvollständige Funktionen entfernen oder zurückziehen. Ein Rollback einzelner Changesets zum Zurücksetzen einer Funktion („Herauspicken“ von Änderungen) ist ebenfalls kompliziert und arbeitsaufwendig und erfordert ggf. umfangreiche Codeüberarbeitungen.

Es ist äußerst wichtig, Funktionen vom Hauptzweig zu isolieren, bis Sie sich entscheiden, diese Funktionen freizugeben. In jedem Fall ist das Zurückziehen von Funktionen aus einer Version kurz vor der geplanten Veröffentlichung riskant und fehleranfällig. Mit Funktionsschaltern können Sie den gesamten Code für alle Funktionen in den Hauptzweig einchecken. Durch Funktionsschalter werden bestimmte Funktionen zur Laufzeit oder bei Veröffentlichung ganz einfach deaktiviert bzw. ausgeblendet.

Implementierungsmöglichkeiten

Es gibt mehrere Möglichkeiten, den Status der Funktionsschalter einer Anwendung dauerhaft beizubehalten. Dies sind zwei vergleichsweise einfache Vorgehensweisen:

  1. Verwenden Sie die Anwendungseinstellungen, um Funktionsschalter zu definieren (bereitgestellt in der XML-Anwendungskonfigurationsdatei, wenn die Anwendung erstellt wird).
  2. Speichern Sie die Funktionsschalter als Zeilen in einer Datenbanktabelle.

Definieren von Funktionsschaltern Es ist nicht nur einfacher, Funktionsschalter in der Konfigurationsdatei mit dem Einstellungs-Designer zu definieren, Sie müssen auch nichts in der Anwendung vornehmen, um den Status der Funktionsschalter zu speichern, wenn die Anwendung beendet wird. Die in Abbildung 4 gezeigte Einstellungs-Beispieldatei definiert die Funktionsschalter.

This Sample ConfigSettings File Stores Feature Toggle State for the Windows Calculator
Abbildung 4: Diese ConfigSettings-Beispieldatei speichert den Funktionsschalterstatus für den Windows-Rechner

Speichern der Funktionsschalter als Datenbankzeilen Um eine Funktionsschalter-Datenbanktabelle zum Speichern des Funktionsschalterstatus zu verwenden, benötigt die Anwendung zwei Einstellungsdateien. Die CommonSettings-Einstellungsdatei definiert die von der Anwendung verwendeten Optionen. Die DatabaseSettings-Einstellungsdatei kontrolliert, ob Funktionsschalter definiert und in einer Datenbanktabelle („True“) oder in einer Einstellungsdatei („False“) dauerhaft gespeichert sind. Die FeatureToggleSettings-Datei gibt die Namen der einzelnen Funktionsschalter an, die in der Datenbank gespeichert sind (siehe Abbildung 5).

Feature Toggle Names Stored in the Database Table As Rows
Abbildung5: In der Datenbanktabelle als Zeilen gespeicherte Funktionsschalternamen

Die im Windows-Beispielrechner verwendete Methode ist das dynamische Erstellen der Funktionsschalterzeilen. Wenn die Anwendung zum ersten Mal ausgeführt wird, ist die Tabelle leer. Zur Laufzeit werden alle Funktionsschalternamen überprüft, um festzustellen, ob sie aktiviert sind. Falls sie nicht bereits in der Tabelle enthalten sind, fügt die Anwendung sie zur Tabelle hinzu. Die FeatureToggle-Beispieltabelle in Abbildung 6 enthält alle hinzugefügten Funktionsschalterzeilen.

FeatureToggle Table After Initial Run
Abbildung 6: FeatureToggle-Tabelle nach der ersten Ausführung

Implementierungsunabhängige Konzepte

Wenn Sie einen Funktionsschalter verwenden, um eine Funktion zu deaktivieren, deaktiviert der Code in der Anwendung die Steuerelemente (bzw. blendet sie aus) basierend auf dem Wert des Funktionsschalters (True/False) für diese Funktion. Sie können die Funktion während der Entwicklung vollständig aus der Benutzeroberfläche ausblenden oder die Funktion als deaktiviert anzeigen. Sobald Sie die Funktion freigeben, wird sie aktiviert und angezeigt.

Abbildung 7 zeigt den Windows-Rechner nur mit aktivierter, sichtbarer Tastenfeldfunktion. Die Speicherfunktionen und erweiterten Funktionen sind zwar sichtbar, aber deaktiviert.

Windows Calculator with Keypad Feature Visible and Enabled, and the Memory and Advanced Functions Features Visible and Disabled
Abbildung 7: Windows-Rechner mit sichtbarer und aktivierter Tastenfeldfunktion, die Speicher- und erweiterten Funktionen sind sichtbar, aber deaktiviert

Im ersten Schritt in dieser Beispielanwendung wird eine neue FeatureToggleDemo-Datenbank erstellt, gefolgt von einer einfachen FeatureToggle-Tabelle. Wenn Sie die Beispielanwendung herunterladen, finden Sie drei SQL-Dateien für folgende Zwecke: Erstellen der FeatureToggle-Datenbank, Erstellen der FeatureToggle-Tabelle und Erstellen der beiden gespeicherten Prozeduren, die von der Anwendung verwendet werden, um Werte aus der FeatureToggle-Tabelle zu laden und wieder darin zu speichern.

Nachdem Sie die FeatureToggle-Tabelle erstellt haben, ist es nicht notwendig, FeatureToggle-Zeilen hinzuzufügen, bevor Sie die Anwendung das erste Mal ausführen. Die Anwendung verwendet eine FeatureToggle-Klassenbibliothek, um die gesamte Anwendungslogik zum Aufrufen und Aktualisieren der Funktionsschalter zu kapseln.

Der folgende Code enthält die privaten Felder, die den Status der Funktionsschalter angeben:

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

Der Code in Abbildung 8 zeigt, wie Sie private Felder aus Funktionsschalterwerten ableiten, die in einer ConfigSettings-Datei gespeichert sind.

Abbildung 8: Gespeicherte Funktionsschalterwerte in einer ConfigSettings-Datei

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;
}

Der Code in Abbildung 9 zeigt, wie Sie private Felder aus Funktionsschalterwerten ableiten, die in der FeatureToggle-Datenbanktabelle gespeichert sind.

Abbildung 9: Private Felder aus Funktionsschalterwerten aus einer Datenbanktabelle

private void GetFeatureTogglesFromStore()
{
  _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;
}

Der Code in Abbildung 10 zeigt, wie die Anwendung die Funktionsschalter auswertet und die Eigenschaften für sichtbar (Visible) und aktiviert (Enabled) für die Windows-Rechnerfunktionen mithilfe von Wrapper-Methoden einstellt.

Abbildung 10: Verwendung von Wrapper-Methoden für die sichtbaren und aktivierten Eigenschaften des Windows-Rechners

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);
  }

Der folgende Code gibt eine Wrapper-Funktion zum Anzeigen oder Ausblenden von Benutzeroberflächenelementen für die erweiterten Funktionen an:

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;
}

Der Windows-Rechner nutzt die wiederverwendbare Klassenbibliothek, um alle mit der FeatureToggle-Tabelle verbundenen Verarbeitungsschritte zu kapseln. Bei Verwendung dieser Klassenbibliothek ist der Code für die ConfigSettings-Datei fast identisch mit dem Code für eine FeatureToggle-Datenbanktabelle.

Zusammenfassung

Der Vorteil von Funktionsschaltern ist, dass neue oder erweiterte Funktionen in den Hauptzweig eingecheckt werden, sodass sie kontinuierlich integriert und mit der vorhandenen Codebasis beim Build getestet werden können. Zuvor wurde Code von neuen oder erweiterten Funktionen in der Regel erst kurz vor der Veröffentlichung in die Codebasis integriert, was riskant, schwierig und fehleranfällig war. Sowohl Funktionsschalter als auch Funktionszweige sind geeignete Vorgehensweisen, um eine stabile Version mit vollständigen Funktionen zu erhalten, welche die erforderlichen Qualitätstests bestanden haben. Welche der beiden Methoden Sie bevorzugen, hängt von Ihren Anforderungen und Ihren bestehenden Entwicklungsprozessen ab.

Bill Heys ist leitender ALM-Berater bei Design Process and Solutions. Zuvor war er leitender ALM-Berater bei Microsoft. Er ist Mitglied der Microsoft Visual Studio ALM Rangers, leitender Entwickler der Rangers-Anleitung zum Verzweigen und Zusammenführen und wirkt bei der Veröffentlichung des Rangers-Leitfadens zur Versionskontrolle mit. Sie erreichen ihn unter bill.heys@live.com.

UNSER DANK gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Michael Fourie (unabhängiger Berater)), Micheal Learned (Microsoft), Matthew Mitrik (Microsoft) und Willy-Peter Schaub (Microsoft)