Clean Code Developer in Brownfield-Projekten

Wird ein Projekt "auf der grünen Wiese" gestartet, kann ein Team die Prinzipien der "Clean Code Developer"-Initiative von Anfang an einsetzen. Doch was ist zu tun, wenn ein Projekt längst läuft? Wo liegen die Herausforderungen bei solchen Brownfield-Projekten?

Lesezeit: 18 Min.
In Pocket speichern
vorlesen Druckansicht
Von
  • Stefan Lieser
  • Ralf Westphal
  • Alexander Neumann
Inhaltsverzeichnis

Wird ein Projekt "auf der grünen Wiese" gestartet, kann ein Team die Prinzipien und Praktiken der "Clean Code Developer"-Initiative von Anfang an im Alltag einsetzen. Das ist oft Herausforderung genug. Doch was ist zu tun, wenn ein Projekt längst läuft? In diesen sogenannten Brownfield-Projekten wurde möglicherweise gegen einige der Prinzipien verstoßen. Wo liegen jetzt die Herausforderungen?

Die Qualität heutiger Software ist oft schlecht. Nicht dass Entwickler keine Funktionen ausliefern, nein, die innere Qualität der Implementierung ist gemeint. Das lässt sich nicht immer mit Zeit- oder Budgetdruck erklären. Oft ist es die innere Struktur der Software, die nicht angemessen ist. Das ist auf allen Ebenen zu beobachten: Es mangelt an einer angemessenen Architektur, Komponentenorientierung fehlt meist ganz, und selbst bei den kleinen Einheiten wie Klassen und Methoden werden grundlegende Prinzipien nicht beachtet. In der Folge sind vor allem Änderungen und Ergänzungen an solcher Software enorm teuer, und die Fehlerrate bleibt über einen langen Zeitraum hoch.

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.

Die "Clean Code Developer"-Initiative ist angetreten, daran etwas zu ändern. Sie hat dazu ein Wertesystem aufgestellt, das zu mehr Softwarequalität führen soll. Die Werte sind Evolvierbarkeit, Korrektheit, Produktionseffizienz und Reflexion.

Evolvierbarkeit: Bei ihr geht es um die Frage, wie leicht sich eine Software an geänderte Rahmenbedingungen anpassen beziehungsweise erweitern lässt. Oft setzt man das mit dem Begriff Wartbarkeit gleich. Wartung gibt es bei Software im engeren Sinne jedoch nicht. Nur beim Betrieb von Software kann man von Wartung sprechen. Es lässt sich beispielsweise vorausschauend sicherstellen, dass eine Datenbank immer über ausreichend freie Kapazität verfügt. In der Entwicklung der Software ist der Begriff Wartung jedoch nicht angemessen. Die Analogie zur Wartung, die man aus der Mechanik kennt, greift hier nicht: Regelmäßig nach dem Ölstand zu schauen funktioniert in der Software nicht.

Da Software sich nicht in einem großen Wurf entwickeln lässt, sondern ein iteratives Vorgehen unabdingbar ist, ist die Evolvierbarkeit schon während des Erstellungsprozesses erforderlich.

Korrektheit: Selbstverständlich muss Software korrekt sein – und das in funktionaler und nichtfunktionaler Hinsicht. Funktionale Anforderungen beziehen sich auf die eigentlichen Funktionen einer Software. Hinzu kommen die nichtfunktionalen wie Anforderungen an Laufzeitverhalten oder sparsamer Umgang mit Ressourcen. Sind alle umgesetzt, ist die Software korrekt.

Das scheint so selbstverständlich, dass es selten hinterfragt wird. In der Praxis ist die Korrektheit jedoch ein großes Problem. Vor allem nach Änderungen oder Ergänzungen treten häufig Probleme auf, die in vorhergehenden Versionen schon überwunden galten.

Produktionseffizienz: Am Ende muss der Kunde alle Anstrengungen, die Entwickler zur Einhaltung der Werte Evolvierbarkeit und Korrektheit unternehmen, bezahlen. Deshalb sind alle Schritte, die zur Einhaltung der Werte führen, effizient durchzuführen. Der Begriff Produktionseffizienz zielt darauf ab, den gesamten Prozess der Softwareerstellung so effizient wie möglich durchzuführen, ohne dabei die anderen Werte zu vernachlässigen. Damit ist die Produktionseffizienz ein Gegenpol zu den anderen Werten, der dafür sorgt, dass man am Ende Zeit und Kosten angemessen berücksichtigt.

Reflexion: Bei der Softwareentwicklung geht es oft um das Abwägen von Alternativen. Um auswählen zu können, müssen Letztere bekannt sein. Nach der Entscheidung für eine der Alternativen muss man zurückschauen, um zu prüfen, ob die Entscheidung die richtige war. Ohne diese Reflexion gibt es keine Weiterentwicklung. Selbst das Überprüfen der Ergebnisse eines Testlaufs bedeutet Reflexion. Auch sie ist notwendig, um herauszufinden, ob die Software korrekt ist. Sie sollte damit auf allen Ebenen der Softwareentwicklung anzutreffen sein.

Da die vier Werte zu abstrakt für eine direkte Umsetzung sind, hat die "Clean Code Developer"-Initiative mehr als 40 Bausteine zusammengetragen, die in Prinzipien und Praktiken unterteilt sind. Jeder Baustein trägt etwas zu einem der Werte bei. So unterstützt beispielsweise das Prinzip "Don't Repeat Yourself" (DRY) die Evolvierbarkeit, weil Code, der nur einmal vorkommt, sich leichter anpassen lässt als mehrere, möglicherweise sogar noch leicht modifizierte Kopien. Ein Praxisbeispiel ist das Refaktorisieren, das auch die Evolvierbarkeit unterstützt. Kein Entwickler ist in der Lage, beim ersten Herunterschreiben sofort den optimalen Code abzuliefern. Das Refaktorisieren dient also dazu, Code immer wieder zu verbessern.

Bei Greenfield-, also auf der "grünen Wiese" gestarteten Projekten sind die Prinzipien von Clean Code Developer bereits von Anfang an einzusetzen. In sogenannten Brownfield-Projekten wurde möglicherweise gegen einige der Praktiken verstoßen. Welche Strategien stehen nun zur Verfügung, um auch solche Projekte so zu verbessern, dass sie den Werten der Initiative entsprechen? Ist es überhaupt sinnvoll zu versuchen, im Nachhinein Projekte zu verbessern?

Über einen längeren Zeitraum entwickelte Softwaresysteme kämpfen in der Regel mit einem grundlegenden Problem: Die innere Struktur wird immer schlechter. Man spricht sogar davon, dass der Code anfängt zu verrotten, oder vergleicht den Effekt mit der Entropie aus der Physik. Natürlich verrottet der Code nicht, aber die Effekte sind vergleichbar. Das pure Hinzufügen von Code reicht nämlich nicht aus, zwischendurch ist Unkraut zu jäten.

Mit jedem Versuch, eine Änderung an der Software vorzunehmen oder eine Funktion zu ergänzen, wird die Situation schlimmer: Der Code beginnt förmlich zu stinken. Die innere Qualität verschlechtert sich, in der Folge dauert es immer länger, Funktionen zu ergänzen.

Das Problem gestaltet sich sogar dramatischer, als man zunächst vermuten mag. Es verhält sich nicht linear, sondern exponenziell. Am Anfang sind die "unschönen Stellen" im Code noch überschaubar. Man hat zwar manchmal das Gefühl, irgendetwas sei falsch oder zumindest nicht ganz richtig, aber irgendwie bastelt man die neue Funktion doch noch hinein. Irgendwann werden die kritischen Stellen immer größer. Man versteht plötzlich seinen eigenen Code nicht mehr. Weil man sich nicht traut, den Code zu ändern, kopiert der Entwickler eben den Code und passt die Kopie an. Hier beginnen sich die Probleme zu verstärken: Plötzlich wurde ein Codebereich verdoppelt, und es besteht die Gefahr, dass beide Teile erneut verdoppelt werden. Der Code explodiert geradezu.

Im Prinzip sollten die Kosten für eine Funktion immer gleich sein, egal ob der Entwickler sie am Anfang implementiert oder erst zu einem späteren Zeitpunkt hinzufügt. In der Praxis sind nachträglich ergänzte Funktionen deutlich teurer – wohlgemerkt bei gleichem Umfang. Das liegt daran, dass Programmierer die innere Qualität der Software in aller Regel nicht auf Evolvierbarkeit auslegen. Dabei ist diese von Anfang an zu berücksichtigen, denn dieses innere Qualitätsmerkmal lässt sich nicht später hinzufügen. Damit ist nicht eine maximale Flexibilität gemeint, die für alles gerüstet ist und jegliche Ergänzung oder Veränderung mitmacht. Der Aufwand wäre zu hoch und würde sich betriebswirtschaftlich nicht darstellen lassen. Gemeint ist eine Struktur, die sich mit wirtschaftlichem Aufwand an den Stellen, an denen es sich als erforderlich erweist, anpassen lässt. Dabei liegt der Fokus nicht darauf, zu jedem Zeitpunkt maximale Qualität anzustreben. Sie sollte angemessen sein und Veränderung sowie Erweiterung ermöglichen.

Wenn die innere Struktur und Qualität einer Software das Ergänzen von Funktionen erschwert, steigen die Kosten. Auch das Anpassen von Funktionen an geänderte Rahmenbedingungen verursacht immer höhere Kosten. Schließlich führt der Aufwand für manuelles Testen zu höheren Ausgaben. Irgendwann sind sie so groß, dass keine Funktionen mehr ergänzt werden, weil das betriebswirtschaftlich nicht sinnvoll wäre. Soll die Software trotzdem im Einsatz bleiben, steigen die Support-Kosten, und will man die Software weiterhin vermarkten, steigen mitunter die Kosten für höheren Marketingaufwand, weil sich der Überzeugungsaufwand immer größer gestaltet.

Eine große Gefahr der ansteigenden Kosten ist, dass der Anstieg nicht linear erfolgt. Das liegt daran, dass sich die Probleme anhäufen und gegenseitig verstärken. So werden steigende Kosten am Anfang eines Softwareprojekts oft nicht als problematisch erkannt. Man geht davon aus, dass die Kosten später wieder sinken. Die strukturellen Fehler führen jedoch dazu, dass die Probleme später immer größer ausfallen, wenn man sie nicht von Anfang an eindämmt. Sind erst tausende Zeilen schlechten Codes angesammelt, fällt es schwer, wieder Ordnung herzustellen. Und je länger man diesen Prozess hinausschiebt, desto teurer wird er.

Da es relativ lang dauert, bis sich die Auswirkungen einer unprofessionellen Arbeitsweise an steigenden Kosten ablesen lassen, ist es meist zu spät für Eingriffe, die wenig Aufwand verursachen. Wenn man nicht ständig die Qualität der Software im Blick hat, wird das Problem schleichend immer größer, bis es am Ende so groß ist, dass zu seiner Behebung massive Veränderungsprozesse einzuleiten sind. In aller Regel betreffen die Veränderungen nicht nur die Arbeitsweise, sondern auch den Entwicklungsprozess. Damit fordert man Entwickler heraus, sich einerseits mit neuen Prinzipien und Praktiken auseinanderzusetzen, andererseits sind sie mit Veränderungen in der Organisation ihrer täglichen Arbeit konfrontiert.

Die nötigen Veränderungen sind in der Regel so umfangreich, dass sie zum einen relativ teuer sind und zum anderen die Gefahr droht, dass sie sich nicht erfolgreich umsetzen lassen. Veränderungsprozesse haben einen typischen Verlauf: Wenn man mit ihnen beginnt, sinkt die Produktivität ab, um dann wieder anzusteigen. Vertraut man nicht darauf, dass nach dem Abstieg ein Aufstieg auf ein höheres Niveau erfolgt, brechen die Beteiligten den Veränderungsprozess möglicherweise zu früh ab und scheitern. Vor allem das Vertrauen des Managements gegenüber den Entwicklern ist hier gefragt. Aber auch Entwickler müssen sich gegenseitig vertrauen und davon ausgehen können, dass jeder nach Kräften sein Bestes gibt.

Entwickler haben ein ungutes Gefühl, wenn sie an Software mit schlechter innerer Struktur arbeiten müssen. Sie sind ständig von der Angst ergriffen, etwas zu zerstören. Da automatisierte Tests, wenn überhaupt, nicht im erforderlichen Umfang zur Verfügung stehen, müssten sie bei jeder Änderung oder Ergänzung die gesamte Software erneut manuell testen. Das ist im erforderlichen Umfang jedoch schlicht nicht realisierbar. Also testet man nur Bereiche, an denen der Entwickler gearbeitet hat. Oft handelt es sich bei Eingriffen in die Software jedoch nicht um eng begrenzte Bereiche. Das liegt daran, dass die Implementierung eben nicht nach den "Regeln der Kunst" erfolgt ist. Bei vielen Ergänzungen oder Änderungen ist demnach an diversen Stellen einzugreifen, was das Testen enorm erschwert. Auch hier liegt ein sich selbst verstärkender Prozess vor: Weil die innere Struktur so schlecht ist, ist oft an vielen Stellen zu ändern. Dadurch wird die Struktur im Laufe der Zeit noch schlechter, die Angst, etwas zu zerstören, noch größer. Selbst lokale Änderungen führen häufig zu unerwarteten Problemen an scheinbar nicht betroffenen Stellen, weil keine saubere Trennung vorliegt. Weil man Angst hat, etwas kaputt zu machen, traut man sich nicht, für die erforderlichen Ergänzungen eine punktuelle Verbesserung der Struktur vorzunehmen. Dadurch wird beim nächsten Mal die Angst noch größer.

Jeder Mensch dürfte schon mal die Abneigung verspürt haben, durch eine Straße zu gehen, in der überall Müll herumliegt. So ergeht es Softwareentwicklern beim Blick auf schlechten Code. Sie verspüren eine innere Abneigung dagegen, sich mit dem Code auseinanderzusetzen. Wie sich in einer Straße der Unrat weiter ansammelt, wenn ihn niemand wegräumt, sammelt sich schlechter Code an. Da der Code den Eindruck vermittelt, es käme nicht darauf an, gute Qualität abzuliefern, wird ein Entwickler seine Ergänzungen tendenziell in gleich schlechter Qualität abliefern.

In Brownfield-Projekten trifft man häufig auf manuelle Handarbeit. Beispielsweise ist der Build- und Release-Prozess oft nicht automatisiert. Steht eine Version zur Auslieferung an, sucht der Entwickler in mühsamer Handarbeit alles zusammen, was zur Verteilung erforderlich ist. Neben der Tatsache, dass die Tätigkeit nicht gerade mit Spaß verbunden ist, besteht das Risiko, Fehler zu machen. Entwickler vergessen Komponenten, Modulversionen passen nicht zusammen, ein erforderliches Datenbank-Update unterbleibt et cetera. Es soll sogar Projekte geben, in denen nicht einmal ein Versionskontrollsystem zum Einsatz kommt.

Auch das Testen der Anwendung erfolgt oft manuell. Da die innere Struktur es nicht zulässt, einzelne Funktionen isoliert zu testen, bleibt nur, Integrationstests durchzuführen. Sie lassen sich automatisieren, jedoch herrscht oft der Glaube vor, das würde sich nicht lohnen. Stattdessen testet man am Ende viel zu wenig.

Die manuellen Arbeiten führen noch zu einem weiteren Problem: Die Rückmeldung an die Entwickler dauert vergleichsweise lange, weil sich die manuellen Tätigkeiten nicht so oft und nicht schnell ausführen lassen. Entwickler sammeln mehrere neu implementierte Funktionen, ehe sie einen manuellen Integrationstest durchführen. Das führt dazu, dass der Programmierer erst spät eine Rückmeldung darüber erhält, ob die implementierte Funktion korrekt ist. Es fallen überflüssige Arbeiten an, die vermeidbar wären, wenn die Rückmeldung früher eingetroffen wäre.

Manuelle Arbeiten lassen sich nicht in allen Fällen vermeiden. Oft sind jedoch Automatisierungen mit wenig Aufwand möglich, und das reduziert nicht nur die Kosten, sondern erhöht gleichzeitig die Qualität.

Ein häufig zu beobachtendes Problem von Brownfield-Projekten ist die Abkopplung von aktuellen Entwicklungen. Wer immer noch mit Visual Basic 6 (VB6) entwickelt, hat beispielsweise keinen Zugriff auf aktuelle Techniken im Bereich der Entwicklungsumgebungen. Aktuelle IDEs bieten komfortable und leistungsfähige Unterstützung für Refaktorisierungen. In VB6 ist das nicht verfügbar. Ohne Werkzeugunterstützung sind Refaktorisierungen aufwendig und damit teuer. Ferner steigt das Risiko, neue Fehler zu verursachen, wenn die Refaktorisierungen nicht automatisiert erfolgen können.

Des Weiteren kann es auf technischer Ebene eine Abkopplung von aktuellen Entwicklungen geben: Soll beispielsweise eine Anwendung, die bislang nur als Windows-Applikation zur Verfügung steht, auch im Web verfügbar sein, bedeutet das bei einer monolithischen Anwendung eine komplette Neuentwicklung. Nur eine Applikation mit einer guten inneren Struktur lässt sich mit vertretbarem Aufwand so anpassen, dass der Kern der Anwendung sich unter einer anderen Oberfläche weiternutzen lässt. Anwendungen, die sich nicht in kurzer Zeit an aktuelle technische Entwicklungen anpassen lassen, haben einen Wettbewerbsnachteil. Kann der Mitbewerber beispielsweise in kurzer Zeit eine Serviceschnittstelle über das REST-Protokoll anbieten, kann das ein entscheidender Vorteil sein. Ob man es im konkreten Fall tatsächlich tun möchte, ist eine Entscheidung, die wohl überlegt sein will. Es sollte aber strukturell umsetzbar sein. Das Risiko besteht darin, eine aktuelle Entwicklung als "Hype" abzutun, obwohl es in Wirklichkeit durch strukturelle Fehler gar nicht möglich ist, sie umzusetzen.

Wer bewirbt sich auf eine Stelle, für die der Inserent Kenntnisse in "uralten" Programmiersprachen fordert? Oft sind das Softwareentwickler, die nur diese Sprache beherrschen und die lange nicht in persönliche Weiterentwicklung investiert haben. Solche Entwickler sind in gewissem Umfang erforderlich, aber man wird sie kaum zu denen zählen, die für Innovation und Weiterentwicklung im Unternehmen sorgen. Damit zeigen sich gleich zwei Probleme: Entwickler, die in der Lage sind, die alten Systeme weiterzupflegen, sind nur schwer zu bekommen. Wenn man sie dann hat, sind sie in aller Regel nicht die Spitzenkräfte, die in der Lage wären, für Weiterentwicklung und Innovation zu sorgen. Da sich das Unternehmen mit seinen Altlasten herumplagt, ist es wenig attraktiv für Spitzenkräfte, die sich ständig weiterentwickelt haben. So entsteht ein sich selbst verstärkender Teufelskreis.

Oft ist in Brownfield-Projekten, die seit längerer Zeit laufen, der Prozess der Softwareentwicklung nicht angemessen. Es wird nach wie vor versucht, Softwareprojekte mit wasserfallartigen Prozessen durchzuführen. Doch das gelingt nur in seltenen Fällen. Softwareprojekte sind zu komplex, um sie auf diese Weise zu steuern. Die Steuerung muss in kurzen Iterationen erfolgen, in denen vor allem der Kunde die Chance erhält, seine Anforderungen zu präzisieren oder sogar zu ändern.

So ist die Herausforderung, ein Brownfield-Projekt auf solide Füße zu stellen, oft mit der Notwendigkeit verbunden, den Prozess anzupassen beziehungsweise überhaupt einen definierten Prozess einzuführen. Das erleichtert die Aufgabe nicht gerade.

Brownfield ist eine Herausforderung. In den nächsten Teilen der Serie geht es darum, sich ihr zu stellen. Es werden konkrete Maßnahmen beschrieben und Tipps gegeben, damit Brownfield-Projekte "clean" werden. Die Bausteine der "Clean Code Developer"-Initiative sind in fünf sogenannte Grade eingeteilt. Der Schnitt durch die Bausteine wurde gewählt, um handhabbare Ausschnitte präsentieren zu können. Die Grade enthalten jeweils etwa acht Bausteine und versetzen den Einzelnen in die Lage, sich eine Zeit lang mit einer überschaubaren Teilmenge der Bausteine zu befassen. Sie geben somit einen Weg vor, sich lernend mit den Bausteinen auseinanderzusetzen, ohne zunächst ein Umfeld überzeugen zu müssen.

Die Artikelserie legt nun bewusst einen anderen Schnitt durch die Bausteine. Sie werden hier im Kontext von Brownfield-Projekten betrachtet. Daraus ergibt sich eine andere Reihenfolge, die den Besonderheiten von Code und Organisation in Brownfield-Projekten gerecht wird. Leitmotiv ist der schnellstmögliche Nutzen für das Team und nicht mehr einfaches Lernen für den Einzelnen.

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

(ane)