Continuous Integration mit Feature Branches?
Jez Humble, Mitautor von „Continuous Delivery“, stellt gerne folgende drei Fragen, um zu vermitteln, worum es bei Continuous Integration (CI) wirklich geht:
- Checkt jeder Entwickler mindestens einmal täglich in den Mainline-Branch ein?
- Werden die Änderungen von einer Suite automatisierter Tests geprüft?
- Wenn der Build fehlschlägt, ist es die oberste Priorität des Teams, das Problem zu finden und zu lösen?
Unter Feature Branches verstehe ich kurzlebige Zweige in der Versionskontrolle, die angelegt werden, um funktionale Erweiterungen einer Software isoliert entwickeln zu können. Der Vorteil dieser Isolation ist, dass Neuentwicklungen unabhängig voneinander ersten Qualitätskontrollen (etwa einem Code Review) unterzogen werden können, bevor sie schließlich integriert werden.1 Ganz ähnlich erlauben auch verteilte Versionskontrollsysteme wie Git durch private Repositories eine Isolation der Arbeit des einzelnen Entwicklers.
Im oben erwähnten Buch merken die Autoren an, dass eine gewisse Spannung zwischen der Verwendung von Feature Branches in einem Entwicklungs-Workflow und CI-Prinzipien besteht:
„If different members of the team are working on separate branches or streams, then by definition they’re not continuously integrating.“
Feature Branches sind konzeptionell ein Widerspruch zu CI, auch wenn sie in der Praxis durchaus weniger als einen Tag existieren können, bevor sie in die Mainline zurückintegriert werden. Ich denke, sie machen es zu leicht, dem positiven Design-Druck auszuweichen, der durch CI auf die Software und ihre Build-Pipeline ausgeübt wird.
CI und Design
Um CI zu praktizieren sollte ich einmal täglich meine Arbeit in die Mainline integrieren, damit sich meine Team-Kollegen mit mir synchronisieren können.2 Das ist allerdings ein Mindestwert. Mit etwas Übung sind durchaus auch vier bis fünf Integrationen pro Tag und Entwickler möglich.
Um diese Integrationsfrequenz zu erreichen, muss man nicht nur als Entwickler sein Handwerk verstehen, die Code-Basis muss auch entsprechend gut modularisiert und verständlich sein. Fragiler Spaghetti-Code lässt sich nur schwer inkrementell entwickeln.
Um schwierigere Umbauten in kleinere Teile zu zerlegen, bieten sich Patterns wie Branch-by-Abstraction oder (wenn unvermeidbar) Feature Toggles an. Wenn ich einfach keine Zerlegung in ausreichend kleine Teilschritte finde, legt sich die Frage nahe, wie das Design an dieser Stelle verbessert werden kann. Nachdem ich den Code sowieso gerade ändern muss, ist das der ideale Zeitpunkt für Refactoring. Frei nach Kent Beck: „First make the change easy, then make the easy change.“
Sicherlich ist es manchmal legitim, sich gegen nötige Design-Verbesserungen zu entscheiden (etwa aus Termindruck). Wie jede technische Schuld, die ich eingehe, will ich diese Entscheidung allerdings bewusst und unter Abwägung der Alternativen treffen können.
Feature Branches führen dazu, dass die Wahrnehmung eines Design-Problems auf die abschließende Rückintegration verlagert wird. Während der Behebung von Merge-Konflikten haben allerdings die wenigsten von uns ausreichend Zeit und Motivation, um noch nötige Design-Verbesserungen einzubringen.
CI und Test Performance
CI setzt voraus, dass automatisierte Tests in ausreichend hoher Geschwindigkeit ausgeführt werden. Läuft die Testsuite im Commit Stage eine Stunde, können an einem regulären Arbeitstag acht Entwickler maximal einmal integrieren – perfekte Abstimmung der Entwickler untereinander vorausgesetzt. Idealerweise sollte jede Entwicklerin nicht länger als zehn Minuten auf Testergebnisse warten müssen. Ich vermute, es ist unwahrscheinlich, dass jemand länger wartet, egal wie lange die Tests laufen.
Diese Performance-Anforderungen erzeugen einen erheblichen Design-Druck. Langsame Einzeltests will ich um praktisch jeden Preis vermeiden. Tests, die keinen oder einen zu geringen Sicherheitsgewinn bringen, müssen zumindest aus dem Commit Stage entfernt werden. Ineffizienter Test-Code muss optimiert, die Anzahl an integrierenden Tests muss minimiert werden. Insgesamt sollte an eine gute Suite automatisierter Tests gleich hohe Qualitätsanforderungen gestellt werden wie an den Code, den sie testet.
Anforderungen an Tests machen auch nicht vor dem getesteten Code halt. Wenn durch das Design der Software effiziente Tests unmöglich sind, dann muss ich das Design ändern, um die Testbarkeit zu erhöhen. Remodularisierung der Architektur oder der Einbau von speziell für Tests entworfenen APIs sind für diesen Fall durchaus angemessene Vorgehensweisen.
Feature Branches verleiten uns dazu, diesem Druck auszuweichen. Zum einen hat das Ausführen der Tests auf einem Feature Branch von vornherein geringeren Wert, da nicht gegen das integrierte Endprodukt getestet wird. Zum anderen zwingt mich niemand, meinen Feature-Branch jede Stunde zu testen. Ist die Ausführung der Tests erst auf den Zeitpunkt der Rückintegration verschoben, ist es wesentlich einfacher, längere Laufzeiten zu tolerieren.
Trade-Offs
Feature Branches finden sich nicht ohne Grund im Workflow vieler Entwicklungsteams. Das Konzept ist intuitiv und damit sehr einsteigerfreundlich. Einen eigenen Entwicklungszweig zu haben kann ein Gefühl von Sicherheit vermitteln. Ich kann dann zwar Mist bauen, aber wenigstens ruiniere ich nicht die Arbeit der anderen, bis ich integriere. Oft wird in einem solchen Workflow die Integrationsarbeit an einen erfahreneren Entwickler übertragen und gleichzeitig mit einem Code Review verknüpft.
Für Teams, die häufiger neue, noch unerfahrene Mitglieder einbinden müssen, sind Feature Branches sicher eine gute Sache. Ebenso für Teams, die andere Arten von „Vier Augen“-Qualitätssicherung, etwa häufiges Pair Programming, für sich ausschließen.
Wir sollten auch nicht aus den Augen verlieren, das CI ursprünglich konzipiert wurde, um das kritische Risiko einer mehrmonatigen Endintegration zu vermeiden. Verglichen damit sind die Integrationsrisiken von Feature Branches in einer ausreichend großen, gut modularisierten Code-Basis minimal. Wer in so einer Situation also nicht kontinuierliche, sondern „nur“ gelegentliche bis häufige Integration betreibt, ist bereits in einer sehr guten Position.
Ich denke aber, Feature Branches bringen die Gefahr mit sich, dass ein Team nicht CI nach der eingangs angeführten, „extremen“ Definition betreibt. Stabile und erfahrene Teams bringen sich damit um einen Teil des positiven Einflusses, den CI auf die Qualität der von ihnen entwickelten Software haben kann. Schade, wenn man berücksichtigt, welcher Aufwand oft schon in Infrastruktur und Automatisierung steckt, um CI zu ermöglichen.
Danke an Mathias Köhrer, Sebastian Dellwig und Stefan Kahlhöfer für ihr hilfreiches Feedback, sowie an die Kollegen aus dem NovaTec CI/CD-Forum für die spannende Diskussion.
- Häufig wird auch die Möglichkeit, parallel zu entwickeln als Vorteil angeführt. Das ist aber etwas zu kurz gedacht, da die scheinbare Unabhängigkeit zur Entwicklungszeit durch ein erhöhtes Risiko bei der Integration erkauft wird.↩
- Es ist nicht ausreichend, die Mainline in den Feature Branch zu integrieren. Meine Änderungen sind dann zwar gegen die meiner Team-Kollegen integriert, umgekehrt aber nicht die der Team-Kollegen gegen meine.↩
Aktuelle Beiträge



Artikel kommentieren