Agiler Projekteinsatz

Software-Entwicklung: Einsatz eines Versionsverwaltungssystems in der Praxis

17.12.2014 von Markus Heinisch
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:

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:

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:

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.

Ausgewählte Strategie mittels Versionsverwaltungssystem

Im agilen Entwicklungsumfeld hat sich in der Praxis eine Kombination aus "Develop on Mainline" und "Branch for Release" bewährt. Diese unterstützt einen CI-Prozess optimal und eignet sich bestens für die typische Aufteilung der Entwicklung in kleine Teilaufgaben, die nur einige Stunden bis wenige Tage dauern. Die regulatorischen Anforderungen an die Entwickler sind einfach und die Einhaltung der notwendigen Richtlinien für alle Beteiligten verhältnismäßig einfach - agile Entwicklung und CI-Prozess ergänzen sich also bestens. Durch gezielte Vermeidung unnötiger Merging-Vorgänge, die in den beiden anderen Strategien notwendig sind, lassen sich Entwicklungstermine zudem deutlich besser planen. werden. Der Aufwand für umfangreiche und komplexe Merging-Vorgänge, die bei "Branch by Feature" und bei "Branch by Team" entstehen, können nur schlecht vorhergesagt werden. Anhand von Subversion wird die Verwendung von Branches, Tagging und Merging vorgestellt.

Ablauf: Beispiel für die Umsetzung der "Develop on Mainline" und "Branch for Release"-Strategien.
Foto: Trivadis AG

Das Bild zeigt eine mögliche Release-Strategie mit zwei parallelen Release-Branches. Die Entwicklung arbeitet an einer Version 2.1 auf dem Trunk. Der Tag "1" markiert einen erfolgreichen Build, der als Branch für einen Release 2.1 genutzt werden kann. Dieser kann beispielsweise nach einem erfolgreichen Build des CI-Servers erzeugt werden. Dieser Build erfüllt alle Qualitätskriterien für einen Release 2.1. Da auch alle notwendigen Features für Version 2.1 bereits implementiert sind, kann ein Branch auf der Basis des Tags "1" gezogen werden. Mit Branch 2.1 werden nur noch Bugfixes durchgeführt.
Alle Änderungen in dem Branch werden zeitnah (am gleichen Tag) in den Trunk übertragen (siehe rote Pfeile). Bugfixes aus dem Trunk in den Branch werden nur übertragen, wenn sie unumgänglich sind (siehe gestrichelte rote Pfeile). Der Grund: Es besteht die Gefahr, dass neue Features ungewollt aus dem Trunk in den Branch übertragen werden. Mit dem Tag "R" wird ein Revisionsstand in Subversion als Release markiert. Üblicherweise wird im Namen des Tags der exakte Release gekennzeichnet, wie "Rel. 2.1". In diesem Branch können in schneller Folge neue Releases erstellt und mit einem Tag markiert werden, wie beispielsweise Release 2.1.1. Solange ein Release aus diesem Branch im Einsatz ist, lassen sich hier Bugfixes erstellen und neue Releases produzieren. Die Tags markieren jedoch auch die Version, die per Rollout bei den Kunden zum Einsatz kommt.

Im Trunk wird nach den 2.1-Branching an der Version 2.2 gearbeitet, sprich neue Features für diese Version entwickelt. Der Tag "2" markiert wiederum einen erfolgreichen Build, der als Release 2.2 genutzt werden kann. Jetzt wird der Branch 2.2 erzeugt und die Entwicklung kann nun den Release 2.2 in diesem Branch fertigstellen. Ein erfolgreicher Build im Branch wird als offizielles Release 2.2 markiert und ausgerollt. Sobald alle Installationen aus dem Branch 2.1 durch einen Release aus dem Branch 2.2. ersetzt worden sind, kann der Branch 2.1. als "final" markiert werden. Dort finden dann keinerlei Entwicklungsaktivitäten mehr statt. Bei diesem Vorgehen existieren innerhalb eines festgelegten Zeitraumes drei Entwicklungslinien gleichzeitig: Mainline, Branch 2.1 und Branch 2.2.

Ablage von Sourcen in einem Versionsverwaltungssystem

Als Versionsverwaltungssystem hat sich in der Praxis Subversion als geeignet erwiesen. Analog zu einem klassischen Dateisystem kann die Software zum Archivieren von Dateien mehrere Festplatten, so genannte Repositories, verwenden. Diese sind im Grunde wie eine Festplatte zu sehen, die im Dateiverzeichnisbaum eingehängt wird - jedoch eine eigene Versionshistorie besitzt. Subversion betrachtet ein Repository als eigenständige Verwaltungseinheit.

In Subversion wird der Source Code für mehrere, zum Teil sogar von einander abhängige Projekte des gleichen Entwicklungsteams verwaltet. Bevor das Entwicklungsteam mit der Code-Produktion beginnen kann, muss die Verteilung und die Struktur der Sourcen der Produkte im SVN festgelegt werden. Die Beteiligten stehen nun zunächst vor den Entscheidung, ob sie ein oder mehrere Repositories für die Verwaltung des Source Codes verwendet wollen und wie die Ablagestruktur für das Multiprojektvorhaben aussehen soll.

Ein oder mehrere Repositories?

Bei mehreren Produkten oder Projekten stellt sich die Frage, ob ein Repository für alle Produkte (Variante A) oder ob pro Produkt ein Repository (Variante B) angelegt werden soll.

Unterschiedliche Ansätze: Ein Repository für alle Produkte (Variante A) oder pro Produkt ein Respository (Variante B)?
Foto: Trivadis AG

Die Unterschiede zwischen beiden Varianten sind aus technischer Sicht eher gering: Beispielsweise werden Berechtigungen pro Repository gepflegt, die Revisionsnummern getrennt verwaltet und ein Merging von Sourcen nur innerhalb eines Repositories unterstützt.

Unterschiede zwischen den Varianten A und B:

Empfehlung: Variante A

Variante A verursacht einen deutlich geringen administrativen Aufwand als Variante B, da nur ein Repository verwaltet werden muss. Alle Produkte innerhalb des Repository besitzen zunächst die gleichen "Einstellungen" - wie beispielsweise Berechtigungen. Die Möglichkeit das Branching und Merging innerhalb des Repository über mehrere Produkte hinweg zu nutzen, bietet zudem deutlich mehr Flexibilität für zukünftige Softwareprojekte. Die konkrete Revisionsnummer für ein Produkt wird hingegen nur an wenigen Stellen benötigt - beispielsweise beim Vergleich von Revisionen (aktueller Stand vs. vorheriger Stand) oder beim Erzeugen eines Branches oder Tags. Letztlich fällt Entscheidung für Variante A: Geringer administrativer Aufwand bei größter Flexibilität.

Struktur innerhalb des Repositories

Nun muss die Ablagestruktur der Sourcen innerhalb eines Repositories festgelegt werden. In SVN gibt es bei beiden Varianten keine technischen Unterschiede. Die Variante 1 trennt die Produkte sehr anschaulich, während die Variante 2 die Eigenschaften des Versionsverwaltungssystems wie Tags und Branches betont.

Welche Struktur der Sourcen soll innerhalb eines Repositories bestehen?
Foto: Trivadis AG

Um alle Sourcen zum jeweiligen Produkt zu erhalten muss aus Sicht eines Entwicklers in Variante 1 nur das Produkt-Verzeichnis runtergeladen werden - in Variante 2 sind es drei verschiedene. Betrachtet man allerdings auch die Branches und Tags, dann ist Variante 2 möglicherweise sinnvoller, da dort der Entwickler nur den Trunk eines Produktes auswählen kann und damit die Branches und Tags Verzeichnisse nicht laden muss. Dies macht sich besonders dann bemerkbar, wenn umfangreiche Branches und Tags vorhanden sind. Auch ein weiterer Umstand spricht eher für Variante 2: Bestehen Produkte aus einzelnen Komponenten und ist deren indiviuduelle Weiterentwicklung geplant, sehen die Varianten wie folgt aus:

Was passiert, wenn zusätzliche Komponenten eingeführt werden?
Foto: Trivadis AG

Das Entwicklerteam hat die Variante 2 bevorzugt, da die Organisation der Sourcen verständlicher ist und der Entwickler den Trunk eines Produktes problemlos kopieren kann, ohne die Tags und Branches der darunter liegenden Komponenten wie cmp1 oder cmp2 automatisch zu übernehmen.

Inhalt des Repositories definieren

Spätestens nachdem das Repository angelegt und die grobe Struktur implementiert ist, stellt sich die berechtigte Frage, was im Repository versioniert verwaltet werden soll und was dort nicht hineingehört. Diese lässt sich relativ leicht beantworten: Grundsätzlich sollten alle Elemente, die zur Erstellung oder Beschreibung eines Softwareproduktes notwendig sind, im Repository abgelegt werden. Andere Bestandteile des Projekts werden nicht im Versionsverwaltungssystem abgelegt sondern in einem Content Management System (CMS). Dazu gehören Projektpläne und -Listen sowie alle weiteren Unterlagen, die nicht unmittelbar zum Entwicklungsprozess gehören. Die folgende Tabelle gibt einen Überblick über den Verwaltungsort diverser Projekt-Artefakte:

Orte zur Verwaltung von Projekt-Artefakten für die agile Softwareentwicklung

Repository

Content Management System

Source-Codes

Binäre-Auslieferungsdateien

Build-Skripte

Generierte Dateien

Konfigurationsdaten

Protokolle von Meetings

Installationsskript

Projektpläne

Release-Notes

Liste offener Punkte

Testspezifikatin und Testdaten

Risikolisten

Anforderungsdokumente

Schnittstellenverträge

Benutzerdokumentation

Architektur- und Designdokumente

Entwicklungshandbuch

Neben den oben genannten existieren weitere Artefakte, die je nach Projekt unterschiedlich behandelt werden. Dazu zählen beispielsweise:

Diese Bestandteile passen auf Grund ihrer Größe oder ihrer Änderungshäufigkeit nicht in ein Repository oder ein Content Management System (CMS). Hier bietet sich eine manuelle, dateibasierte Verwaltung auf einen Netzlaufwerk an, dass von allen Projektbeteiligen gelesen, aber nicht geschrieben werden kann. In den Release-Notes oder im Entwicklungshandbuch sollte jedoch unbedingt ein eindeutiger Verweis auf den Ablageort dieser Artefakte hinterlegt sein. Im Falle von Java oder .Net als Entwicklungsplattform können Libraries und Frameworks in speziellen Repositories, wie beispielsweise Nexus oder Nuget, verwaltet werden.

Fazit

Für den Umgang mit Versionskontrollsystemen in der agile Softwareentwicklung bietet sich eine Kombination von "Develop on Mainline" und "Branche for Release". Auf der Basis dieser Strategien kann ein erfolgreicher CI-Prozess etabliert werden: Kurze Entwicklungsaufgaben, die im agilen Vorgehen nur eine geplante Dauer von ein paar Stunden bis wenige Tage haben, unterstützen den täglichen Build der Software und fördern die ständige Verfügbarkeit der Software für Tests und Kundendemonstrationen.

B&M-Verfahren

Vorteile

Nachteile

Develop on Mainline

Passende Basis für einen CI-Prozess
Wenig Merging
Einfachheit des Verfahrens

Erhöhter Aufwand bei größeren Entwicklungsaufgaben

Branch for Release

Sehr gute Ergänzung zu "Develop on Mainline"
Mehrere gleichzeitige Releases möglich

Bei Merges on der Mainline in den Release-Branch können unbeabsichtigt Features in den Release-Branch wandern

Branch by Feature

Features werden unabhängig voneinander in eigenen Branches entwickelt
Geeignet für große Entwicklungsteams

Unpassende Basis für einen CI-Prozess
Erhöhter Merging Aufwand
Im Vergleich komplexeres Verfahren

Branch by Teams

Jedes Team besitzt eigenen Branch, somit weniger Branches als bei Branch by Feature
Geeignet für große Entwicklungsteams

Unpassende Basis für einen CI-Prozess
Erhöhter Merging-Aufwand
Im Vergleich komplexeres Verfahren