Aufräumen macht glücklich und sorgt für lange Wartbarkeit
Mit Refactoring für Ordnung im Quellcode sorgen
Ein regelmäßiges und gründliches Refactoring des Quellcodes sorgt für eine gute Struktur und Wiederverwendbarkeit. Im Haushalt und am Arbeitsplatz ist das Aufräumen eine Selbstverständlichkeit, da man nur an einem sauberen und aufgeräumten Arbeitsplatz gute Ergebnisse erzielen kann. In der Regel ist während der laufenden Arbeit zum Aufräumen zu wenig Zeit. Man muss sich diese extra einplanen. Bei der Softwareentwicklung ist es nicht anders. Das Aufräumen des Quellcodes nennt man Refactoring. Es ist nahezu genauso wichtig, wie die eigentliche Entwicklung, denn damit sichert man die Möglichkeit einer langfristigen Weiterentwicklung und Pflege der Applikation.
Refactoring ist ein Prozess zur Umstrukturierung des Quellcodes unter der Beibehaltung der Funktionalität. Darunter versteht man viele kleine Anpassungen, welche zu einer spürbaren Steigerung der Codequalität führen. Auch kleinere Fehler und typische Nachlässigkeiten können bei umfangreichen Projekten, welche ständig angepasst und weiterentwickelt werden, zu erheblichen Problemen führen. Einige Beispiele:
- Fehler bei der Bezeichnung von Objekten und Variablen
- Duplikate im Quellcode, entstanden durch Copy-and-paste
- Mängel in der Struktur der Klassen und der Zuordnung von Objekten
- fehlende oder unvollständige Kommentare
- unterschiedliche Logik beim Aufbau von Klassen.
Um diese und ähnliche Mängel zu beseitigen, kommen Methoden, Tools und Best Practices aus dem Bereich des Refactorings zum Einsatz. Darum geht es in diesem Artikel. Wir nennen Ansatzpunkte, wie man den Quellcode analysiert und mit den richtigen Tools effizient für Ordnung sorgt. Durch Refactoring wird die Lesbarkeit des Quellcodes verbessert. Die Möglichkeit, diesen später erneut zu verwenden, nimmt zu und außerdem können Anpassungen und Erweiterungen deutlich schneller und leichter durchgeführt werden. An erster Stelle geht es darum, Redundanzen und nicht mehr benötigte Codeabschnitte zu eliminieren. Das Motto lautet: "Remove dead Code". Die Praxis zeigt, dass der Quellcode oft mit sich wiederholenden bzw. nicht mehr benötigten Abschnitten überladen ist. Eine der häufigsten Ursachen dafür sind Copy-and-paste-Operationen. Dabei versucht man die Funktionalität von einer auf eine andere Stelle im Quellcode zu übertragen. Es handelt sich i. d. R. dabei jedoch nicht um eine 1:1-Kopie. Kleinere Teilbereiche, zum Beispiel Objekte, Variablen und andere Bezeichner werden angepasst. So entstehen die Dopplungen. Da während des laufenden Betriebs die Zeit für die Überarbeitung und Bereinigung des Codes oft knapp ist, nimmt man sich meist vor, diese Dopplungen später zu beseitigen, d. h. die Funktionen beispielsweise generisch nutzbar mit Hilfe von Parameter zu gestalten. Oft ist es leider in der Praxis der Fall, dass die Überarbeitung wegen Mangel an Zeit bzw. Ressourcen immer wieder verschoben und dann eventuell doch vergessen wird. Das Ergebnis: Unnötige Doppelungen, unübersichtliche Codeabschnitte und damit einhergehend ein höherer Pflege- und Wartungsaufwand.
Technische Ansatzpunkte
Vor allem ungenau bezeichnete Variablen erschweren die Lesbarkeit und Verständlichkeit des Quellcodes. Je mehr Variablen und andere Bezeichner ein Abschnitt enthält, desto schwieriger ist es, den Gesamtüberblick zu behalten und dem Lesefluss zu folgen. Refactoring zielt darauf ab, wenig aussagekräftige Bezeichner so anzupassen, dass jede Variable bzw. Funktion genau das macht, was der Betrachter vom Namen erwartet. Überladene Verzweigungen und verschachtelte Schleifen sorgen ebenso für Verwirrung im Code. Funktionen und Methoden dienen dazu, die Businesslogik einer Anwendung auf einzelne Bestandteile aufzuteilen. Ist diese Aufteilung nicht granular genug, sind die betreffenden Elemente zu unübersichtlich. Ein Hinweis darauf sind "überlange" Funktionen, welche sich über viele Quellcodezeilen erstrecken. Ein häufiger Mangel liegt auch vor, wenn eine Methode bzw. Funktion für mehrere Dinge verantwortlich ist. Im Buch "Five lines of Code" spricht der Autor von maximal fünf Zeilen Quellcode in einer Funktion [1]. Ob man das so strikt betrachten muss, dass ist zunächst nachrangig. Korrekt ist jedoch, dass zu lange Quelltextabschnitte schwer verständlich sind. Komplizierte Konstrukte sollen überarbeitet und so zerlegt werden, dass diese nachvollziehbar und verständlich sind. Wenn innerhalb einer Methode Fallunterscheidungen stattfinden, sollte das am Anfang geschehen. Das macht den Codeabschnitt deutlich besser lesbar. Trifft man auf eine Situation, in der eine Methode erst in der Mitte einer Fallunterscheidung angebracht ist, dann kann es von Vorteil sein, die Methode aufzusplitten. Auch die Gestaltung von Kommentaren beeinflusst die Lesbarkeit und Übersichtlichkeit des Codes. Kommentare dienen dazu, das Verständnis einer Funktion, einer Klasse oder eines Algorithmus zu erhöhen. Bei Verwendung von aussagekräftigen Namen kann man auf Kommentare i. d. R. verzichten. Auch veraltete Kommentare und auskommentierte Quellcodereste sollen gelöscht werden.
Das Ziel eines Refactorings ist es, für einen besseren "Geruch" des Quellcodes zu sorgen.
Im Rahmen des Refactorings spricht man oft von den sogenannten Code-Smells ("Gerüchen"). Gemeint sind damit funktionierende, aber schlecht strukturierte, schwer lesbare und verwirrende Quellcode-Abschnitte. Man unterscheidet zwei Kategorien von Code-Smells: klassenübergreifende und klasseninterne Code-Smells. Zu den klassenübergreifenden Code-Smells gehören beispielsweise parallele Vererbungshierarchien oder alternative Klassen, welche das Gleiche tun, jedoch an unterschiedlichen Stellen angewendet werden. Klasseninterne Code-Smells beziehen sich beispielsweise auf umfangreiche Parameterlisten, temporäre Felder, Kommentare ohne Funktion, Code-Duplikate, lange Methoden usw. Das Ziel eines Refactorings ist es, für einen besseren "Geruch" des Quellcodes zu sorgen, d. h. die Code-Smells zu entfernen.
In der Praxis findet Refactoring auf unterschiedlichen Ebenen statt, von lokal bis abstrakt und global. Dabei kommen verschiedene Techniken zum Einsatz. Red-Green-Refactoring und Branch by Abstraction sind zwei davon. Die Red-Green-Refactor-Technik hat einen Bezug zur testgetriebenen Entwicklung (Test-driven Development = TDD). Der Ablauf der Programmierung bei TDD wird in drei Phasen unterteilt, die sich zyklisch wiederholen. Das Refactoring nimmt dabei einen festen Platz ein (Abb. 1). In der ersten Phase wird ein Test geschrieben, der zunächst fehlschlägt. In der zweiten Phase wird Produktivcode erstellt. Dieser wird so lange überarbeitet bzw. verbessert, bis der Test erfolgreich durchläuft. Im dritten Schritt werden Test und Produktivcode im Rahmen des Refactorings überarbeitet.
Brunch by Abstraction findet Verwendung, wenn eine große Menge an Refactoring-Aufgaben anstehen. So kann ein System in Schritten überarbeitet werden. Anpassungen werden bereits umgesetzt, während weitere Änderungen noch im Gange sind. Dafür verwendet man eine Abstraktionsschicht, welche die Koexistenz mehrerer Implementierungen im Softwaresystem ermöglicht. Abb. 2 verdeutlicht die schrittweise Vorgehensweise.
Tools
Wie man bereits ahnen kann, stellt Refactoring keine einfache Aufgabe dar. Eine umfassende Umgestaltung des Quellcodes kann man am besten mit Hilfe von Werkzeugen vornehmen. Diese können sowohl bei der Analyse des bestehenden Quellcodes als auch beim eigentlichen Refactoring genutzt werden. Die Werkzeuge müssen direkt in den Entwicklungsprozess integriert werden. Sie stehen deshalb als Erweiterungen bzw. Plugins für die gängigen Entwicklungsumgebungen zur Verfügung. Ein weiterer wichtiger Punkt ist die verwendete Programmiersprache. Die Tools sind meist auf eine oder einige Sprachen ausgerichtet. Einige Beispiele:
- jSparrow: Es handelt sich um ein Plug-in für die Entwicklungsumgebung Eclipse. Das Tool kann in folgenden Punkten helfen: Minimierung von Code-Smells, Upgrade auf neue Java-Versionen (Umwandlung in neue Sprachkonstrukte) und Leistungsverbesserungen durch die Verwendung effizienterer Konstrukte [2].
- VisualAssist: Diese Extension für Visual Studio macht Vorschläge zur Verbesserung des Quellcodes. Unterstützt werden die Programmiersprachen C/C++ und C#. Sie bietet Refactoring-Befehle und grundlegende Syntaxfehler sowie Unterstützung für die Rechtschreibprüfung [3].
- ReSharper: Dies ist ein Analyse- und Refactoring-Tool für die Codequalität der Programmiersprachen C#, VB.NET, XAML, ASP.NET, ASP.NET MVC, JavaScript, TypeScript, CSS, HTML und XML. Das Tool integriert sich in Visual Studio [4].
Die Auswahl der Tools ist groß. Neben den genannten Kriterien wie Programmiersprache und unterstützter Entwicklungsumgebung unterscheiden sie sich in Funktionsumfang und Preis. Beispielhaft wird nachfolgend das Plugin Visual Assist für Visual Studio vorgestellt. Visual Assist unterstützt das Programmieren in den Sprachen C, C++ und C#. Durch die unmittelbare Integration in Visual Studio (Extension) benötigt man kein weiteres Tool, sondern kann es direkt und auch während der laufenden Entwicklung verwenden. Das Tool bietet umfassende Unterstützung bei den typischen Basisaufgaben des Refactoring, wie Navigation, Code Generation, Code Assistance, Code Understanding, Code Correction, Code Inspection und Code Snippets (Abb. 3).
Dazu zwei Beispiele: Eine Hauptfunktion liegt auf dem Bereich "Suche und Navigation" im Quellcode. In größeren Projekten besteht eine besondere Herausforderung darin, dass man sich im Quellcode schnell zurechtfindet. Werden Namen von Klassen, Variablen, Eigenschaften usw. oder Codeabschnitte angepasst, muss man schnell herausfinden, an welcher Stelle das betreffende Objekt noch verwendet wird. Visual Assist bietet dazu unterschiedliche Unterstützung. Mittels "Find by Context" werden Verweise auf ein Symbol anhand seines Kontext gesucht. Ein weiteres nützliches Feature ist die Suche nach Dateien nach einem bestimmten Muster. Nehmen wir an, wir benötigen für neue Features eine neue Datei. Hatten wir diese Idee nicht gerade schon gestern? Hatten wir die Datei bereits angelegt? In großen Projektordnern beginnt jetzt die Suche… Tippen wir dazu im Dialogfeld einen relevanten Auszug aus dem Dateinamen ein und das Suchergebnis wird blitzschnell angepasst.
Ebenfalls zur Steigerung der Quellcodequalität tragen die Funktionen Codeüberprüfung und automatische Korrektur bei. Bei der Codeüberprüfung wird während der Eingabe automatisch auf spezielle Qualitätsprobleme geprüft. Die Funktion ermittelt bzw. behebt typische Programmierfehler wie beispielsweise Formatfehler, die fehlerhafte Programmierung gegen Schnittstellen usw. Die automatische Korrektur korrigiert direkt bei der Eingabe, speziell Fehler in der Symbol- und Zeigerschreibweise. Beispielsweise kann alles in Kleinschreibung eingegeben werden und Visual Assist korrigiert die Groß-/Kleinschreibung aller Symbole automatisch.
Auch die Effizienz der Codierung kann mit Hilfe eines solchen Tools gesteigert werden. Auch dazu ein Beispiel aus der Praxis: Microsoft verwendet bei seinen User-Interface-Bibliotheken (WPF, UWP, MAUI, WinUI) eine deklaratorische Vorgehensweise [5]. Das UI wird nicht im Designer gestaltet oder in der Programmiersprache erstellt, sondern mittels eines XML-basierten Sprachdialektes, bezeichnet als XAML, definiert. In Visual Assist kann man Code-Fragmente auch für XAML definieren. Regelmäßig muss zum Beispiel für das Layout einer neuen Seite eine Gitterstruktur mittels <Grid/>-Element definiert werden. Dabei werden häufig vertikal drei Zeilen eingesetzt, d. h. Kopf- und Fußzeile mit einer festen Breite und der Rest des Bereiches verbleibt für den Content. In der horizontalen Ausrichtung werden dabei i. d. R. auch mehrere Spalten definiert. Was liegt also näher, als die Definition eines solchen <Grid/>-Elements als Vorlage zu speichern? Schreiben Sie den Quellcode einmal im XAML-Code-Editor, markieren Sie den Ausschnitt und rufen Sie das Dialogfeld Edit VA Snippets auf. Fügen Sie den Quelltextausschnitt im Bereich XAML als Vorlage hinzu und vergeben Sie einen Namen, zum Beispiel Grid3x3 (s. Abb. 4).
Ab sofort ist die Grundstruktur einer neuen View auf der Basis eines <Grid/>-Elements mit zwei Mausklicks angelegt.
Fazit
Auch wenn das Aufräumen des Quellcodes nicht den größten Spaß auslöst, ist es sinnvoll, Zeit in diese Aufgabe zu investieren. Am besten wird der Prozess des Refactorings direkt in den Entwicklungszyklus integriert, d. h. nur bereinigter Code darf in das Quellcode-Repository eingefügt werden. Auch die Last der technischen Schulden kann auf diese Weise reduziert bzw. vermieden werden. Ein regelmäßiges Refactoring, zerlegt in kleine Happen, sorgt für Nachhaltigkeit. Auch kleine Schritte führen dabei zum Ziel. Durch den Einsatz von Tools kann man sich lästige Routineaufgaben deutlich vereinfachen.
- C. Clausen: Five lines of Code – Das Praxisbuch für Refactoring und Clean Code
- jSparrow
- VisualAssist
- ReSharper
- WPF | UWP | MAUI | WinUI
Wikipedia: XAML
Stefan Lieser
am 09.02.2023Andreas
am 10.02.2023Schade um das späte Frühstück...