Erfahrungen mit verteilter Versionsverwaltung wie Git
Tags: git, softwarearchitektur, softwareentwicklung
Kategorie Software Engineering | 1 Kommentar »
Damit dies hier nicht ein Politikblog wird, heute mal was aus dem Bereich Verwaltung großer Softwareprojekte. (Ich fürchte allerdings, vor der Bundestagswahl wird es noch einen letzten Politikbeitrag geben müssen.) Den Quelltext eines Softwareprojekts verwaltet heute hoffentlich jeder mit einer Versionsverwaltung. Inzwischen ist die Zeit der verteilten Versionsverwaltungssysteme wie Git und Mercurial angebrochen. Ich habe in den letzten Jahren mehrere größere mit Git verwaltete Projekte betreut. Zeit also für ein paar Erkenntnisse.
Unterschied zentrale und verteilte Versionsverwaltungen
Die breite Nutzung von Versionsverwaltungen in Softwareprojekten seit Mitte der 1990er Jahre dürfte einen entscheidenden Beitrag zur Produktivitätssteigerung geleistet haben. Vielleicht war es so gar die Neuerung mit dem größten Effekt. Typische auch heute noch bei Neuprojekten genutzte Vertreter sind CVS und Subversion (SVN) plus eine Vielzahl kommerzieller Varianten wie Perforce und SurroundSCM. Zwei wesentliche Unterschiede zur aktuellen Generation von verteilten Versionsverwaltungen wie Git und Mercurial sind:
- in der alten Welt werden alle Operationen durch einen zentralen Server ausgeführt
- in der alten Welt werden die Revisionen von jeder Datei einzeln gezählt
Bei den verteilten Versionsverwaltungen gibt es zwar immer noch einen zentralen Server, aber der Entwickler arbeitet zunächst lokal. Der typische Ablauf sieht folgendermaßen aus:
- Den lokalen Stand des Repository holen („pull“).
- Änderung vornehmen, Dateien ändern, anlegen oder löschen.
- Änderungen ins lokale Repository einspielen („commit“ bzw. „submit“).
- Punkte 2 bis 3 mehrfach wiederholen.
- Änderung vom lokalen Repository an den zentralen Server übertragen („push“).
Interessant dabei ist, dass man sich nicht den aktuellen Stand der Dateien holt („pull“), sondern immer eine lokale Kopie der gesamten Änderungen hat. Dadurch können zum Beispiel Vergleiche zwischen Dateiversionen lokal beantwortet werden, ohne eine Anfrage an den entfernten Server zu schicken. Eine ständige Verbindung zum Server wird somit nicht benötigt und man kann auch auf der Zugfahrt arbeiten. Das gilt auch, wenn man zwischen verschiedenen Entwicklungszweigen („branches“) umschalten will, da die gesamten Informationen lokal verfügbar sind!
Der zweite Unterschied ist aber vielleicht sogar noch wichtiger. Spielt man Änderungen in das lokale Repository ein, bekommt das gesamte Repository eine neue Revision und nicht nur die gerade veränderten Dateien. Der Bezeichner einer Revision bezieht sich somit immer auf den gesamten Stand des Repository und nicht auf einzelne Dateien. Dadurch können Änderungen in unterschiedlichen Entwicklungszweigen („branches“) leichter zusammengeführt („mergen“) werden.
Code Reviews mittels Pull Requests etablieren
Neben den Versionsverwaltungen haben sich auch die Werkzeuge drumherum erheblich weiterentwickelt. Mit Werkzeugen meine ich nicht grafische Benutzeroberflächen, denn meiner Erfahrung nach benutzen viele Entwickler nach einer gewissen Eingewöhnungsphase doch wieder die Kommandozeile :-) Ich spreche eher von Diensten wie Gitorious, GitHub und Bitbucket.
Eine der wichtigsten Erfindung dieser Dienste ist der Pull Request, der, richtig eingesetzt, einen sauberen Prozess für Code Reviews ermöglicht. Ein Entwickler arbeitet an seinen Änderungen immer in einem eigenen Zweig („branch“), den er zu Beginn seiner Arbeit abspaltet. Arbeitet er länger an den Änderungen, kann er seinen Branch mit dem aktuellen Stand auch immer wieder auffrischen. Hat der Entwickler seine Arbeit beendet, legt er einen Pull Request, meist mittels einer schicken Weboberfläche, an. Ein anderer Entwickler begutachtet nun die Änderungen in seinem Browser und kommentiert diese. Sind die Änderungen fein, übernimmt der Gutachter die Änderungen in den Hauptzweig durch einen simplen Klick auf einen „Merge“ Button. Zum Gelingen sind aber ein paar Dinge wichtig:
- Training im Vorgehen, damit jeder weiß, was Pull Requests sind und wie sie funktionieren
- funktionierendes Team, damit Kommentare im Code Review nicht als persönlicher Angriff gewertet werden
- Eskalationsmechanismus bzw. Moderation der Pull Requests, damit es bei Meinungsverschiedenheiten nicht zu einem langen Pingpongspiel kommt
- möglichst geringer Umfang von Pull Requests (der Code Review sollte nie länger als 1 Stunde dauern, da man sonst schlichtweg dabei einschläft)
- definierte Code Richtlinien, möglichst automatisiert angewendet im Bauprozess, damit im Code Review echte Probleme und nicht Formatierungsfehler gefunden werden
- Einplanung von Zeit für Pull Request Bearbeitung, da ansonsten zusätzlicher Aufwand durch Aktualisierung des Pull Requests durch den Entwickler entsteht
- Dokumentation des Vorgehens inklusive einer kleinen Checkliste, damit jeder Entwickler schnell prüfen kann, ob alles erledigt ist, bevor er seinen Pull Request abschickt
Hat man sich für Pull Requests entschieden, darf es keinem Entwickler mehr erlaubt sein, Änderungen direkt in den Hauptzweig zu übernehmen (also kein direktes „push“). Einige Werkzeuge wie Stash können dies über Rechtevergabe technisch verhindern, aber eine schlichte Arbeitsanweisung tut es meiner Erfahrung nach auch.
Jegliche Weiterentwicklung findet deshalb in möglichst kleinen Feature Branches statt. Das ist nur möglich, da die Kosten (Speicherplatz, Rechenzeit, Netzwerkverkehr) für solche Zweige bei verteilten Versionsverwaltungen wesentlich geringer sind als in den alten Systemen. Das hier beschriebene Branchingmodel inklusive Pull Requests ist allgemein als GitHub Flow bekannt.
Revisionshistorie ändern
Ein weiteres sehr geniales Feature verteilter Versionsverwaltungen ist es, dass man die Revisionsgeschichte jederzeit ändern kann. Das mag auf den ersten Blick absurd oder sogar gefährlich klingen, ist in der Praxis aber sehr nützlich. Während der Entwickler ein neues Feature bearbeitet, spielt er in kurzen Abständen seine Änderungen ein (also häufige kleine „submits“ bzw. „commits“). Ein Pull Request kann deshalb selbst bei kleinen Änderungen aus mehreren Commits bestehen. Alle diese Commits in die Historie des Hauptzweigs zu übernehmen ist meist nicht sinnvoll, da man vor lauter Commits nicht mehr inhaltlich zusammenhängende Commits erkennen kann. Deshalb wird man vor der Übernahme des Pull Requests in den Hauptzweig noch die Historie des Entwicklerzweigs so ändern, dass sie nur noch aus einem einzelnen Commit besteht, der dann die gesamten Änderungen inklusive Links auf die zugehörigen Tickets enthält.
Ein weiteres praktisches Beispiel für die Bearbeitung der Revisionsgeschichte ist, wenn man zum Beispiel die Emailadresse ändern muss, weil die eigene Firma von einer anderen gekauft wurde (seltener Fall) oder man dummerweise beim Anlegen des lokalen Repository die falsche Emailadresse konfiguriert hat (häufiger Fall).
Projektstruktur in Zeiten verteilter Versionsverwaltung mit Git
Ein typisches Subversion (SVN) Repository sieht häufig so aus:
- Projekt1/
- Branches/
- Tags/
- Trunk/
- KomponenteA/
- KomponenteB/
- KomponenteC/
- …
- Projekt2/
- Branches/
- Tags/
- Trunk/
- Projekt3/
- Branches/
- Tags/
- Trunk/
- …
Ich weiß nicht, warum sich das so etabliert hat, dass man alles und jedes in ein einzelnes großes Repository schmeißt. Neben dem Quelltext finden sich dann häufig auch noch die Marketingdokumente in irgendeinem Pfad und alle möglichen anderen Werkzeuge. Vermutlich war es in der Vergangenheit einfach zu teuer (Zeit und Administration) ein neues Repository anzulegen. Wenn man den Schritt hin zu verteilter Versionsverwaltung geht, sollte man auch gleich diesen Zopf abschneiden und den Inhalt über mehrere Repository verteilen. Damit das nicht zu abstrakt wird, hier die möglichen Git Repository für eine fiktive Webanwendung, bestehend aus einem Server und mehreren Clients:
- server.git – jeglicher Backend Code, also zum Beispiel Geschäftslogik und Persistenz, den Clients zur Verfügung gestellt lediglich über eine REST Schnittstelle
- webclient.git – Code für die Weboberfläche
- ios.git – Code für die coole iPad oder iPhone App
- android.git – Code für die erfolgreichere Android App :-)
- tools.git – Code von internen Hilfswerkzeugen, etwa für Bauprozess, DevOps Skripte, etc.
Natürlich kann es sein, dass man den Servercode über mehrere Repository verteilt, zum Beispiel wenn das Backend aus verschiedenen einzeln deploybaren Diensten wie Suchkomponente, REST Schnittstelle und Geschäftslogik, Job Queue, usw. besteht.
Gibt es Abhängigkeiten zwischen den Komponenten, so dass eine Komponente eine andere Komponente als Bibliothek benötigt, muss man die Komponenten entweder in einem Repository belassen oder sich mit dem Thema Artefaktmanagement (etwa Sonatype Nexus) beschäftigen. Artefaktmanagement ist ein Thema für sich, was eines weiteren Blogbeitrags bedarf :-)
Die Vorteile von kleinen Repository liegen klar auf der Hand:
- man holt sich nur die Repository, mit denen auch wirklich arbeitet
- das tägliche Update vom Server („pull“) ist überschaubarer und interessanter
- Rechteverwaltung im Repository kann wegfallen, da man zum Beispiel dem freiberuflichen iOS Entwickler nur Zugriff auf das Repository mit der iPad oder iPhone App gibt
- das Zusammenführen von Zweigen geht schneller, da potenziell weniger Dateien betroffen sind
- es gibt keine verwaisten Bereiche im Repository, um die sich niemand kümmert
- man kann Architekturentscheidungen wie Quelltextabhängigkeiten schon auf Ebene der Repository erzwingen
- der Einsatz unterschiedlicher Bausysteme wie Gradle für das Java Backend und Brunch für den Ember.js Webclient sind sinnvoll möglich
Die weichen Faktoren
Das Arbeiten mit einer verteilten Versionsverwaltung erfordert viel Umdenken bei Entwicklern, die in der alten Welt groß geworden sind. Das kann teils zu starker Ablehnung führen. Auch darf man nicht vergessen, dass unter Umständen ganze Abteilungen sich in ihrem Existenzrecht bedroht fühlen, etwa der bisherige CVS/Subversion/Perforce/Surround Administrator. Bei Absolventen hingegen stößt man eher auf Unverständnis, wenn diese sich noch mit der alten Welt auseinandersetzen sollen. Der Einsatz von Git kann deshalb durchaus zu einem höheren Coolness-Faktor beitragen, der die Firma für junge Mitarbeiter interessanter macht. Wie immer bei Umstellungsprozessen und Wandel ist es deshalb wichtig, alle Betroffenen ins Boot zu holen, gleichzeitig aber auch den Rückhalt beim entsprechenden Entwicklungsleiter zu haben.
Meine Erfahrung zeigt aber, dass sich die meisten Mitarbeiter schnell an den neuen Ablauf gewöhnen und diesen auch gut finden. Schließlich freuen wir Techies uns alle, wenn wir endlich eine halbwegs schlüssige Begründung gefunden haben, endlich mal ein neues Tool oder Gadget einsetzen zu dürfen. In diesem Sinne hoffe ich, dass mein Erfahrungsbericht genügend Argumente dafür geliefert hat :-)
[…] habe Erfahrung mit verteilter Versionsverwaltung à la Git? […]