Code verändern, ist schwer. Wer kämpft sich schon gern durch einen Dschungel aus Legacy Code? Viel schöner ist die Entwicklung auf einer grünen Wiese. Code neu schreiben, ist demgegenüber leicht.
Wenn es in Projekten knirscht, weil die Last der Brownfield Codes erdrückend ist, ist Neuentwicklung allerdings nur selten eine Option. Der Aufwand wäre gewaltig; dafür gibt es kein Geld und keine Zeit.
Aus dem Morast scheint dann nur der trübe Weg langwieriger Refaktorisierungen zu führen. Wochen und Monate arbeitet sich das Team daran ab. Spaß macht das nicht. Motvierend für andere ist das auch nicht. Wenn die Codebasis an einem solchen Punkt ist, leidet das Produkt unter doppelt schlechter Qualität: nicht nur die des Codes lässt zu wünschen übrig, sehr wahrscheinlich ist auch die Qualität des Teams – oder zumindest seine Attraktivität – suboptimal.
Natürlich kann Software nicht dauernd neu geschrieben werden. Aber wenn es soviel leichter ist, Legacy Code neu zu schreiben, statt ihn zu ändern, warum gibt es dann kein Bemühen, der Leichtigkeit des Greenfield näher zu kommen?
Ich meine, darum sollte sich der Entwurf bemühen. Die Softwarearchitektur sollte die Machbarkeit der Neuentwicklung als nicht-funktionale Anforderung sehen, ohne die Nachhaltigkeit nicht zu erreichen ist.
Zunächst einmal wird Code natürlich zum Bug Fixing und für neue Anforderungen erweitert. Das geht auch eine ganze Zeit gut. Aber wie lange? Niemand weiß das genau. “Es kommt halt darauf an…” Das ist wahr – nur führt diese weise Haltung gewöhnlich eben zu dem monolithischen Code, der dann nicht mehr mit vertretbarem Aufwand veränderbar ist.
Deshalb glaube ich, dass die Entscheidung, wie lange an Code rumgeschraubt werden sollte, weniger weise, weniger emotional ausfallen sollte, sondern viel pragmatischer und regelhafter.
Hier mein Vorschlag:
- Eine Codebasis sollte in Komponenten von max. 10.000 LOC aufgeteilt werden.
- Eine Codebasis sollte in Services von max. 60.000 LOC aufgeteilt werden.
Unter Komponenten verstehe ich hier binäre Codeeinheiten mit separatem Kontrakt auf derselben Plattform. Komponenten machen also keinen Aufwand bei der Kommunikation untereinander.
Unter Services hingegen verstehe ich Gruppen von Komponenten mit einem separatem gemeinschaftlichen Kontrakt – allerdings auf u.U. unterschiedlichen Plattformen. Die Kommunikation zwischen Services ist also kein no-brainer mehr.
Die Aufteilung in Komponenten und Services dieser Größe schlage ich vor, weil sich für mich damit Grenzen machbarer Neuentwicklung ergeben.
Komponenten neu entwickeln
Eine Komponente von 10K LOC [1] kann ein Team in einem Monat [2] neu entwickeln, wenn es hart auf hart kommt. Das scheint mir ein überschaubarer Zeitraum aus Sicht des Managements. Eine Entscheidung dafür sollte das Geschäft nicht aufs Spiel setzen.
Eine Software wird von vornherein komponentenorientiert entwickelt. Das Team beobachtet das Wachstum der einzelnen Komponenten. Seien wir ehrlich: dabei kontinuierlich den Komponentencode sauber zu halten, ist eher eine Idealvorstellung. Kleinere Refaktorisierungen werden vorgenommen – aber wenn größere notwendig werden, ist dafür eher keine Zeit. Die werden vertagt… Also kommt der Tag, an dem eine Komponente 10K LOC umfasst und eigentlich so richtig refaktorisiert werden müsste.
An dem Punkt entscheidet sich das Team nun jedoch für eine komplette Neuentwicklung. Statt mühselig Code zu säubern, wird auf der grünen Wiese neu angefangen – im Rahmen des Komponentenkontrakts. Der resultierende Code ist dann nicht nur sauber, sondern rein ;-) Damit meine ich, dass Refaktorisierung nur zu Clean Code zweiter Wahl führt, weil sie sich im Rahmen des Existierende bewegt. Das schränkt die Kreativität ein, das behindert die Innovationsmöglichkeiten. Ein Beginn auf der grünen Wiese hingegen ist frei von solchen Altlasten. Da ist alles erlaubt und denkbar – solange der ursprüngliche Kontrakt eingehalten wird.
Services neu entwickeln
Einen Service von 60K LOC kann ein Team in 6 Monaten neu entwickeln, wenn es hart auf hart kommt. Das ist kein ganz kurzer Zeitraum, das entscheidet man nicht zwischen Tür und Angel – doch es ist immer noch viel überschaubarer als die Neuentwicklung einer kompletten Anwendung von 500.000+ LOC. 6 Monate sind absehbar. 6 Monate sind in vielen Teams ein Releasezyklus oder gar weniger.
Der Trick bei den Services ist nun, dass ihre Neuentwicklung noch mehr Freiheitsgrade bietet. Nicht nur kann die interne Struktur über Komponentengrenzen hinweg rein gemacht werden, nein, es kann sogar ein Plattformwechsel stattfinden. Damit ist die Bedingung für die Möglichkeit kontinuierlicher Innovation geschaffen. Denn die hängt nicht nur an den Fähigkeiten der Entwickler, sondern auch am technologischen Fortschritt.
Wer sich vor 5 Jahren einmal für Java entschieden hat, muss dann nicht bis zum Lebensende (der Software) alles in Java entwickeln. Für jeden Service kann vielmehr immer wieder neu entschieden werden, ob ein Plattformwechsel Vorteile bietet. Eine Entscheidung ist möglich, weil die Servicegrenze in der Architektur überhaupt gezogen wurden – und weil darauf geachtet wurde, die Größe im Rahmen machbarer Neuentwicklung zu halten.
Günstig mag ein Plattformwechsel sein, wenn eine andere Plattform bessere technische Möglichkeiten bietet. Aber der Wechsel kann auch durch Erhaltung der Attraktivität der Codebasis motiviert sein. Plattformen unterliegen Moden. Um dauerhaft Entwickler für die Mitarbeit zu begeistern, mag es angezeigt sein, Plattformmoden zu folgen. Wer will denn heute eine große Cobol-, Fortran-, PHP- oder VB6-Codebasis pflegen? Durchschnittlich attraktiv sind C# oder Java oder Ruby oder Python – dahinter lauern aber schon Scala, Groovy, Clojure, Erlang, F#, JS und andere.
Nur wer “Sollbruchstellen” im Code in Form von Servicegrenzen vorsieht, hat die Chance, technologisch kontinuierlich uptodate zu bleiben.
Heute wird vielen Softwaresystemen eine solche Grenze nachträglich durch den Wunsch nach mobile applications aufgezwungen. Da entsteht plötzlich ein zusätzlicher Service in Form einer App. Da tritt nach Jahren mal wieder – gezwungenermaßen – eine neue Plattform auf den Entwicklungsplan. Da entsteht plötzlich Attraktivität. Leider sind damit aber auch viele Teams überfordert, weil sie jahrelang keine Übung gehabt haben im Plattformwechsel oder gar auch nur im Neuanfang auf einer grünen Wiese.
Fazit
Refaktorisierungen zur Herstellung und Erhaltung evolvierbarer Stukturen sind nicht überflüssig – aber überbewertet, würde ich mal sagen. Viel öfter sollte Neuentwicklung statt Refaktorisierung gedacht werden. Dazu bedarf es aber klarer Grenzen, in denen Neuentwicklung auch machbar ist. Ich habe hier mal zwei solche Grenzen unterschiedlicher Granularität vorgeschlagen. Ob die bei diesen LOC und diesen Zeiträumen verlaufen sollten, sei dahingestellt. Da mag sich jedes Team seine eigenen Grenzen setzen. Dass es jedoch Komponenten- und Servicegrenzen geben sollte, da bin ich sicher. Wir brauchen diese beiden Horizonte.
Der Servicehorizont liegt mir besonders am Herzen. Denn einer der größten Übelstände der Softwareentwicklung scheint mir derzeit die Erstarrung durch Konsolidierung und Homogenisierung. Dem muss entgegen gewirkt werden. Vielfalt muss möglich sein. Denn Vielfalt bedeutet natürliche Lebendigkeit. Nur so ist Innovation kein Kraftakt alle Jubeljahre, sondern jederzeit möglich.
Fußnoten
[1] Die LOC habe ich mal über den Daumen für ein Team von 5 Entwicklern berechnet, das jeden Tag pro Entwickler im Schnitt 100 LOC Produktionscode herstellt. Bei 20 Arbeitstagen/Monat ergibt das 10.000 LOC.
[2] Den Monat Aufwand für eine machbare Neuentwicklung einer Komponente bzw. die 6 Monate für einen Service meine ich nicht wörtlich. Was ein Unternehmen für machbar bei Neuentwicklungen hält, soll es selbst entscheiden. Mir scheint ein Monat für eine Komponente jedoch nicht ganz unrealistisch. Und ein halbes Jahr für die Möglichkeit eines Plattformwechsels, hören sich auch nicht so schlecht an, oder? Wenn möglich, können Services aber natürlich auch kleiner gehalten werden. 3 Monate für eine Neuentwicklung wären besser als 6.