Der Saubermann [Knigge für Softwarearchitekten]

Peter Hruschka, Gernot Starke

©Shutterstock/Dima Sobko

Schlechter Code stinkt, verursacht Unwohlsein, Kopfschmerzen und eine Menge anderer übler Probleme. Schlechter Code kann in ganz verschiedenen Ausprägungen daherkommen – die wir mit unterschiedlichen Maßnahmen bekämpfen oder verbessern sollten. In dieser Folge des „Knigge für Softwarearchitekten“ möchten wir Ihnen den Saubermann vorstellen, der Chaos und Unordnung von schlechtem Code beseitigt.

Schlechter oder riskanter Code, das bedeutet unverständliche, ungeschickte, umständliche Programmierung, überschüssige Komplexität, verletzte Konventionen oder Idiome, Missbrauch der Programmiersprache oder deren falscher Einsatz. Und, Sie haben es schon vermutet, keine automatisierten Testfälle. Anti-Clean-Code, sozusagen. Falls Sie Ihr eigenes System jetzt schon wiedererkennen – willkommen im Club.

Dummerweise gibt es in manchen Systemen riesige Mengen von schlechtem Code, sodass der freundliche Saubermann erst suchen sollte, welche Stellen er bereinigen muss.

Kategorien des Codegrauens

Im Wesentlichen kennen wir folgende Kategorien schlechten Quellcodes:

  • Code, der zur Laufzeit des Systems Schmerzen oder Probleme verursacht, beispielsweise durch schlechte Performance oder übermäßigen Ressourcenverbrauch.
  • Code, der Änderungen am System erschwert oder verhindert, z. B. durch zu hohe Komplexität, Unverständlichkeit oder schlechte Schnittstellen.
  • Code, der gängige Idiome, Konventionen oder Best Practices verletzt, schlechte Bezeichner verwendet oder einfach nur mies aussieht.
  • Code, der deutlich anders als der Rest eines Systems geschrieben ist, beispielsweise in anderen Sprachen, mit anderen Frameworks, nach anderen Paradigmen oder Konzepten.
  • Nicht getesteter Code. [1] nennt den Legacy Code (Kasten: „Code ohne Tests = Legacy Code“).

Hinzu kommt noch riskanter Code – der nicht unbedingt schlecht sein muss, aber eine tickende Zeitbombe sein könnte. Zwei Aufgaben hat der Saubermann damit: Schmutz (d. h. schlechten oder riskanten Code) finden und zweitens – saubermachen.

Code ohne Tests = Legacy Code
Michael Feathers erklärt im Vorwort zu [1] sehr eindrücklich, was riskanter oder schlechter Code für ihn bedeutet:
„Code without tests is bad code. It doestn’t matter how well written it is; it doesn’t matter how pretty or object-oriented or wellencapsulated it is. With tests, we can change the behavior or ouf code quickly and verifiably. Without them, we really don’t know if our code is getting better or worse.“

Verschmutzung messen

Schlechten Code finden Sie am besten durch geeignete Metriken. Dabei sollten Sie eine Mischung aus organisatorischen Metriken, Laufzeitmessungen sowie statischen (Code-)Metriken kombinieren. Grundsätzlich macht es Sinn, Metriken immer über längere Zeiträume zu messen und ihre Entwicklung über die Zeit zu beobachten.

Beginnen wir mit zwei organisatorischen Metriken: Sie sollten einerseits messen, für welche Bausteine das Entwicklungsteam wie viel Zeit und Aufwand verwendet, andererseits für Ihre Bausteine die Anzahl der darin gemeldeten Fehler und Probleme kennen. (Relativ) hohe Zahlen rechtfertigen Umbau- und Verbesserungsmaßnahmen. In üblichen CI-Umgebungen werden diese Werte nicht gemessen – daher müssen Sie als Saubermann hier selbst tätig werden.

Stoppuhr und Co.

Als Nächstes messen Sie als Saubermann das Laufzeitverhalten Ihres Systems durch detailliertes Profiling: Messen Sie Laufzeiten einzelner Bausteine, die Häufigkeiten der Aufrufe und deren Speicher- oder Ressourcenverbrauch. Idealerweise messen Sie diese Größen regelmäßig (mindestens wöchentlich) in Last- oder Stresstests.

Auch hieraus können Sie „Reinigungsbedarf“ ableiten: Säubern Sie Bausteine, die besonders häufig aufgerufen werden, besonders lange Zeit oder viel Speicher benötigen.

Diese Ratschläge klingen völlig naheliegend – werden unserer Erfahrung nach in Projekten aber drastisch vernachlässigt – bis dann irgendwann die gesamte Performance nicht mehr stimmt und das Management nach Wundern ruft – die das Entwicklungsteam dann grundsätzlich bis vorgestern geliefert haben muss.

Höhe der Verantwortung

Kommen wir zur statischen Codeanalyse: Zur Identifikation von riskantem Code empfehlen wir Ihnen insbesondere die Messung der afferenten Kopplung. Sie gibt die Anzahl der eingehenden Abhängigkeiten an, also derjenigen Bausteine, die vom jeweils vermessenen Baustein abhängig sind. Anders ausgedrückt misst sie die Höhe der „Verantwortung“ eines Bausteins. Hohe afferente Kopplung bedeutet, dass im Fehler- oder Problemfall sehr viele andere Bausteine betroffen sind. In seinem brillanten Vortrag über Komplexität auf der JAX London hat Tim Berglund [2] gezeigt, dass in vielen Softwaresystemen die afferente Kopplung asymptotisch verteilt ist: Wenige Bausteine haben sehr hohe afferente Kopplungen, sehr viele Bausteine sehr niedrige.

Abb. 1: Bausteine und die afferente Kopplung

Bei den besonders stark afferent gekoppelten Bausteinen (also denen mit sehr hoher Verantwortung) können Sie dann fast beliebige andere Metriken oder Prüfungen anwenden [3]: Sämtliche Verstöße gegen Idiome, Konventionen oder gute Programmierpraxis haben bei afferent gekoppelten Bausteinen besonders gravierende Auswirkung – und sollten unbedingt „gesäubert“ werden! Messen können Sie die afferente Kopplung übrigens mit [4] oder [3].

Reinräume sind zu teuer

Die Produktion der Chipindustrie findet in Reinräumen statt – in denen es praktisch keinen Schmutz gibt – aber sehr hohe Kosten für Aufbau und Unterhalt dieser Reinräume.

In unseren Softwaresystemen benötigen wir nur in Ausnahmefällen solche klinisch reinen Bedingungen. Wir können uns durchaus etwas schmutzigen Code leisten – sofern dieser Code selten geändert werden muss und halbwegs gut durch automatisierte Tests abgesichert ist.

Unit- und Integrationstests sollten zum Handwerkszeug aller Entwickler und Softwarearchitekten gehören. Aktuelle Frameworks [3] besitzen hier eine Art Suchtfaktor: Einmal von Spock Framework und Co. angefixt, wollen Sie nie wieder anders arbeiten

Professionelle Reinigung

Zu fachlich oder technisch relevantem Code gehören automatisierte Testfälle (siehe Feathers, Michael: „Working Effectively with Legacy Code“, Prentice Hall, 2005 und Textkasten). Falls Sie riskanten oder schlechten Code gefunden haben, sollten Sie zuerst priorisieren: Welche Codestellen enthalten die größten Probleme, tragen die höchste Verantwortung, benötigen die meiste Laufzeit, verursachen die meisten Fehler oder den höchsten Pflegeaufwand. Diese Bausteine bearbeiten Sie dann gemäß dem folgenden Verfahren (angelehnt an Feathers und Feathers, Michael: „Working Effectively with Legacy Code“, Prentice Hall, 2005, ein großartiges Buch – das Gernots Meinung nach ):

  1. Identifizieren Sie die als nächstes zu ändernde Codestelle.
  2. Finden Sie geeignete Punkte (Codestellen), an denen Sie diese Codestelle testen können. Definieren Sie Testfälle dazu.
  3. Lösen Sie vorhandene Abhängigkeiten von dieser Codestelle auf, sodass Sie Testfälle implementieren können.
  4. Erst jetzt ändern Sie den ursprünglichen Code und refaktorisieren.

Insbesondere das Auflösen von Abhängigkeiten ist in der Praxis eine schwierige und oft langwierige Aufgabe, die wir hier leider nicht ausführlicher erläutern können.

Fazit

Finden Sie riskanten oder schlechten Code durch eine Kombination technischer und organisatorischer Metriken: Beobachten Sie Abhängigkeiten und Komplexität, Fehlercluster und Laufzeit-/Performancewerte. Korrelieren Sie diese Metriken: Bausteine, die in mehr als einer der Metriken schlecht abschneiden, sollten Sie mit hoher Priorität verbessern (Stichwort: Refactoring).

Falls Sie in Ihren Systemen keinen schlechten oder riskanten Code haben – Glückwunsch: In unseren weit mehr als zwanzig Jahren Berufserfahrung haben wir in praktisch jedem unserer Audits solchen Code entdeckt (was uns ja grundsätzlich freut, weil wir dann als Saubermänner noch viele Jahre lang ein gesichertes Auskommen haben werden).

Aufmacherbild: Magnifying glass on a background von Shutterstock / Urheberrecht: Dima Sobko

Geschrieben von
Peter Hruschka
Peter Hruschka
Informatikstudium an der TU Wien, Promotion über Echtzeit-Programmiersprachen. 18 Jahre im Rahmen eines großen deutschen Softwarehauses verantwortlich für Software-Engineering. Initiator, Programmierer und weltweiter Prediger und Vermarkter eines der ersten Modellierungstools. Seit 1994 selbstständig als Trainer und Berater mit den Schwerpunkten Software-/Systemarchitekturen und Requirements Engineering, bevorzugt im technischen Umfeld.
Gernot Starke
Gernot Starke
    Informatikstudium an der RWTH Aachen, Dissertation über Software-Engineering an der J. Kepler Universität Linz. Langjährige Tätigkeit bei mehreren Software- und Beratungsunternehmen als Softwareentwickler, -architekt und technischer Projektleiter. 1996 Mitgründer und technischer Direktor des „Object Reality Center“, einer Kooperation mit Sun Microsystems. Dort Entwickler und technischer Leiter des ersten offizielle Java-Projekts von Sun in Deutschland. Seit 2011 Fellow der innoQ GmbH.  
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: