Agiler Projekteinsatz

Software-Entwicklung: Einsatz eines Versionsverwaltungssystems in der Praxis

Ziel eines jeden Entwicklungsprojektes ist ein qualitativ hochwertiges und pünktliches Release der geplanten Software. Die Wege von der Anforderung zur fertigen Applikation sind jedoch vielfältig.

In diesem Artikel wird der Einsatz eines Versionsverwaltungssystems in einem Multiprojektvorhaben thematisiert. Es handelt sich dabei im Grunde um ein Archiv, das sämtliche Änderungen an Dateien protokolliert und Entwicklern bei Bedarf ältere Versionen der archivierten Dateien zur Verfügung stellt. In der Praxis kommt dazu oft Apache Subversion (SVN) zum Einsatz. Das Tool ist unter Entwicklern sehr bekannt, frei verfügbar und zudem gut dokumentiert.

Im nachfolgend beschriebenen Projekt steht das Entwicklungsteam vor der Herausforderung, dass ein Versionsverwaltungssystem mehrere Versionen (Releases) gleichzeitig unterstützen muss. Die Gründe liegen auf der Hand: Ein oder mehrere Versionen einer Software sind beim Kunden im Produktiveinsatz. Gleichzeitig untersucht ein Quality Management Team bereits das nächste Release, während das Entwicklungsteam bereits an einer neuen Version arbeitet. Die Herausforderung in diesem Projektumfeld besteht nun darin, dass das Entwicklungsteam ein Versionsverwaltungssystem als hilfreiches Tool einsetzen kann - ohne sich jedoch selbst bei der Arbeit zu behindern.

Der Begriff Release bezeichnet einen eindeutig identifizierbaren Entwicklungsstand einer Software. Dieser wird klar definiert, damit Kunden oder Tester diese eindeutig referenzieren und mögliche Fehlerberichte der korrekten Version zuordnen können. Dies geschieht in der Praxis durch einen eindeutigen Identifier - beispielsweise durch eine Versionsnummer wie "2.1.1" oder einen Code-Name wie "Tokyo". Das Entwicklungsteam markiert damit einen Versionsstand der Sourcen im Versionsverwaltungssystem. In Subversion werden diese auch als "Tags" bezeichnet und vom Entwicklungsteam unter anderem verwendet, um schnell den passenden Source-Code zu einem Fehlerbericht parat zu haben.

Vier grundlegende Strategien

Ein modernes Versionsverwaltungssystem unterstützt die parallele Entwicklung von unterschiedlichen Softwareversionen durch sogenannte "Branches". Ein solcher "Branch" ist zunächst nichts weiter als ein weiterer Zweig im Dateiverzeichnisbaum von Subversion. Das System schlägt drei Hauptverzeichnisse vor:

  • "trunk": Sourcen für die Hauptentwicklung der Software

  • "branches": Sourcen für die parallele Entwicklung

  • "tags": Kopien älterer Source-Stände, die mit "Tags" markiert wurden

Nachfolgend wird gezeigt, wann und unter welchen Umständen ein Branch erstellt wird und wie das Ergebnis der parallelen Entwicklung wieder in den zugehörigen "trunk" übernommen werden kann (Merging). Unabhängig von dem gewählten Versionsverwaltungssystem gibt es dazu eine Reihe von Branching- und Merging-Verfahren (B&M)

1. Develop on Mainline

"Develop on Mainline" bedeutet, dass die Entwickler ihre Source Code-Änderungen fast ausschließlich auf der Mainline durchführen. Sämtliche Änderungen sind somit für alle anderen Entwickler direkt nach einem Checkin der Sourcen verfügbar. Das Verfahren vermeidet das Merging von Source Code zu definierten Projektständen, da die Mainline stets den aktuellen Source Code enthält. Damit ist sichergestellt, dass Continuous Integration stattfinden kann und somit ein Build auf der Mainline die Integration der Sourcen aller beteiligter Entwickler wiedergibt. In der Praxis zeigt sich, dass nicht jeder Build erfolgreich ist. Das Vermeiden von Branches bedeutet jedoch, dass der Aufwand zur Entwicklung von neuen Features etwas höher ausfallen kann. Diese Tatsache ergibt sich aus zusätzlichen Strategien, mit deren Hilfe sich neue Features parallel in der Mainline entwickeln lassen. Branches machen jedoch Sinn, wenn Änderungen im Branch nicht mehr in die Mainline gezogen werden (Merge) - wie beispielsweise bei einem Release.

Doch wie können größere oder komplexere Änderungen durchgeführt werden, wie lassen sich mehrere neue Features parallel entwickeln und wie wird ein Refactoring durchgeführt? Für die Erfüllung für diese Art von Aufgaben bei "Develop on Mainline" wird kein Branch angelegt, sondern die Aufgabe wird in kleine Teilaufgaben zerlegt. Diese werden nicht alle auf einmal sondern Schritt für Schritt durchgeführt, um bestehenden Source Code in der Mainline nicht zu brechen. Folgende Tipps können die Entwicklung erleichtern:

  • Eine Aufgabe mittels inkrementeller Änderungen in kleinen Schritten umsetzen

  • Neue Features bis zum Abschluss der Entwicklung nicht in der Software freischalten

  • "Branch by Abstraction" bei größeren Änderungen: Ein abstrakter Layer wird über die Teile des Source Codes gelegt, die geändert werden sollen. Damit kann die Implementierung schneller ausgetauscht werden kann

  • Lose Kopplung von Komponenten des Systems: Abhängigkeiten sind geringer und einzelne Komponenten können leichter geändert werden.

2. Branch for Release

Branch for Release" bedeutet, dass kurz vor einem Release ein Branch für den Zweck eines Releases erstellt wird. Dieses Vorgehen komplettiert die "Develop on Mainline" Strategie. Die grundlegende Idee besteht darin, dass ein Team auf der Mainline neue Features entwickelt, während ein zweites Team auf dem Release-Branch Bugfixes durchführt - ohne dadurch neue Features in das Release zu implementieren. Während nur eine Mainline des betreffenden Projektes existiert, können mehrere Branches für verschiedene Releases vorhanden sein.

Ist ein Branch erzeugt, wird im Wesentlichen der enthaltene Code nur noch getestet und das fertige Release validiert, während in der Mainline bereits an weiteren Features entwickelt wird. Dieses Vorgehen minimiert den so genannten "Code Freeze", also die Zeitspanne in der kein Code im Versionsverwaltungssystem geändert werden darf. Außerdem wird so die unabhängige Entwicklung von Features parallel zum Release gefördert.

Das Vorgehen wie folgt: Zunächst werden die Features auf der Mainline entwickelt. Ein Branch wird erst dann gezogen, wenn der Feature-Code "releaseable" ist, das heißt alle für den Release notwendigen Features sind entwickelt und wurden erfolgreich getestet. Von diesem Codestand wird ein Release-Branch gezogen. In diesem werden nur noch Bugfixes durchgeführt, die direkt in die Mainline übernommen werden (Merging). Dieses Verfahren ist zwar praktisch, eignet sich jedoch nur eingeschränkt für große Entwicklerteams. Der Grund: Aufgrund der Teamgröße und Abhängigkeiten zwischen den einzelnen Features kann nur schwer festgelegt werden, wann ein Set von Features wirklich fertig ist und ein Release-Branch gezogen werden sollte.

3. Branch by Feature

Das Verfahren "Branch by Feature" ist für große Teams gedacht, die an mehreren Features arbeiten und dabei die Mainline in einem Release-Status halten wollen. Jedes Feature wird deshalb in einem eigenen Branch entwickelt. Erst wenn es fertig ist und alle Tests erfolgreich durchlaufen hat, werden die Änderungen des Feature-Branch in die Mainline übertragen. Um dieses Verfahren erfolgreich einsetzen zu können, müssen die nachfolgenden Anforderungen erfüllt sein:

  • Jede Änderung der Mainline muss zeitnah, am besten, in jeden Branch übertragen werden.

  • Branches dürfen nur wenige Tage existieren, niemals jedoch über eine Entwicklungsiteration oder -Sprint hinaus.

  • Die Anzahl der aktiven Branches darf die der zu entwickelnden Features nicht überschreiten.

  • Es wird erst dann ein neuer Branch gezogen, wenn ein Feature in die Mainline erfolgreich übertragen wurde.

  • Ein Merge aus einem Branch darf nur durchgeführt werden, wenn das Feature erfolgreich getestet wurde.

  • Refactorings müssen sofort auf die Mainline übertragen werden, damit die Anzahl der Merge-Konflikte möglichst gering bleibt.

  • Die technische Leitung der Entwicklung ist verantwortlich, dass die Mainline releasable bleibt.

Die harten Anforderungen machen deutlich, dass "Branch by Feature" ein hohes Maß an Disziplin von Entwicklern und Management voraussetzt. Werden sie nicht eingehalten, drohen hohe Kosten durch unkalkulierbaren Aufwand beim Zusammenführen der Branches zu einem fertigen Release.

"Branch by feature" widerspricht trotz vieler Vorteile im Grund einem klassischen CI-Prozess, da die "Integration" der einzelnen Branches zu einen kompletten System nicht mehr mehrmals täglich stattfindet, sondern unter Umständen erst nach vielen Tagen.

4. Branch by Team

"Branch by Team" ist dem Verfahren "Branch by Feature" sehr ähnlich. Die Grundidee besteht darin, dass die Mainline zu jeder Zeit "releasable" ist, sprich sich zu einer funktionierenden Software kombinieren lässt. Jedes Team arbeitet an mehreren Features innerhalb eines Branch. Dieser wird in erst dann die Mainline gezogen, wenn er nach erfolgreichem Bugfixing wieder stabil ist und ein oder mehrere Features komplett implementiert wurden. Darüber hinaus wird jede Änderung an der Mainline unverzüglich in die Branches aller Teams gezogen. Mit jeder Änderung werden auch alle Unit- System/Akzeptanz-Test durchgeführt.

Vorteil diese Verfahrens gegenüber "Branch by Feature" ist, dass es deutlich weniger Branches gibt. Dafür bezieht sich die "unit of work immer auf einen kompletten Branch und nicht auf den zuletzt geänderten Source Code. Dies kann ein Nachteil sein, da keine einzelnen Änderungen - und auch kein schneller Bugfix - in die Mainline übernommen werden kann, ohne unfertige Features mit zu ziehen. Zudem verzögert sich die Integration mit der Mainline in Richtung eines "Integration Milestone", was den Prinzipien der Continuous Integration fundamental widerspricht. Bei mangelnder Entwicklerdisziplin wird das Merging von größeren und verzögerten Source-Code-Änderungen in der Praxis schnell extrem aufwendig. Ein bekanntes Risiko besteht beispielsweise darin, dass die Teams nicht konsequent den Merge aus dem Branch in die Mainline und von der Mainline in alle Branches vollziehen. Damit divergiert der Source Code der Einzel-Branches relativ schnell und das Merging wird extrem aufwändig und risikoreich - von einem Gesamtsoftwarestand kann dann nicht mehr die Rede sein.