zurück zum Artikel

Herausforderung Brownfield, Teil 2: Das Sicherheitsnetz aufspannen

Stefan Lieser, Ralf Westphal, Alexander Neumann

Um Brownfield-Projekte gemäß den Prinzipien der "Clean Code Developer"-Initiative zum Erfolg zu führen, ist es wichtig, ein Sicherheitsnetz für Softwareentwickler aus Versionskontrollsystem und Continuous Integration zu spannen.

Um Brownfield-Projekte gemäß den Prinzipien der "Clean Code Developer"-Initiative zum Erfolg zu führen, ist es wichtig, ein Sicherheitsnetz für Softwareentwickler aus Versionskontrollsystem und Continuous Integration zu spannen. Nur so nimmt man ihnen die Angst, etwa Code zu verlieren, und ermöglicht ihnen, auf Fehler angemessen zu reagieren.

Dass etwas verloren gehen könnte, ist eine große Angst bei der Entwicklung von Software. Das zeigt sich beispielsweise in Codebereichen, die auskommentiert sind. Oft werden nicht mehr benötigte Abschnitte nicht entfernt, sondern durch Kommentare nur inaktiv geschaltet. Schaut man sich den Code genauer an, stellt man häufig fest, dass er so trivialer Natur ist, dass ein Entwickler ihn jederzeit wieder neu schreiben könnte. Dahinter mag die latente Absicht liegen, effizient zu arbeiten. Schließlich möchte man Code nicht zweimal entwickeln. Also erscheint es ihm ressourcenschonend, ihn nur auszukommentieren, um ihn bei Bedarf wieder hervorzuholen.

Mehr Infos

Herausforderung Brownfield

Im Rahmen einer Artikelserie beleuchten die Initiatoren der "Clean Code Developer"-Initiative, Stefan Lieser und Ralf Westphal, die Herausforderungen an Clean Code Developer in Brownfield-Projekten.

Manchmal trauen sich zudem Entwickler nicht, einen Codeabschnitt zu löschen, weil sie nicht genau verstehen, was der Code ausführt. Deswegen löschen sie ihn nicht, sondern kommentieren ihn, in der Hoffnung, mit der Information irgendwann noch mal etwas anfangen zu können.

Trotz aller guten Absichten ist ein Manko an auskommentiertem Code, dass er letztlich eher hinderlich ist, als dass er hilft. Code, der auskommentiert ist, spielt zur Laufzeit keine Rolle, schließlich beachtet ihn nicht einmal der Compiler. Aber offensichtlich scheint er trotzdem eine gewisse Rolle zu spielen, sonst hätte der Entwickler ihn entfernt.

Stößt ein Programmierer auf auskommentierten Code, muss er ihn dennoch lesen und interpretieren. Da der Compiler den Code aber nicht mehr beachtet und ihn auch die Entwicklungsumgebung nicht interpretiert, ist die Herausforderung viel höher, ihn zu verstehen. Schon die fehlende Einfärbung syntaktischer Einheiten gestaltet es schwieriger, den Code zu lesen. Tooltips und andere Analysen stehen gar nicht zur Verfügung. Das andere Problem hängt mit der Tatsache zusammen, dass der Compiler den Code nicht mehr berücksichtigt. Das führt dazu, dass der Code unverändert stehen bleibt, während die Umgebung, in die der Code eingebettet war, sich womöglich verändert. Bereits das Umbenennen von Bezeichnern führt dazu, dass der auskommentierte Code nicht mehr mit dem Rest synchronisiert ist. So gestaltet es sich immer schwieriger nachzuvollziehen, wozu der Code ursprünglich diente.

Schließlich sorgen die Kommentare dafür, dass der Code unleserlich und damit unverständlich wird. Man erreicht das Gegenteil von dem, wozu Kommentare dienen sollen, nämlich schwer verständliche Codebereiche zu erläutern. Da die oben beschriebenen Ängste letztlich unberechtigt sind, sollten Entwickler den auskommentierten Code entfernen. Damit das in einem Team von Entwicklern nicht zu Verwirrungen führt, ist darüber vorher im Team zu sprechen.

Ein weiteres Argument für auskommentierten Code wurde allerdings noch nicht ausreichend berücksichtigt, nämlich die Tatsache, dass man den Code möglicherweise später noch mal benötigt. Es mag durchaus vorkommen, dass nicht trivialer Code auskommentiert wurde. Ihn später zu reproduzieren mag mit viel Aufwand verbunden sein. Es erscheint in manchen Fällen tatsächlich sinnvoll, solche Codebereiche zu erhalten, da es letztlich nicht zu entscheiden ist, ob der Code überflüssig oder erhaltenswert ist. Als Sicherheitsnetz lässt sich jedoch ein anderes Werkzeug als das Auskommentieren verwendet, und zwar ein Versionskontrollsystem (VCS). Durch so ein System spielt es keine Rolle, ob man ältere Codebereiche noch mal benötigt oder nicht, da sie im System grundsätzlich zu Verfügung stehen, im aktuellen Stand der Codebasis aber nicht sichtbar sind und somit dort nicht verwirren.

Ein VCS hinterlegt alle Artefakte, die man zum Erstellen eines Programms benötigt. Dadurch ist zunächst sichergestellt, dass es eine zentrale Stelle gibt, an der sich alle benötigten Artefakte zusammengefasst finden lassen. Darüber hinaus sorgt ein VCS für eine Historie. Jede Übertragung von Artefakten in das System führt dort zu einer neuen Revision des Dokuments. Die unterschiedlichen Stände sind alle reproduzierbar, sodass sich auch Teile wiederherstellen lassen, die zu einem früheren Zeitpunkt aktuell waren. Somit ist es nicht mehr erforderlich, alte Stände durch Auskommentieren zu sichern.

Neben der Reproduzierbarkeit älterer Revisionen erhalten Entwickler durch ein VCS die Option, parallel an einer gemeinsamen Codebasis zu arbeiten. Sie können gleichzeitig Dateien aus dem VCS abrufen. Änderungen an den Dateien sind ohnehin kein Problem, solange nicht zwei Entwickler an derselben Datei Änderungen vornehmen. Selbst solche Fälle sind in der Praxis meist unkritisch, da moderne Systeme wie Subversion oder Git parallele Änderungen an derselben Datei zusammenführen können.

Richtig eingesetzt wirkt der Einsatz eines VCS auch einer anderen Angst entgegen, nämlich eine Datei, die zum Projekt gehört, verlieren zu können. Solange es keine zentrale Stelle gibt, in der alle zu einem Projekt gehörenden Dateien abgelegt sind, ist zu befürchten, dass Teams die Dateien über mehrere Stellen verteilen. Daraus resultiert die Angst, womöglich eine der Stellen zu übersehen. Die zentralisierte Dateiablage sorgt dafür, dass es unzweifelhaft genau einen Ort gibt, an dem alle Dateien abgelegt sind. Das erleichtert nebenbei die Datensicherung.

Der erste Schritt bei der Arbeit an einem Brownfield-Projekt muss demnach sein, alle relevanten Dateien unter Versionskontrolle zu stellen. Ferner müssen alle an Kunden herausgegebenen Versionsstände reproduzierbar sein. Das ist notwendig, damit der Quellcode einer beliebigen Version jederzeit auf Knopfdruck abrufbar ist. Nur so ist gewährleistet, dass der Releaseprozess nicht behindert wird. Ohne einfache Reproduzierbarkeit von Versionsständen droht die Gefahr, Fehler in unterschiedlichen Versionsständen nicht reproduzieren zu können – mit der Folge, dass der Kunde die Versionen nicht erhält. Das behindert jedoch eine schrittweise Verbesserung der Codebasis. Deshalb ist jede, an einen Kunden ausgegebene Version im VCS zu markieren. Durch ein geeignetes Schema der Versionsnummern ist erkennbar, um welches Build es sich handelt. Wird genau die Versionsnummer im System verwendet, um den entsprechenden Stand des Produkts zu markieren, lassen sich alle ausgelieferten Stände ohne Weiteres reproduzieren.

Erst dadurch ist es möglich, häufiger neue Versionen herauszugeben. Es sind nicht mehr Features zu sammeln, sondern es lassen sich auch Versionen mit nur einer kleinen neuen Funktion veröffentlichen. Sicherlich sollte man es nicht gleich übertreiben und zu viele unterschiedliche Versionen in Umlauf bringen, aber die Flexibilität, das technisch zu können, ist ein wichtiger Aspekt für die Weiterentwicklung und Veränderung eines Produkts. Man gewinnt dadurch die Flexibilität zurück, in kleinen Schritten neue Funktionen zu ergänzen beziehungsweise bestehende an geänderte Rahmenbedingungen anzupassen, ohne gleich gezwungen zu sein, den gesamten Kundenstamm auf eine neue Version zu bringen.

Die Tatsache, dass alle Dateien in einem zentralen VCS hinterlegt sind, bedeutet allerdings noch nicht, dass dort tatsächlich alle relevanten Dateien abgelegt sind. Schließlich könnte es sein, dass ein Mitarbeiter eine benötigte Datei nur auf seinem lokalen Arbeitsplatzrechner gespeichert hat. Das fällt so lange nicht auf, wie er dafür zuständig ist, das betreffende Projekt auf seinem Rechner zu übersetzen. Sobald er im Urlaub ist, geht nichts mehr. Niemand weiß, was zu tun ist, um das Projekt zu übersetzen und auslieferungsfertig zu machen. Dann fürchten Entwickler oft den Aufwand: Jeder drückt sich davor, solange es eben geht, die Software zur Auslieferung vorzubereiten.

Die Vorbereitungen zur Auslieferung eines Softwareprodukts sind mehr als das bloße Übersetzen des Quellcodes. Es sind die zum Produkt gehörigen Dateien zusammenzustellen, es ist ein Set-up zu generieren, Datenbank-Skripte müssen erstellt werden. Auch Verwaltungsaufgaben fallen an: Das Produkt muss eine Versionsnummer erhalten. Sie sollte sich in allen benötigten Binärdateien wiederspiegeln. Die Quelldateien sind in der Versionskontrolle zu markieren, damit sich der Versionsstand später reproduzieren lässt. Schließlich sind vielleicht eine Vorlage für das Brennen von CDs zu erstellen oder das Produkt zum Download bereitzustellen. Solange Projekte diese Schritte jedes Mal in Handarbeit durchlaufen, kann von Produktionseffizienz keine Rede sein.

Der Integrationsprozess wird daher in vielen Projekten zu selten durchgeführt. Auch hier ist die Lösung naheliegend: Sie lautet Automatisierung. Wenn der Integrationsprozess soweit automatisiert ist, dass dafür kein Spezialwissen erforderlich ist, sondern nur jemand auf einen Knopf drücken muss, hat er seinen Schrecken verloren. Dann lässt sich der Prozess oft ausführen, weil damit kein großer Aufwand mehr verbunden ist – im Idealfall mit jeder Änderung, die ein Projekt im VCS erfährt. Schließlich ist nur so sicherzustellen, dass sich der aktuelle Stand des Projekts übersetzen und alle Teile integrieren lassen.

Damit der Integrationsprozess leicht zu automatisieren ist, ist zunächst dafür zu sorgen, dass die Projekte im VCS möglichst keine externen Abhängigkeiten haben. Damit ist gemeint, dass ein Projekt alle zur Übersetzung erforderlichen Dateien und Bibliotheken enthalten sollte. Dann reicht es aus, das Projekt aus der Versionskontrolle abzurufen, und es lässt sich übersetzen. Sind externe Abhängigkeiten erforderlich, kann die Übersetzung nur auf solchen Rechnern erfolgen, die die Abhängigkeiten unterstützen. Meist bedeutet das, dass man dort Softwareprodukte installieren muss. Das lässt sich nicht in allen Fällen vermeiden, sollte sich aber reduzieren lassen. Im Idealfall ist es nur erforderlich, die Kommandozeilenwerkzeuge der verwendeten Entwicklungsumgebung zu installieren. Bibliotheken, die man nicht im eigenen Haus entwickelt, sollten im günstigsten Fall im jeweiligen Projekt abgelegt sein. Dann gelangen sie beim Abrufen des Projekts aus dem VCS gleich mit auf die Festplatte des Entwicklers, sodass dort keine zusätzliche Installation erforderlich ist.

Das Ablegen externer Abhängigkeiten in die Verzeichnisstruktur des Projekts löst auch ein Versionsproblem: Nutzt man eine Bibliothek im Laufe der Zeit in unterschiedlichen Versionsständen, liegt in der Versionskontrolle beim jeweiligen Projekt automatisch immer die richtige Version. Somit ist das wieder eine Voraussetzung dafür, dass sich auch ältere Versionen des Produkts aus der Versionskontrolle reproduzieren lassen.

Nachdem Teams sichergestellt haben, dass möglichst alle Abhängigkeiten des Projekts in der Versionskontrolle vorhanden sind, lässt sich der Build-Prozess in der Regel leicht automatisieren. Ziel ist es, ihn mit einem Kommandozeilenbefehl ausführen zu können. Es dürfen keine interaktiven Eingriffe erforderlich sein. Das unterstützen moderne Entwicklungsumgebungen in der Regel. Bei älteren Entwicklungswerkzeugen, es geht ja schließlich um Brownfield-Projekte, kann das schon mal schwieriger sein. Meist lässt sich aber ein Weg finden, den Build-Prozess zu automatisieren.

Je nach verwendeter Entwicklungsumgebung kommen Programme wie make, msbuild und NAnt zum Einsatz beziehungsweise Zusatzprodukte wie FinalBuilder. Damit lassen sich die zur Integration des Produkts erforderlichen Schritte vollständig automatisieren. Am Ende ist es Fleißarbeit, alle Aspekte des Erstellungsprozesses zu automatisieren. Vor allem braucht das niemand neu "erfinden", es gibt genügend Literatur und Hinweise im Internet, die den Vorgang detailliert beschreiben.

Liegt ein automatisierter Build-Prozess vor, wird im nächsten Schritt ein Rechner für diesen Prozess abgestellt. Er hat keine andere Aufgabe, als bei Bedarf den Build-Prozess auszuführen. "Bei Bedarf" bedeutet mit jeder Änderung in der Versionskontrolle. Dieser als Continuous Integration bezeichnete Vorgang sorgt dafür, dass sich niemand mehr um den Build-Prozess kümmern muss. Er läuft automatisiert ab und sorgt dafür, dass zum aktuellen Stand des Projekts ein aktuelles Set-up des Projekts vorliegt. Vor allem sorgt Continuous Integration dafür, dass sich Probleme beim Build und bei der Integration sofort entdecken lassen.

Um den kontinuierlichen Integrationsprozess in Gang zu bringen, installiert man auf der Build-Maschine ein entsprechendes Programm. Dazu steht eine Fülle an Produkten zur Auswahl, oft in Abhängigkeit von der verwendeten Entwicklungsumgebung. Die Palette reicht von reinen Open-Source-Lösungen wie CruiseControl oder Hudson über kommerzielle Produkte, die mit Einschränkungen kostenlos zu verwenden sind, wie JetBrains' TeamCity, bis hin zu Produkten wie Microsofts Team Foundation Server (TFS).

Es ist wichtiger, den Continuous-Integration-Prozess so schnell wie möglich mit Leben zu füllen, statt auf das am besten integrierte Tool zu setzen. Denn die Integrationstiefe von Tools hat ihren Preis: Je leistungsfähiger ein Produkt ist, desto aufwendiger ist seine Installation und Konfiguration. Ferner führt ein hoch integriertes Tool dazu, dass immer nur eine Weiterentwicklung des Gesamtsystems möglich ist. Dagegen lassen sich beim Einsatz einzelner spezialisierter Werkzeuge die einzelnen Bausteine getrennt weiterentwickeln oder sogar austauschen. Es geht nicht darum, die eierlegende Wollmilchsau zu finden, sondern den CI-Prozess zu beginnen. Mit darauf spezialisierten Produkten ist das reine Tooling keine Hexerei und schnell erledigt. Die Hoffnung auf hohe Integrationstiefe wird jedoch häufig nicht erfüllt, weil das einen enormen Aufwand nach sich zieht.

Continuous Integration ist allerdings nicht mit der Installation und Konfiguration eines Produkts erledigt. Es muss Bestandteil des Prozesses und auch Teil der Entwicklungstätigkeit werden. Jeder im Unternehmen sollte schließlich wissen, wo er den aktuellen Stand und auch zurückliegende Stände der Softwareprodukte findet. Benachrichtigungen sollten so konfiguriert sein, dass ein Entwickler sofort erfährt, dass seine Änderungen zu einem Fehler geführt haben. Das gesamte Entwicklerteam muss in den Prozess integriert sein, damit Fehler schnell zu beheben sind. Nicht zuletzt sollte jeder Entwickler aufmerksam sein, ob Veränderungen an der Codebasis eine Anpassung des Continuous-Integration-Prozesses erfordern.

Vor allem muss jeder Programmierer wissen, warum es wichtig ist, einen defekten Build so schnell wie möglich zu reparieren. Dazu sollten die Entwickler von Anfang an bei der Einführung von Versionskontrolle und Continuous Integration beteiligt sein. Dadurch erreicht man ein tieferes Verständnis, was schließlich die Bereitschaft fördert, sich am Prozess aktiv zu beteiligen.

Die bis hier aufgebaute Infrastruktur sorgt dafür, dass sich alle Softwareprojekte bei Änderungen komplett übersetzen und integrieren lassen. An letzter Stelle steht noch die automatisierte Installation der Produkte auf Testsysteme. Zum einen lässt sich damit Zeit für Handarbeit einsparen, zum anderen ist dadurch sichergestellt, dass die Testsysteme in einem definierten Zustand sind. Das erleichtert Softwaretestern einerseits die Arbeit, da es sie von Routineaufgaben befreit. Andererseits ermöglicht es ihnen, bei ihren Tests in einem definierten Zustand zu beginnen.

Auch der Vertrieb kann von Continuous Deployment profitieren. Denn nun verliert die Kundenpräsentation ihre Schrecken: Die Frage, ob die Entwickler es auch dieses Mal schaffen, ein lauffähiges Demosystem zu installieren, stellt sich nicht mehr, wenn der Prozess automatisiert ist.

Die ersten Schritte beim Umgang mit einem Brownfield-Projekt sorgen dafür, eine reproduzierbare Umgebung zu schaffen und wiederkehrende Tätigkeiten zu automatisieren. Continuous Integration gibt eine sofortige Rückmeldung darüber, ob sich der aktuelle Stand des Projekts übersetzen und integrieren lässt. Fehlerträchtige und lästige Handarbeit wird reduziert. Das sind erforderliche Voraussetzungen für Eingriffe in den Quellcode.

Die Einführung von Versionskontrolle und Continuous Integration spannt ein Sicherheitsnetz auf, das die eingangs geschilderten Ängste unbegründet erscheinen lässt. Doch bevor Entwickler den Code modifizieren, sollten Projekte das Sicherheitsnetz noch um automatisierte Tests erweitern. Sie geben dem Entwickler die Gewissheit, dass er durch seine Änderungen nichts zerstört hat. Um das automatisierte Testen im Kontext von Brownfield-Projekten geht es im nächsten Teil der Serie.

Stefan Lieser
ist freiberuflicher Berater/Trainer/Autor aus Leidenschaft. Seine Schwerpunkte liegen im Bereich Clean Code Developer sowie Domain Driven Design.

Ralf Westphal
ist Microsoft MVP für Softwarearchitektur und freiberuflicher Berater, Projektbegleiter und Trainer für Themen rund um .NET-Softwarearchitekturen

Versionskontrolle

Continuous Integration

(ane [5])


URL dieses Artikels:
https://www.heise.de/-888901

Links in diesem Artikel:
[1] http://www.heise.de/developer/artikel/Clean-Code-Developer-in-Brownfield-Projekten-855114.html
[2] https://www.heise.de/hintergrund/Herausforderung-Brownfield-Teil-3-Das-Sicherheitsnetz-erweitern-956969.html
[3] https://www.heise.de/hintergrund/Herausforderung-Brownfield-Teil-4-Komplexitaet-bewaeltigen-durch-Differenzierung-1031414.html
[4] https://www.heise.de/hintergrund/Herausforderung-Brownfield-Teil-5-Explizite-Architektur-als-Ziel-fuer-Refaktorisierungen-1187086.html
[5] mailto:ane@heise.de