Verteiltes Arbeiten mit Git: Wie Teams die richtige Strategie finden​

Git verwirrt mit seiner Vielzahl an Optionen. Eine einheitliche Strategie im Team ist also Pflicht. Doch sie muss zu den Anforderungen im Projekt passen.​

In Pocket speichern vorlesen Druckansicht 7 Kommentare lesen
Go-Spiel

(Bild: artpage/Shutterstock.com)

Lesezeit: 18 Min.
Von
  • Simon Bortnik
Inhaltsverzeichnis

Mit der Versionsverwaltungssoftware Git hat Linus Torvalds eines der wichtigsten Werkzeuge in der Softwareentwicklung etabliert. Es schuf die Voraussetzungen für verteiltes Arbeiten an Code – auf Basis der von Torvalds definierten Prinzipien sowie der implementierten Funktionen. Wie genau das Werkzeug in der Praxis eingesetzt werden sollte, ließ er jedoch offen.

Simon Bortnik

(Bild: 

Simon Bortnik

)

Simon Bortnik hat das Bachelor- und Masterstudium in Wirtschaftsinformatik an der Technischen Universität München absolviert. Noch während seines Studiums stieß er zu msg Research und hat dort u.a. im Rahmen eines Kundenprojektes die Git-Strategie des Teams festgelegt.

Im Laufe der Jahre entwickelten sich daher verschiedene Ansätze und Vorgehensweisen, die unterschiedlich gut in einen bestimmten Projektkontext passen. Schlimmstenfalls kann sich eine ungeeignete Vorgehensweise sogar als Hindernis für ein Projekt erweisen.

Damit Entwicklerinnen und Entwickler für ihre Projekte eine fundierte Auswahl treffen können, werden im Folgenden bewährte Git-Strategien vorgestellt. Eine Gegenüberstellung ihrer Funktionen sowie ihre jeweiligen Vor- und Nachteile in unterschiedlichen Projektkontexten helfen bei der Entscheidungsfindung.

Von git pull --rebase merge über git remote update --prune bis zu git bisect start HEAD HEAD~10 bietet Git selbst bei korrekter Verwendung reichlich Potenzial zur Verwirrung. Das liegt unter anderem daran, dass die Software eine ungefilterte Bedienschnittstelle bietet: Alles, was Git technisch beherrscht, können Anwenderinnen und Anwender ohne Kontrolle auf eigene Gefahr ausführen. Erschwerend kommt hinzu, dass es meist mehrere Lösungen für ein und dasselbe Problem gibt.

Anwender müssen daher alle im Team verwendeten Lösungswege verstehen. Im Unterschied zu einer Vorgehensweise, bei der jede Entwicklerin beziehungsweise jeder Entwickler Probleme Ad-hoc selbst löst, führt das zu höherer Komplexität, mehr Fehlern und wachsendem Abstimmungsbedarf – woraus sich zwangsläufig organisatorische Probleme ergeben. Deshalb ist es unerlässlich, dass sich jedes Team auf eine gemeinsame und einheitliche Git-Strategie einigt und sich alle Teammitglieder daran halten.

Die gewählte Vorgehensweise muss sich an den Anforderungen und den zu lösenden Problemen des Teams orientieren. Entscheidend dafür ist, dass sich jedes Team zwei technischen Fragen widmet: Abbildung und Isolation.

  • Wie wird die Release-Strategie in Git abgebildet?
  • Wie isoliert ist der Produktionscode vom Entwicklungscode?

Auf der Suche nach der passenden Strategie gilt es weitere grundsätzliche Fragen zu klären: Sollen eine oder mehrere Versionen des Programms gleichzeitig zur Verfügung stehen? Soll der Produktionscode vom Entwicklungscode gar nicht, kaum, stark oder vollständig isoliert sein?

Eine Release-Strategie kann entweder vorsehen, dass immer nur eine Version des Programms zur Verfügung steht oder dass mehrere Versionen parallel zueinander existieren und unterstützt werden müssen. Ersteres findet sich meist bei Software-as-a-Service (SaaS). Bei diesen Anwendungen liegt der Betrieb in der Hand des Anbieters, der beispielsweise Kosten sparen kann, indem er nur eine Version betreibt. Typische Beispiele für solche Dienste sind etwa Facebook und GitHub.

Wenn hingegen Unternehmenskunden eine für sie erstellte Software selbst betreiben, müssen die betreffenden Entwicklungsteams häufig mehr als eine Version – oft auch veraltete – gleichzeitig unterstützen. Die Kunden wollen sich auf diese Weise in der Regel einen angemessenen Zeitraum bis zum Upgrade auf neuere Versionen sichern. Ein bekanntes Beispiel dafür ist das Datenbankmanagementsystem PostgreSQL. Die aktuelle Version ist 16. Daneben stehen aber auch die Versionen 12 bis 15 noch parat – und sind weiterhin im praktischen Einsatz. Für diese älteren Versionen erscheinen auch noch regelmäßig Bugfixes. Besonders mutige Kunden könnten sogar schon PostgreSQL 17 ausprobieren, dessen noch nicht finale Version bereits zur Verfügung steht.

Im Hinblick auf die Trennung von Entwicklungs- und Produktionscode kann es sowohl sinnvoll sein, beide kaum voneinander zu isolieren als auch eine vollständige Abgrenzung zu gewährleisten. Letzteres bietet mehr Zeit und Möglichkeit für Qualitätskontrollen. Davon profitieren unerfahrene Teams, aber auch solche, deren Produkte den höchsten Stabilitätsstandards entsprechen müssen. Damit einher geht jedoch ein erhöhter Verwaltungsaufwand, und es bleibt weniger Zeit für die Entwicklung selbst: Feature Branches müssen angelegt, über die Lebenszeit hinweg aktuell gehalten sowie anschließend wieder zusammengeführt und gelöscht werden.

Der extreme Gegenentwurf dazu verzichtet auf fast alle Kontrollen, um den Entwicklungsteams mehr produktive Zeit einzuräumen. Erfahrene Teams profitieren von einem solchen Vorgehen in der Regel durch eine höhere Auslieferungsgeschwindigkeit. Der Nachteil ist, dass ein solches Vorgehen schneller zur versehentlichen Verteilung von Codefehlern führen kann.

Abhängig von den Anforderungen in puncto Versionierung und Separierung bieten sich verschiedene etablierte Alternativen an: der GitHub Flow, der GitLab Flow und Trunk-based Development (TBD).

Die Stärken des GitHub Flow sind seine Einfachheit und dass er sowohl Continuous Integration (CI) als auch Continuous Delivery (CD) beherrscht – auf multiple, parallele Versionen müssen Entwicklerinnen und Entwickler dann jedoch verzichten.

Der GitHub Flow grenzt Entwicklungs- und Produktionscode gut voneinander ab. Ist ein Feature entwickelt, wird es in den Main-Branch integriert und ausgeliefert (Abb. 1).

Teams, die auf den GitHub Flow setzen, entwickeln Features auf einem dedizierten Feature Branch, der so lange existiert, wie das Feature entwickelt wird. Entwickler werden dazu angehalten, mindestens einmal täglich auf dem Main Branch neu aufzubauen, um auf dem neuesten Stand zu sein. Sobald ein Feature fertiggestellt ist, geht ein Pull Request (PR) mit der Aufforderung zur Genehmigung an eine festgelegte Anzahl anderer Teammitglieder. Ist das Feature freigegeben, erfolgt die Integration des Features in den Main Branch.

Da der Main Branch den produktiven Code repräsentiert, kann er als Basis für eine CI/CD-Pipeline dienen, die Änderungen sofort automatisiert ausliefert. Diese Konstellation erlaubt es allerdings nicht, mehr als eine Version der Software gleichzeitig auszuliefern, da der Main Branch stets nur die aktuellste Version des Produktionscodes repräsentiert.

Der GitLab Flow ist im Gegensatz zum GitHub Flow auf multiple, parallele Versionen ausgelegt. Bei ihm entfallen dafür Continuous Integration (CI) und Continuous Delivery (CD).

Der GitLab Flow ist auf multiple, parallele Versionen der Software ausgerichtet, trennt aber Entwicklungs- und Produktionscode ebenfalls gut voneinander ab. (Abb. 2).

Wie beim GitHub Flow arbeiten Entwicklerinnen und Entwickler auch beim GitLab Flow an einzelnen Features auf dedizierten Feature Branches. Durch Rebase auf den Main Branch lassen sich die Feature Branches aktuell halten und ausschließlich durch einen Pull Request in den Main Branch zusammenführen. Der Unterschied zum GitHub Flow besteht darin, dass sich der Stand des Main Branch zu einem beliebigen Zeitpunkt zu einer Release-Version erklären lässt. Der dadurch erstellte Release Branch repräsentiert den Code einer ausgelieferten Version. Das Entwicklungsteam kann beliebig viele neue Release Branches erzeugen, während die Arbeit am Main Branch unabhängig davon weiterläuft.

Es gibt allerdings ein Problem beim GitLab Flow: Um parallel mehrere Versionen anbieten zu können, müssen sich Bugs in einzelnen Versionen beheben lassen. Dazu stehen mehrere Optionen offen:

Die Feature-Variante: Bugfixes werden wie ein neues Feature behandelt. Sobald der Bugfix fertig ist, fließt das Feature in den Main Branch ein. Von dort übertragen Entwickler es manuell per Cherry Picking auf die einzelnen Release Branches. Das funktioniert, solange der Main und der Feature Branch sich nicht zu sehr auseinander entwickelt haben. Andernfalls würden sich die betroffenen Code-Passagen auf den zwei Branches so sehr voneinander unterscheiden, dass es beim Cherry Picking zu einem Merge Conflict käme.

Im Release Branch: Vergleichbar zur Feature-Variante kann es sich anbieten, den Bug auf dem betroffenen Release Branch zu beheben und den Bugfix anschließend per Cherry Picking in den Main Branch zu holen. Diese Vorgehensweise ist jedoch weniger empfehlenswert, denn sie birgt die Gefahr, dass Fehler unbeabsichtigt in einem Release Branch landen können.

Ein Hotfix Branch: Die sicherste Möglichkeit bietet ein Hotfix Branch, der aus dem Release Branch erzeugt wird. Auf dem Hotfix Branch können Entwicklerinnen und Entwickler den Bug beheben, ausgiebig testen und dann in den Release Branch überführen. Auf diese Weise lässt sich ein Merge-Konflikt bei dem kritischen Merge ausschließen. Anschließend lässt sich der Fix per Cherry Picking in den Main Branch überführen oder, falls der Unterschied zwischen den beiden Branches zu groß ist, als neues Feature in den zentralen Entwicklungsprozess einbinden und so in den Main Branch integrieren.

In allen drei Fällen ist es zwingend, den Bug in allen betroffenen Versionen zu beheben. Das schließt in der Regel sämtliche Versionen ein, die nach der betroffenen Version erzeugt wurden.

Trunk-based Development ermöglicht multiple, parallele Versionen und Continuous Integration (CI) sowie Continuous Delivery (CD). Es setzt allerdings Dark Launching, eine hohe Testabdeckung und hohe Qualitätsstandards voraus.

Trunk-based Development (TBD) ermöglicht nur eine niedrige Abgrenzung von Entwicklungs- und Produktionscode. Dafür erlaubt es multiple, parallele Versionen (Abb. 3).

Voraussetzung für diese Strategie ist, dass alle Entwicklerinnen und Entwickler eine konstant hohe Codequalität liefern, denn bei Trunk-based Development wird neuer Code schneller und mit weniger Kontrollen in Produktion ausgerollt.

Im Unterschied zu GitHub und GitLab Flow werden neue Features nicht isoliert auf eigenen Feature Branches entwickelt, sondern auf einer lokalen Kopie des Main Branch. Der Reviewprozess über einen Pull Request an Mitentwickelnde entfällt dabei, der neue Code fließt über ein simples git push-Kommando in die produktive Umgebung.

Kommen Release Branches zum Einsatz, unterstützt TBD – wie der GitLab Flow – mehrere, parallele Versionen eines Programms. Ohne Release Branches folgt TBD dem GitHub-Ansatz mit einer einzelnen per CI/CD ausgelieferten Version.

Da TBD nur wenige Reviews vorsieht, lastet mehr Verantwortung auf den einzelnen Entwicklern. Die TBD-Strategie sollte daher nur zum Einsatz kommen, wenn jedes Teammitglied in der Lage ist, den eigenen Code so zu gestalten, dass dabei keine anderen Features in der Anwendung funktionsunfähig werden.

Sicherzustellen, dass neuer Code nicht an einer Stelle der Anwendung zu einem Problem führt, fällt mit zunehmender Größe der Codebasis umso schwerer – ungeachtet der Kompetenz und Sorgfalt aller Beteiligten. Geeignete Rahmenbedingungen sind daher unerlässlich: Neben einer sorgfältig geplanten Softwarearchitektur – etwa durch Low Coupling und Strong Cohesion – helfen vor allem automatisierte Tests mit hoher Testabdeckung, die jedes Teammitglied schnell in der lokalen Entwicklungsumgebung ausführen kann. Nur so lässt sich mit angemessener Sicherheit bestimmen, ob Änderungen bereit für die produktive Umgebung sind.

Um täglich Updates für den Produktivcode bereitstellen zu können, müssen Entwicklerinnen und Entwickler ihr Arbeitspensum so planen, dass sie neue Pakete innerhalb der üblichen Arbeitszeit fertigstellen können. Eng damit verbunden ist zudem die Fähigkeit, sogenannte Dark Launches durchzuführen. Also Änderungen produktiv auszurollen, ohne, dass diese von den Anwendern gesehen oder benutzt werden können. Dazu lassen sich UI-Elemente wahlweise verbergen oder gesteuert via Umgebungsvariablen überspringen.

Von Feature Branches ist im Zuge der TBD-Strategie grundsätzlich abzuraten. Die Vorteile wie geringer Git Overhead oder das schnelle Einführen von neuem Code lassen sich nicht optimal nutzen oder werden gar komplett verhindert. Google geht sogar so weit, in einem Developer Blog zu fordern, dass kein Repository jemals mehr als drei Branches enthalten sollte. Von dieser Regel sind Ausnahmen möglich, wenn mehrere Entwickler eine Lösung zusammen erarbeiten müssen. Jedoch ist dies nicht als Freikarte misszuverstehen. Denn wenn einzelne Personen Arbeitspakete zu oft nicht mehr erledigen können, ist das ein klares Zeichen dafür, die einzelnen Arbeitspakete in kleinere aufzuteilen, damit sie innerhalb eines Tages zu bewältigen sind.

Um die nachfolgende Entscheidungshilfe nutzen zu können, muss ein Team sich über zwei Dinge einig sein: Wie viel Trennung zwischen Entwicklungs- und Produktionscode soll bestehen und wie viele Versionen des Produkts sollen gleichzeitig gepflegt werden?

Die maximale Anzahl an Versionen ist häufig extern vorgegeben. Der Trennungsgrad sollte aber eine Entscheidung des Teams sein. Denn nur, wenn sich alle Beteiligten mit den Verpflichtungen wohlfühlen, die eine Strategie auferlegt, kann das Team alle Vorteile der gewählten Strategie ausnutzen.

Wenn das Team aus erfahrenen Programmierern besteht, die eine Testsuite mit großer Abdeckung zur Verfügung haben, kann eine geringe Trennung sinnvoll sein. Sind hingegen oft neue Entwicklerinnen und Entwickler einzubinden, ist der Code undurchsichtig, liegen keine sinnvollen Tests vor oder lassen sich diese nicht schnell auf den Rechnern ausführen, dann ist eine höhere Trennung von Entwicklungs- und Produktionscode vorzuziehen.

Tabelle 1: Trennung von produktivem Code und Programmierer
Hohe Trennung Geringe Trennung
Multiple Versionen GitLab Flow TBD (mit Release Branches)
Einzelne Version GitHub Flow Trunk-based Development
Wähle weise: Je nach Trennungsgrad und Anzahl der gleichzeitig zu pflegenden Versionen kommt eine von drei Strategien in Frage.