Suche
Agile Codewartung

Forever young: Codequalität in Alt- und Wartungsprojekten

Daniel Winter
©Shutterstock/ natalia bulatova

In jedem neuen Softwareprojekt wird, oder zumindest sollte es so sein, der Qualitätssicherung von Beginn an ein hoher Stellenwert zugeschrieben. Dies umfasst nicht nur eine hohe Testabdeckung der fachlichen Logik, sondern spiegelt sich auch im Aufbau des Quellcodes selbst wider. Qualitätssicherung in Alt- und Wartungsprojekten, die bereits ein Jahrzehnt oder länger existieren, ist hingegen eine zeitintensive Mammutaufgabe, deren Einführung gut überlegt sein will. Allgemeine Probleme und erste Lösungsansätze mit Checkstyle, PMD und einem Code Formatter stehen im Fokus des ersten Teils dieser Artikelserie.

Sobald alle Stories abgeschlossen, Features programmiert, Testfälle bestanden sind und die Software erfolgreich beim Kunden ihren Dienst verrichtet, beginnt die längste und wohl zäheste Phase im Softwareleben: die Wartung. Diese ist meist durch das Beheben von Fehlern gekennzeichnet (Abb. 1). Dabei kann zu Beginn noch auf das umfassende Fachwissen der ursprünglichen Entwickler zurückgegriffen werden, die nicht nur bestimmte Architekturentscheidungen getroffen haben, sondern auch die fachliche Qualifikation besitzen, auftretende Fehler schnell zu identifizieren und zu beheben. Mit der Zeit jedoch geht dieses Wissen verloren, sei es durch die Bearbeitung von zahlreichen neuen Projekten oder durch die Fluktuation bei den Mitarbeitern selbst. Neue, motivierte Entwickler kommen mit der nun schon in die Jahre gekommenen Software in Kontakt und müssen die aktuellen Fehler bearbeiten oder sogar neue Erweiterungen implementieren und testen.

 

Artikelserie
Teil 1: Erste Lösungsansätze mit Checkstyle, PMD und einem Code Formatter
Teil 2: Höhere Testabdeckung mit Mockito

Abb. 1: Lebenszyklus der Software, unterteilt in Entwicklung und Wartung

Never change a running system

Am einfachsten und für die neuen Mitarbeiter wahrscheinlich auch am reizvollsten wäre eine komplette Neuimplementierung der Anwendung. Nicht nur könnte man so alle alten Fehler korrigieren; es bestünde auch noch die Möglichkeit, moderne Technologien einzusetzen, neue Frameworks zu evaluieren und aktuelle Entwurfsmuster (Design-Patterns) in die Architektur mit einfließen zu lassen. Insgesamt, so die Intention, würde die Anwendung schneller reagieren, besser aussehen, Erweiterungen wären einfacher zu realisieren und das verloren gegangene Fachwissen im Unternehmen wäre wiederhergestellt.

Die Realität sieht jedoch anders aus. Anwendungen, die bereits seit Jahren erfolgreich und ohne größere Probleme in Unternehmen eingesetzt werden, werden nicht einfach ausgetauscht. Dabei spielen nicht nur die auftretenden Kosten einer kompletten Neuentwicklung eine Rolle. Hinzu kommt, dass der Kunde schlicht keinen Sinn darin sieht, ein Produkt ein zweites Mal entwickeln zu lassen, das bereits existiert, tief in die eigene Systemlandschaft integriert ist und mit dem die Mitarbeiter nach langer Einarbeitungszeit vertraut sind. Getreu dem Motto „Never change a running system“.

Aufmacherbild: Inscription on a blackboard FOREVER YOUNG von Shutterstock / Urheberrecht: natalia bulatova

[ header = Seite 2: Mit Ausdauer zur Codequalität ]

Mit Ausdauer zur Codequalität

Die Hauptaufgabe der neuen Entwickler ist es deshalb, das alte System am Leben zu erhalten. Um Fehler zu beheben, muss man die grundlegende Architektur kennenlernen, sich durch Dutzende Klassen debuggen, Kommentare studieren und vielleicht sogar mehrere hundert Seiten lange Dokumentationshandbücher wälzen. Es muss sich schlicht eingearbeitet und das Projekt kennengelernt werden. Dass man nicht die erste Person ist, die diese Wege beschreitet, wird spätestens am Quellcode deutlich. Wenn viele Entwickler zu unterschiedlichen Zeiten an einem Projekt arbeiten, ohne dass eine einheitliche Formatierung definiert ist, gibt es mindestens doppelt so viele Codestile. Ebenso können mit der Zeit verschiedene Architekturmuster bei der Erweiterung eingesetzt worden sein. Als engagierter Mitarbeiter verspürt man nun natürlich den Drang, diese Dinge zu ändern, die Codestile zu vereinheitlichen und Methoden oder ganze Klassen einem Refactoring zu unterziehen, um die unter Umständen verloren gegangene Architektur wieder hervorzuheben. Bevor aber begonnen und versucht wird, die Applikation an mehreren Fronten zu verbessern, sollte zunächst folgende Frage gestellt werden: Ist die Verbesserung der Codequalität für mein Projekt sinnvoll und überhaupt umsetzbar?

Um diese Frage zu beantworten, muss zuerst Klarheit geschaffen werden, ob die benötigte Zeit genehmigt wird. Die Codequalität in einem Altprojekt zu verbessern, ist zeit- und arbeitsintensiv, besonders dann, wenn nebenbei noch die tägliche Arbeit verrichtet werden muss. Ein ausreichend großes Zeitbudget, verbunden mit der nötigen Ausdauer des dazugehörigen Personals, bilden die Grundvoraussetzungen für die Phase der Qualitätsverbesserung – zwei Ressourcen, die eigentlich immer zu knapp bemessen sind. Ein weiterer wichtiger Punkt ist die Planungssicherheit in Bezug auf die weitere Nutzung der Anwendung beim Kunden für die nächsten Jahre. Der eigentliche Sinn, die Codequalität zu steigern, besteht darin, die spätere Erweiterung und Fehlerbehebung zu vereinfachen und zu beschleunigen. Besitzt man nun neben dem eigenen Willen, etwas zu verbessern, auch noch ein gewisses Kontingent an Zeit und Budget, könnte die eigentliche Arbeit doch beginnen, oder?

Erfahrung, Motivation, Konsequenz und Zurückhaltung

Nein. Änderungen an einem Altprojekt, sei es durch unsachgemäße, manuell durchgeführte Codeformatierung oder durch Refactoring, bergen immer Gefahren. So kann allein das falsche Einfügen von Klammern in if-else-Anweisungen die gesamte Programmlogik verändern. Vielleicht mag so ein Fehler auf den ersten Blick banal erscheinen und erfahrenen Entwicklern in normalen Projekten auch nicht passieren. Aber bei Altprojekten ist es nicht unüblich, tausende Zeilen schlecht formatierter Klassen zu bearbeiten. Wenn dann auch noch gleichzeitig beim Kunden ein Fehler im Produktivsystem zu beheben ist, kann schnell der Überblick verloren gehen. Im Nachhinein die entsprechende Codezeile zu identifizieren kann sehr aufwändig und mitunter auch frustrierend sein. Wenn schon das Verändern von einzelnen Zeilen eine gewisse Gefahr birgt, muss beim Refactoring von Methoden oder ganzen Klassen erst recht vorsichtig agiert werden. Alter Code enthält zahlreiche, unkommentierte Bugfixes und Änderungen in der Fachlogik, die in den Dokumentationen und Handbüchern nicht kommentiert sind. Zusätzlich besteht die Gefahr, dass neue Fehler eingebaut, aber erst im Betrieb sichtbar werden. Fehlen dazu auch noch automatisierte Tests, ist ein Refactoring recht kompliziert. Es sollte dann nur in den wenigsten Fällen erfolgen und einen triftigen Grund voraussetzen. Weiterhin müssten bei einem umfassenden Refactoring die nötigen regressiven Integrationstests ausgearbeitet und implementiert werden, um eine vollständige Funktionsabdeckung aller Sonderfälle zu gewährleisten. Bei dem Einsatz neuer Technologien und Frameworks ist in der Planung der zusätzliche Zeitaufwand für das Lösen unbekannter Probleme mit einzubeziehen.

Bevor also die Codequalität verbessert werden kann, benötigt man die erforderliche Erfahrung mit dem Altsystem, um mögliche Auswirkungen abzuschätzen. Eine intensive Einarbeitung ist somit unerlässlich. Wurde der Prozess der Codequalitätsverbesserung erst begonnen, muss der Entwickler fokussiert auf sein Ziel sein und motiviert bleiben, da das Verändern von altem Code für die meisten nicht zu den interessantesten Aufgaben in der Softwareentwicklung gehört. Hat man schlussendlich die Phase erreicht, um alle Änderungen in das Produktivsystem zu überführen, sind eine umfassende Qualitätssicherung und die Beachtung von ITIL-Prozessen zur Vermeidung von Fehlern und Ausfällen essenziell.

[ header = Seite 3: Check and Style ]

Check and Style

Ein erster Schritt zur Verbesserung der Codequalität ist die Überprüfung und Anpassung der Software mit dem statischen Codeanalysetool Checkstyle [2]. Dabei setzt Checkstyle auf einen fest definierten Regelsatz, um einen einheitlichen Programmierstil sicherzustellen. Es existieren Regeln für verschiedene Komponenten, wie für:

Checkstyle kann dabei als Plug-in direkt in Eclipse, Hudson und Jenkins integriert werden. In Eclipse bietet sich dem Entwickler die Möglichkeit, seinen geschriebenen Quellcode sofort auf Regelverstöße zu überprüfen und zu ändern. Eine Einbindung von Checkstyle in den Continuous-Integration-Prozess und damit in die Daily Builds bietet den Vorteil, dass das gesamte Projektteam auf die Fehler aufmerksam wird und diese sofort beheben kann.

Die eigentliche Hauptaufgabe ist es aber nun, den entsprechenden Regelsatz für das Altprojekt zu definieren. Bei neuen Softwareprojekten werden die Regeln entweder vom Kunden vorgegeben oder im Projektteam aufgestellt. Die spätere Software wird damit auf Basis dieser Regeln programmiert. Bei einem Altprojekt ist das der falsche Ansatz. Hier liegt ein über die Jahre gewachsenes System vor, bei dem zahlreiche Entwickler bereits ihre Vorstellungen eines guten (oder schlechten) Programmierstils umgesetzt haben. Würde der Standardregelsatz oder der eines aktuellen Projekts verwendet, wären die Regelverletzungen zu zahlreich, um sie zu beheben. Eine Anwendung der Standardregeln von Checkstyle in meinem eigenen Wartungsprojekt führte zu dem Ergebnis, dass über 80 000 Verstöße vorlagen. Kein Entwickler hätte wohl die Zeit oder die Motivation, so umfangreiche Veränderungen vorzunehmen. Wahrscheinlich könnte danach niemand mehr die Korrektheit der Software gewährleisten. Wie aber ist es dann möglich, Checkstyle in einem Altprojekt einzusetzen?

Die Lösung ist, die Regeln basierend auf der Anwendung zu erstellen. Auf den ersten Blick sieht dieses Vorgehen nicht sehr vielversprechend und erfolgreich aus. Man möchte meinen, dass Regeln, die das aktuelle Altsystem widerspiegeln und dessen Fehler nicht aufzeigen, überflüssig wären. Der Vorteil wird erst durch den kombinierten Einsatz mit einem Codeformatierungstool ersichtlich. Dazu sollte die Konzentration zuerst auf die Regeln gelegt werden, die sich mit der reinen Codeformatierung befassen, wie der Überprüfung von Codeblöcken und der Verwendung von Leerzeichen. Hier kann der aktuelle Entwickler oder das Projektteam, unabhängig vom bisher eingesetzten Programmierstil, die eigenen Vorstellungen umsetzen. Eine einheitliche Klammersetzung bei Methodendefinitionen und Anweisungen erhöhen die Lesbarkeit enorm. Überhaupt ist eine konsequente Klammersetzung (auch bei einzeiligen Anweisungen) durchaus sinnvoll, da Methoden in Altprojekten oft die Eigenschaft haben, mehrere hundert Zeilen lang zu sein. Dadurch erhöht sich nicht nur die Übersichtlichkeit. Der Quellcode kann auch einen Teil seiner Komplexität verlieren, weil zum Beispiel Berechnungen für eventuelle Sonderfälle klarer abgegrenzt sind. Weitaus vorsichtiger müssen die Regeln behandelt werden, die folgende Aspekte überprüfen:

  • Klassendesign
  • Zugriffsmodifikatoren
  • Größe von Dateien, Klassen und Methoden
  • Namenskonventionen
  • Java-Kommentare

Hier empfiehlt sich ein prototypischer Testlauf mit den Standardvorgaben der Regeln. Das Ergebnis ist in den meisten Fällen wohl katastrophal, da mit Sicherheit gegen fast alle Regeln wiederholt verstoßen wird. Prädestiniert sind dabei besonders die Größenüberprüfungen und die Zugriffsmodifikatoren. Es werden zahlreiche Methoden und Klassen existieren, die die vorgegebene maximale Größe weit überschreiten oder Redundanz bei den Zugriffsmodifikatoren aufweisen (so sind z. B. Methoden in Interfaces automatisch public und abstract). Hier stehen einem nun drei Lösungsmöglichkeiten zur Verfügung: Zum einen kann man die Regel anpassen, etwa indem man die maximal erlaubte Methodenlänge erhöht, zum anderen den Code, indem zum Beispiel die Methode refactored wird. Oder man entfernt die Regel aus dem Regelsatz. Das umfassende Refactoring aufgrund von Checkstyle ist meist keine Option. Würde dieser Ansatz dauerhaft verfolgt, wäre die aufzuwendende Zeit wohl selbst vor dem eigenen Management kaum zu vertreten und sollte deshalb am besten unterbleiben. Nur wenn sich der manuell zu tätigende Aufwand in Grenzen hält und die Codequalitätsverbesserung signifikant ist, kann in einigen Fällen ein Refactoring durchgeführt werden. Die Anpassung der Regel hört sich logischer an, ist aber auch nur begrenzt umsetzbar. Eine maximale Methodenlänge von 1 000 Zeilen würde zwar den alten Code nun regelkonform erscheinen lassen, hätte aber keinen Mehrwert für neu geschriebenen Quellcode, weil dieser die Regel immer erfüllen würde. Kann also weder der Quellcode noch die Regel angepasst werden, ist das einzig sinnvolle Mittel, die Überprüfung aus dem Regelsatz zu entfernen. Warnungen, die nicht behoben werden können, demotivieren, erfüllen keinen Zweck und sind daher zu vermeiden. Weiterhin könnten Warnungen, die neuen Quellcode betreffen, aus der schieren Masse nur schwer identifiziert werden.

Der Regelsatz für die Java-Kommentare ist wohl nur in den wenigsten Altprojekten noch einsetzbar. Zu häufig werden Kommentare überhaupt nicht geschrieben oder wichtige Bugfixes im Quellcode nicht kenntlich gemacht. Sollten Sie aber dennoch das Glück haben, durchgehend Javadoc-Kommentare in Ihrem Projekt zu besitzen, führen Sie diese weiter, aktualisieren Sie sie und sichern Sie sich zusätzlich mit möglichst vielen Checkstyle-Regeln ab (Listing 1.). Der Quellcode und die dazugehörigen Kommentare sind meist der erste und manchmal auch der einzige Einstiegspunkt bei Problemen in einem Altprojekt. Javadoc und zusätzliche Kommentare helfen auch, schwer verständlichen Quellcode zu begreifen. Eine weitere wichtige Regel für ein Altprojekt ist das Verbieten der Annotation @unused (Listing 2). Mit diesem simplen Mechanismus kann relativ einfach sichergestellt werden, dass als unbenutzt deklarierter Code gelöscht wird. Nach einem gewissen Zeitraum wird kein Entwickler solchen Code wiederverwenden, weil die technischen und/oder fachlichen Gründe, die dazu führten, dass der Quellcode keine Verwendung mehr findet, unklar sind.

<!-- Überprüft Javadoc bei public – Klassen und Interfaces  -->
<module name="JavadocType">
  <property name="scope" value="public"/>
</module>

<!-- Überprüft Javadoc bei public - Methoden, dabei brauchen RuntimeExceptions nicht beschrieben werden-->
<module name="JavadocMethod">
  <property name="scope" value="public"/>
  <property name="allowUndeclaredRTE" value="true"/>
</module>

<!-- Überprüft Javadoc bei private, protected und public - Variablen -->
<module name="JavadocVariable"/>
<!-- Erlaubt alle Warnungen, bis auf @unused  -->
<module name="SuppressWarnings">
  <property name="format" value="^unused$"/>
</module>

[ header = Seite 4: Automatisch formatieren ]

Automatisch formatieren

Bei der Erstellung eines brauchbaren Checkstyle-Regelsatzes für Altprojekte gibt es kein Patentrezept. Vielmehr werden sich die Regeln von Projekt zu Projekt unterscheiden, basierend auf dem zugrunde liegenden Quellcode. Um trotzdem eine Codequalitätsverbesserung mit Checkstyle zu erreichen, ist ein hohes Maß an Erfahrung mit dem Altprojekt vonnöten, sowie Zeit, um abzuwägen, ob die einzelnen Regeln anwendbar sind. Hat man sich schließlich auf einen Regelsatz geeinigt, muss sichergestellt werden, dass alle Klassen eine regelkonforme Formatierung aufweisen. Dies kann mit einen Codeformatierungstool erreicht werden, wie es beispielsweise in Eclipse standardmäßig integriert ist (Abb. 2, Window | Preferences | Java | Code Style | Formatter).

Abb. 2: Codeformatierungstool in Eclipse

In den angezeigten Menüpunkten können die durch Checkstyle festgelegten Regeln in das entsprechende Profil eingetragen (Abb. 3) und nach erfolgreichem Anlegen (oder Editieren) die Formatierung auf das gesamte Projekt angewendet werden. Für die verbliebenen Warnungen kann entweder eine manuelle Anpassung oder die Entfernung aus dem Regelsatz erfolgen.

Abb. 3 Einstellungen des Codeformatierungsprofils

Ein Prise PMD

Neben Checkstyle kann eine weitere Überprüfung des Altprojekts mit PMD [1] erfolgen. Während Checkstyle sein Hauptaugenmerk auf die Formatierung des Quellcodes richtet, analysiert PMD den Programmierstil, wie das Design der Klassen, und gibt Empfehlungen auf Basis von Best Practices. PMD ist ebenfalls als Eclipse-, Jenkins- und Hudson-Plug-in [4] verfügbar. Im Gegensatz zu Checkstyle (in Verbindung mit einem Codeformatierungstool) besteht hier jedoch kaum die Möglichkeit, automatisiert Änderungen vorzunehmen und das Altprojekt an die PMD-Regeln anzugleichen. Um manuelle Anpassungen zu vermeiden, empfiehlt sich eine alternative Vorgehensweise. Anstatt aufwändig alle Regeln wie bei Checkstyle auf deren mögliches Verbesserungspotenzial für den Quellcode zu überprüfen, sollte der PMD-Regelsatz eines ähnlichen, aktuellen Softwareprojekts als Grundlage dienen. Eine erste Filterung von wichtigen Regeln wurde von dem dortigen Projektteam bereits getroffen. Gleichwohl müssen die Regeln natürlich ein zweites Mal überprüft werden. Hierfür bietet sich die direkte Darstellung in Eclipse an (Abb. 4, Window | Preferences | PMD | Rules Configuration).

Abb. 4: PMD-Regeln

Nicht nur, dass die Regeln nach Kategorien sortiert sind, auch eine kurze Beschreibung wird angezeigt, die meist neben dem aussagekräftigen Regelnamen selbst ausreicht, um den Zweck der Überprüfung zu verstehen. Durch ausreichend Erfahrung mit dem Quellcode des Altprojekts sollte es dem Entwickler möglich sein, alle Regeln für das Altprojekt ohne umfangreiche Analyse und Suche in eine der folgenden drei Kategorien einzuteilen:

  • Sinnvolle und zutreffende Regeln
  • Zu entfernende Regeln
  • Regeln, die möglicherweise entfernt werden sollten und/oder bei denen eine weitere Verifizierung sinnvoll erscheint

Dabei können die letzten zwei Listen recht schnell anwachsen. Sollten Regeln in die letzte Kategorie einsortiert werden, muss genau abgewogen werden, inwieweit manuelle Formatierungen oder Refactorings sinnvoll und zeitlich machbar sind. Besonders Regeln der folgenden Kategorien sollten besser entfernt werden, als den Versuch zu unternehmen, das Altprojekt anzupassen:

  • Design
  • Migration
  • Quellcodegröße

Als Vorzeigebeispiele können hier wiederum die Regeln genannt werden, die die Länge von Methoden und Klassen oder die Anzahl der Übergabeparameter einer Methode überprüfen. Berechtige Ausnahmen bilden, wie bei Checkstyle, die Regeln, die unbenutzten Code identifizieren (Listing 3). Nicht nur, dass das Löschen zügig erledigt ist, es wird ebenfalls die Übersichtlichkeit in den Klassen verbessert und die Einarbeitung für neue Mitarbeiter erleichtert. Analog zu Checkstyle gibt es auch für PMD keinen allgemeingültigen Regelsatz. Vielmehr muss man darauf vorbereitet sein, eine hohe Anzahl an Regeln zu entfernen. Dadurch kann aber immer noch eine, wenn vielleicht auch nur geringe, Codequalitätsverbesserung erreicht werden.

<!-- Identifiziert unbenutzten Quellcode  -->
<rule ref="ruleset/unusedcode.xml/UnusedPrivateField"/>
<rule ref="ruleset/unusedcode.xml/UnusedLocalVariable"/>
<rule ref="ruleset/unusedcode.xml/UnusedPrivateMethode"/>
<rule ref="ruleset/unusedcode.xml/UnusedFormalParameter"/>

[ header = Seite 5: Ausblick ]

Ausblick

Im zweiten Teil der Artikelserie zeige ich, wie mit dem Framework Mockito [5] die Testabdeckung von neuem und altem Quellcode erhöht und verbessert werden kann. Weiterhin erkläre ich, wie man am besten mit alten fachlichen und technischen Fehlern umgeht und warum eine umfangreiche Dokumentation nicht nur der Zierde dient. Abschließend werden Möglichkeiten aufgezeigt, um die verbesserte Codequalität auch bei zukünftigen Erweiterungen und Fehlerbehebungen zu erhalten.

Geschrieben von
Daniel Winter
Daniel Winter
Daniel Winter studierte Informatik an der FH Zittau/Görlitz und arbeitet seit 2011 als Consultant für Softwareentwicklung bei der Saxonia Systems AG (daniel.winter@saxsys.de). Sein aktueller Fokus liegt in den Möglichkeiten der Codequalitätsverbesserung bei Projekten jedweder Art. Ausgiebige Erfahrungen in diesem Bereich sammelte er bei der Wartung einer Leasing-Refinanzierungssoftware.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.