Herausforderung Brownfield, Teil 4: Komplexität bewältigen durch Differenzierung

Architektur/Methoden  –  Kommentare

Ist das in den letzten Artikeln der Artikelserie beschriebene Sicherheitsnetz bei einer Brownfield-Anwendung aus automatisierten Tests, Versionskontrolle und automatisierter Produktion gespannt, geht es darum, den Code zu säubern und ihn sauber zu halten. Das bedeutet, dass Funktionseinheiten klar definierte Verantwortlichkeiten haben.

Dass man automatisiert testen sollte, ist nicht neu, in vielen Projekten sind Unit-Tests aber immer noch keine Selbstverständlichkeit, geschweige denn Test-Driven Design (TDD). Dass Entwickler ein Versionsverwaltungswerkzeug einsetzen sollen, ist ebenfalls ein alter Hut. Effektiv nutzen sie das vielfach jedoch immer noch nicht. Dabei könnten moderne verteilte Versionsverwaltungen den Entwicklungsprozess stabiler gestalten. Aber darüber mögen Teams (oder ihre Manager?) nicht nachdenken, weil vor Jahren die Entscheidung für ein Versionsverwaltungssystem gefallen ist, mit dem man schon Mühe genug hat.

Doch einerlei: Das Sicherheitsnetz aus automatisierten Tests, Versionsverwaltung und schließlich automatisierter Produktion (Continuous Integration) mag noch löchrig sein, jedoch ist es im Projekt nun irgendwie vorhanden. Nicht jedes Loch lässt sich ja sofort flicken. Man mag sich Mühe geben, aber TDD einzuführen braucht eben seine Zeit.

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.

Das Team hat auch keine Angst mehr, seinen Brownfield-Code tief umzugraben, um daraus wieder fruchtbaren Humus für eine längerfristige Weiterentwicklung an der Anwendung zu erzeugen. Doch wo soll es beginnen? Die übliche Antwort lautet: Refaktorisieren – dort im Code beginnen, wo es schlecht riecht (Code Smell), und sich Stück für Stück vortasten. Der Code soll in einem besseren Zustand als vorgefunden hinterlassen werden. Es lässt sich zweifelsfrei über dem Sicherheitsnetz arbeiten – auf der Mikroebene. Das Team braucht allerdings ein dem großen Ziel angemessenes "Big Picture".

Spätestens nach der Installation eines Sicherheitsnetzes stellt sich die Frage, wie denn das Big Picture für das Projekt, die Anwendung, aussieht. Das mag überflüssig klingen. Es liegt ja ein Auftrag vor. Der Kunde hat den Wunsch geäußert: "Entwickeln Sie eine Warenwirtschaft" oder "Wir brauchen ein neues Buchungssystem" oder "Unsere Verträge sind so kompliziert, die müssen wir jetzt automatisiert prüfen". Darauf wurden die Anforderungen erhoben und geplant und getan und losgewerkelt ... aber nun steht man im Brownfield-Matsch. Irgendwie war das mit dem Big Picture vielleicht doch nicht ausreichend. Selbst eine vielleicht gemalte Schichtenarchitektur hat nicht so viel genützt.

Das Big Picture lastet wie eine Presse auf dem Code. Kräfte jenseits der Entwicklerkompetenz wie Zeit und Budget drücken den Code mit Macht zusammen. Andere Kräfte sind jedoch relevanter.

Code unter Druck (Abb. 1)

Zu nennen wäre das persistente Datenmodell. Ein Repräsentant dafür ist das oft gesetzte relationale Datenbank-Management-System (DBMS). Mehr oder weniger offen ausgesprochen versuchen viele Projekten, die persistenten Daten einer Anwendung in einem einzigen Datenmodell zu beschreiben und in einem Persistenzmedium zu halten. (Von Import-/Export-Daten sei abgesehen, sie sind nur temporär von Interesse.) Sie versuchen, alle Vorgänge von Warenwirtschaft, Buchungssystem oder Vertragsprüfung vorzugsweise auf dieses eine Datenmodell auszurichten. Wenn möglich soll es sich sogar anwendungsübergreifend nutzen lassen. Ist es so nicht am besten? Beugt man nicht so Inkonsistenzen am effizientesten vor? Sind nicht nur dann alle Daten immer für jeden verfügbar? Ist Homogenität nicht kostengünstig)? Ist Konsolidierung nicht nur bei Hardware gut, sondern auch innerhalb von Software? Gründe für das universelle, das eine Datenmodell gibt es viele.

Es übt allerdings nicht nur als Konzept eine ungeheure Kraft auf das Denken aus, sondern auch über sein Schema auf den Code. Wenn es nur ein Datenmodell gibt, dann auch nur eine API für das Persistenzmedium. Damit gibt es keine zwei Meinungen mehr über den Zugriff, der sich schnell mal einstreuen lässt. So entsteht enge Kopplung. Das eine Datenmodell suggeriert Konstanz und Verlässlichkeit, sodass sich Code ohne Risiko breit daran binden kann. Das ändert auch eine Datenzugriffsschicht kaum, also eine Kapselung der konkreten Persistenz-API oder gar des Schemas.

Das eine Datenmodell zwingt immer wieder zu Kompromissen. Es "verklebt" Code vertikal im Funktionsdurchstich von der GUI bis zur Datenzugriffsschicht und horizontal über Durchstiche hinweg. Denormalisierungen in relationalen Datenmodellen sind dafür ein Beleg. Denn wo man denormalisieren muss, um einer Funktion zu dienen, soll sich ein zweiter Zweck jenseits einer anderen Funktion erfüllen lassen. Ein Anwendungsteil braucht ein flexibles und einfach zu pflegendes Datenmodell, ein anderer ein performantes. Die Presskraft des einen Datenmodells ist stark. Sie drückt Code zusammen von der persistenten Klasse, die der O/R-Mapper befüllt, bis zum GUI-Steuerelement, das sich an persistente Objekte bindet.

Eine weitere Presskraft ist die Benutzerschnittstelle. Wer eine Anwendung realisieren möchte, sieht sie meist durch eine Benutzeroberfläche repräsentiert. Multiple Document Interfaces (MDI) sind das Ergebnis. Vom Sachbearbeiter bis zum Chef schauen alle durch dieselben Fenster in die Daten. Das eine GUI presst nun allerdings Code mit Leichtigkeit zusammen, wenn dem keine rigorose Architektur Widerstand leistet. Wenn dazu noch Erfolgsdruck hinzukommt – "Wir müssen dem Kunden schnell was zeigen!" –, wandert Code wie von Geisterhand gern in die GUI. Für Planung ist kaum Zeit, doch ein Ort, an dem Code auf jeden Fall stehen kann und muss, ist sicher: die GUI. Denn Code wird schließlich immer ausgeführt als Reaktion auf ein Ereignis, und zwar ausgelöst vom Anwender. Warum nicht den Code möglichst dicht an das Ereignis heranführen? Es entstehen Dialoge mit mehreren Tausend Zeilen Code. Eine Brownfield-Anwendung unter Druck des einen Frontends und des einen Datenmodells – so sieht ein guter Teil der Anwendungsentwicklung aus großer Flughöhe aus.

Damit aber nicht genug. Es gibt noch eine weitere, oft übersehene Kraft, die auf dem Code lastet. Die geht von einem Nichts, einer Lücke aus. Sie entspringt der Leere zwischen dem großen Ganzen der Anforderungen – "Wir brauchen eine neue Warenwirtschaft" – und dem Schichtenmodell oder einem anderen typischen Lösungsansatz aus.

Von den Anforderungen zur Architektur ist immer ein Sprung zu vollziehen – das ist die kreative Leistung der Softwareentwicklung. In vielen Fällen ist dieser jedoch viel zu groß, um zu evolvierbarem Code zu kommen. Das Team springt – und fällt in die Lücke zwischen Anforderungen und Architektur, weil das Bewältigen der Distanz über seine Kräfte geht. Es gibt nur eine Mittel: Für das eine große Ganze der Anforderungen gibt es nur den einen Entwurf. Eine Anwendung ist gefordert, also gibt es eine Architektur und eine Codebasis und ein Datenmodell. Das ist eine Gleichung, die in vielen Teams tief verankert ist.

Sind die Anforderungen genügend kompliziert – und das ist öfter und schneller der Fall, als man denkt –, klafft eine Lücke zwischen ihnen und dem Entwurf. Der Entwurf deckt sie nicht wirklich ab. Deshalb ist er lückenhaft und unspezifisch. Deshalb füllen Projektteams weiße Flächen ad hoc aus. Brownfield-Code entsteht, weil ihm der Planungsrahmen fehlt. Die Last der Leere zwischen Anforderungen und Planung drückt ihn unerbittlich zusammen. Was lässt sich dagegen tun? Projektteams müssen die bisherige Leere zwischen Anforderungen und Planung mit Differenzierungen füllen.