Refactoring

Wie Sie von Code Refactoring profitieren

03.12.2021
Von   IDG ExpertenNetzwerk
Andreas Hernitscheck entwirft als Softwarearchitekt komplexe Systeme unter Verwendung von Cloud-Technologien. Als Entwicklungsleiter koordiniert er den Lebenszyklus von Software und führt Teams zum Erfolg. Das breite Wissen über verschiedene Technologien erweitert er seit 1988 bis heute ständig, um optimal zu beraten und gute Lösungen zu schaffen.
Code Refactoring kann entscheidend dazu beitragen, dass Ihr Unternehmen profitabel bleibt. Wie Sie alten Code erneuern, lesen Sie hier.
Neu programmieren oder Code Refactoring? Das Überprüfen der vorhandenen Legacy-Programmierung kann bei dieser Entscheidung helfen.
Neu programmieren oder Code Refactoring? Das Überprüfen der vorhandenen Legacy-Programmierung kann bei dieser Entscheidung helfen.
Foto: ZinetroN - shutterstock.com

Refactoring ist das Überarbeiten von Programmcode, um vorhandene architektonische Schwächen zu beseitigen. Dieser Artikel erklärt zwei mögliche Vorgehensweisen, um alten Programmcode teilweise oder ganz durch neuen zu ersetzen.

Refactoring of Code - Technische Schulden nicht ignorieren

Es gibt einige unbeliebte Begriffe bei Projektleitern und Managern. Einer davon ist sicherlich "Refactoring". Gehören Sie zur Gruppe der Verantwortlichen eines Projekts und einer der Entwickler erwähnte die Notwendigkeit von Refactoring? Wenn Sie sofort wussten, was Refactoring ist, zuckten Sie vielleicht innerlich zusammen und gingen direkt in eine Abwehrhaltung. Für alle anderen zur Erklärung: der Entwickler verlangt nach viel Zeit und damit Geld, um einiges noch einmal neu zu entwickeln.

In den seltensten Fällen wird darauf eingegangen, sondern oft mit Unverständnis reagiert und einem "Wieso, es funktioniert doch alles!" gekontert. Wenn Sie an diesem Punkt einfach abblocken, könnte es gut sein, dass Sie eine Schuld auf sich laden, die das Unternehmen viel Geld kosten wird. Falls unter Ihrer Führung als Entwicklungsleiter, Projektmanager oder CTO schon nach Refactoring verlangt wurde, sind Sie sehr wahrscheinlich dafür verantwortlich und haben eine technische Schuld erzeugt bzw. erzeugen lassen.

Vielleicht können Sie sich noch an die Anfangszeit des Projekts erinnern? Der Enthusiasmus war groß und neue Features wurden schnell implementiert. Nach einigen Jahren tritt oft das Gegenteil ein; das Entwickeln neuer Features dauert lange und die Motivation der Entwickler ist deutlich gesunken. In Abschätzungen zu neuen Features fallen schon mal Sätze wie "Wir wissen nicht, wie lange es dauert". Ein Grund für dieses Phänomen kann die von Grund auf schlechte Architektur sein. Nachträgliche Änderungen kosten sehr viel Geld und manche lassen sich gar nicht mehr durchführen.

Lesetipp: Technische Schulden - und wie man diese vermeidet

Alles neu oder Code Refactoring?

Dazu gibt es keine universelle Antwort. Haben Sie jedoch Mut zu vollständig neuem Code, wenn das zeitlich irgendwie darstellbar ist. Dies wird wesentlich schneller gehen als den alten Code Stück für Stück zu refaktorieren. Das liegt vor allem auch daran, dass die Notwendigkeit der Refaktorierung im schlechten Code begründet ist und es ähnlich wie bei neuen Features schwierig ist, Neues einzubauen bzw. zu ersetzen.

Die traurige Wahrheit ist, dass bestehender Code selten gut dokumentiert ist und es oft auch keine gepflegten und strukturierten fachlichen Informationen darüber gibt, was das Programm eigentlich genau tun soll. Verschwenden Sie nicht zu viel Zeit den vorhandenen Code zu analysieren, um herauszufinden, was er tut. Erstellen Sie vielmehr eine neue Liste mit Anforderungen, was er können muss. Dabei werden Sie vielleicht auch feststellen, dass so manche Logik gar nicht mehr gebraucht wird, da sich über die Zeit die Anforderungen verändert haben und alte Umsetzungen dann schließlich doch nicht genutzt wurden.

Ein wunder Punkt in vielen Projekten ist die fehlende Testabdeckung. Beispielsweise durch sogenannten Unit-Tests, die automatisch Teile des Programmcodes auf ihre Funktion und Richtigkeit prüfen. Auch wenn es den meisten Entwicklern bekannt ist, dass diese Tests enorm wichtig zur Qualitätssicherung sind, werden sie oft vernachlässigt und manchmal jahrzehntelang gar nicht geschrieben. Wenn neuer Programmcode geschrieben wird, muss unbedingt darauf geachtet werden, dass sich diese Fahrlässigkeit nicht wiederholt. Um dies zu vermeiden sollte testgetriebene Entwicklung (TDD) ohne Diskussion abverlangt werden. Übrigens: Falls ihre Entwickler feststellen, dass sich vorhandener Code nicht durch automatische Tests prüfen lässt, ist dies ein zuverlässiges Zeichen dafür, dass die Architektur schlecht ist.

Lesetipp: Softwaretesting - Installationen und Funktionstests automatisieren

Monolithen und Spaghetticode

Tatsächlich sind das zwei Begriffe die Entwickler so verwenden. Sie beschreiben je bestimmte Zustände des Produkts. Lassen Sie uns zunächst auf den monolithischen Code eingehen.

Die Monolithen sind nicht per Definition etwas Schlechtes, bringen jedoch oft Probleme mit sich, wie die Schwierigkeit Teile auszutauschen oder bei erhöhter Last zu skalieren. So kommt es auf den Einsatz der Software an. In einem Microcontroller sind die Bedingungen ganz anders, wie bei einer Internet-Anwendung. Es ist eine alte Programmierweisheit Teile in sogenannte Bibliotheken auszulagern. Diese können so leichter getestet und durch ihre bereits separate Natur auch leichter ersetzt werden. Monolithen sind große Programme, die diese Aufteilung eher nicht verfolgen, da alles in ein einziges Projekt integriert wurde. Die Grenzen von einer Logik zur anderen sind nicht immer klar zu erkennen.

Der sogenannte Spaghetticode beschreibt eine Verflechtung von unterschiedlichen Programmteilen auf eine Weise, die es schwer macht zu erkennen, welche Fragmente mit anderen auf welche Art kommunizieren und welche Daten ausgetauscht werden. Dabei kann es vorkommen, dass Informationen über viele Schichten durchgereicht werden und somit keine sauberen Zuständigkeiten zu erkennen sind.

Die Monolithen und der Spaghetticode verwandeln das Produkt am Ende zu einer Art Black-Box. Es geht etwas hinein und es kommt etwas heraus. Dazwischen arbeitet eine kaum nachvollziehbare Logik.

Code Refactoring: Methoden in der Praxis

Sie haben sich für ein Refactoring of Code entschieden? Falls der Code bereits einigermaßen modular ist, wissen die Entwickler meist, wie sie diese Teile ersetzen. Gehen wir in diesem Artikel aber einmal vom schlimmsten Fall aus: den Monolithen und dem Spaghetticode.

Die Replace-Bypass-Methode

Es ist zunächst notwendig einen Programmabschnitt so gut zu isolieren, dass die eingehenden und ausgehenden Informationen klar sind. Am besten wird nicht nur ein kleiner Programmabschnitt gewählt, sondern so viel, dass eine fachliche Funktion isoliert werden kann. Ein verständliches Beispiel ist die Erstellung einer Rechnung. Hier sollen Daten wie die Rechnungsposten und Adressen an eine Funktion übermittelt werden und als Ergebnis soll ein PDF zurückgeliefert werden. Diese neue Funktion darf ein Entwickler gleich separat in einem eigenen Projekt entwickeln und als Bibliothek behandeln, natürlich testgetrieben durch Unit-Tests.

Im sogenannten Legacy-Code (der alte Code), wird der isolierte Abschnitt nun nicht mehr aufgerufen und stattdessen die neue Bibliothek verwendet. Da es in der Praxis nicht unbedingt so ist, dass die notwendigen Daten für die neue Bibliothek geeignet sind, kann es notwendig sein, diese durch zusätzlichen Code vorzubereiten. Ein Beispiel dafür: aus bestehenden Listen mit einfachen Datentypen, Objekte erzeugen, da dies der objektorientierten Programmierung entspricht. Mit sogenannten Interfaces einigt sich der Legacy Code mit der Bibliothek auf ein standardisiertes Format.

Darstellung eines Bypasses zur Verwendung von neuem Code unter Verwendung eines Schalters.
Darstellung eines Bypasses zur Verwendung von neuem Code unter Verwendung eines Schalters.
Foto: Andreas Hernitscheck

Entfernen Sie den Legacy Code an dieser der Stelle nicht unbedingt gleich, wenn Sie sich mit der Fachlichkeit oder der Stabilität des neuen Codes nicht sicher sind. So kann ein Schalter eingebaut werden, der zum Beispiel durch Umgebungsvariablen oder Programmparameter dafür sorgt, dass schnell wieder der Legacy Code verwendet wird. Dadurch haben Sie die Möglichkeit im ausgelieferten Produkt schnell das alte Verhalten herbeizuführen, ohne alles zurückzubauen und nochmal ausliefern zu müssen. Falls sich nach einiger Zeit die neue Lösung bewährt hat, sollte der Legacy-Abschnitt entfernt werden.

Die New-Frame Methode

Was die Replace-Bypass-Methode leider nicht löst, ist das grundlegendere Problem der schlechten Gesamtarchitektur. Selbst wenn Teile ausgetauscht wurden, bleibt die grundlegende Struktur erhalten.

Wie wäre es, wenn Sie zunächst einen neuen Rahmen schaffen, ein Grundgerüst, in dem die Geschäftslogik an vorgesehenen Plätzen integriert wird. So wäre auch die empfohlene Vorgehensweise, wenn ein komplett neues Produkt entwickelt wird. Der Haken dabei ist nun, dass dieser Rahmen zunächst gar keine Geschäftslogik bietet, also nichts Erkennbares tut. Dennoch, eine saubere Struktur ist ein Katalysator für besseren Code. Umgekehrt ist auch ein schlechter Rahmen ein Förderer schlechten Codes. Es gibt den Effekt, dass schlechter Code oft nur weiteren schlechten Code zulässt.

Darstellung zur Verwendung von Legacy-Code in einer neuen Architektur
Darstellung zur Verwendung von Legacy-Code in einer neuen Architektur
Foto: Andreas Hernitscheck

Wie kommt nun schnell die alte Geschäftslogik in den neuen Rahmen? Möglicherweise erlaubt das bisherige Produkt, wenn auch nur nachträglich, das Integrieren von einfachen Schnittstellen, die der neue Rahmen aufrufen kann. So lässt sich das alte Produkt im Hintergrund nutzen, bis es durch neuen Programmcode ausgetauscht wurde.

Die erwähnten Methoden lassen sich auch kombinieren, indem zunächst Teile des alten Produkts ausgetauscht werden, diese dann jedoch schnell in einen neuen Rahmen integriert werden. Der Rahmen des neuen Produkts, kann somit zeitlich parallel entworfen werden.

Es gibt noch weitere Möglichkeiten als die oben erwähnten. Bei der Auswahl des Verfahrens kommt sehr auf die Beschaffenheit des vorhandenen Legacy-Produkts an.

Wann refaktorieren?

Wenn das Produkt weiter ausgebaut werden soll, dann sofort! Die Schulden werden nicht weniger, sondern mit jeder Änderung mehr. Es ist auch nicht ratsam neue Features gleichzeitig zur Refaktorierung zu entwickeln. Schaffen Sie eine kleine "Feature-Pause", sonst kann es passieren, dass die Entwickler doppelte Arbeit haben.

Eigentlich gehört das Refaktorieren sogar in jeden Sprint. Es muss eingeplant werden, dass der Code erst dann fertig ("ready") ist, wenn nicht nur getestet und dokumentiert wurde, sondern auch ein Review und eine Bewertung stattfanden. Wenn das Thema zu einem Feature noch aktuell in den Köpfen vorhanden ist, geht das Überarbeiten am schnellsten. Bevor viel diskutiert wird, sollte der Entwickler dieses Mini-Refactoring einfach vor der Fertigstellung erledigen. Das gehört zum Handwerk und das Resultat ist sozusagen sein digitaler Nachlass. Hier ist es seitens der Projektmanager oder CTOs unklug ein Veto einzulegen, nur weil das Wort "Refactoring" gefallen ist. Die Diskussionen zu diesem Thema können länger dauern als die Tätigkeit selbst.

Technische Schulden vermeiden

Softwarearchitekten gelten als sehr erfahrene Programmierer. Diese Gruppe beschäftigt sich nicht nur mit Programmcode zur Erfüllung von Features, sondern hat auch ein technisches Verständnis von wichtigen Qualitätsmerkmalen. Dazu zählen unter anderem:

  • Änderbarkeit,

  • Austauschbarkeit,

  • Zuverlässigkeit,

  • Stabilität und

  • Sicherheit.

Insbesondere die Änderbarkeit hat eine massive Auswirkung auf die Kosten neuer Features. Es gibt einige architektonische Regeln, die ein Softwarearchitekt kennt, womit qualitative Software gefördert wird.

Ja es ist möglich im Vorfeld darauf zu achten, dass alles ordentlich entwickelt wird. Und wie im Hausbau, sollten Sie zu Beginn des Projekts einem Architekten Zeit einräumen, den richtigen Rahmen zu entwerfen. Dies kann Wochen oder Monate dauern und diese Investition ist teuer. Jedoch wenig im Vergleich zu dem, was ein Unternehmen in Folge möglicherweise investieren müsste, um die technischen Schulden auszugleichen. Gerne wird gesagt, dass eine Programmierung schnell gehen muss und später einzelne Teile neu programmiert werden können. Das letztere passiert jedoch so gut wie nie, da in der Entwicklung chronischern Zeitmangel herrscht. Dies zeigt, dass es in der Verantwortung der Führung liegt, ein stetes Refactoring zu fördern und die Mitarbeiter in ihrem Architektur- und Qualitäts-Verständnis weiterzubilden.

Wenn das Projekt schon sehr fortgeschritten ist oder gar viele Jahre entwickelt wurde, machen Sie nicht den Fehler und lassen Sie einen Junior-Entwickler eine neue Version parallel erschaffen. Das muss einfach erwähnt werden, da dieses Vorgehen immer wieder beobachtet werden kann. Softwarearchitekten haben normalerweise mehr als zehn Jahre Erfahrung. Da kann ein Junior nicht mithalten und er wird wahrscheinlich nur ein weiteres Produkt mit technischen Schulden erschaffen. Besser sieht es aus, wenn das bisherige Team am neuen Produkt entwickelt, denn es hat hoffentlich aus Fehlern gelernt. Jedoch ist es auch dabei sehr zu empfehlen, dass das Team von einem Architekten geleitet wird, der mit seiner Erfahrung dafür sorgen kann, dass neue oder unerkannte Probleme vermieden werden. Gerade ein bisher projektfremder Architekt kann hier neue Perspektiven einbringen. (bw)