Software vorausschauend entwickeln
Wartet man lang genug, wird jeder Code Legacy. Folglich stellt sich die Frage, wie er sich so schreiben lässt, dass er langfristig wartbar bleibt. Einer der wichtigsten Faktoren ist die Verständlichkeit.
- Golo Roden
Ein geflügeltes Wort unter Entwicklern besagt, dass schwer verständlicher Code ein erstrebenswertes Ziel sei – schließlich werde so deutlich, wie schwer es war, ihn zu schreiben. Da Entwickler allerdings häufig in die Lage kommen, mit "fremdem" Code arbeiten zu müssen, sollte das Streben nach hoher Verständlichkeit im eigenen Interesse liegen. Der US-amerikanische Informatiker Donald E. Knuth prägte in den 1980er-Jahren in dem Zusammenhang das Konzept des literarischen Programmierens.
Allzu oft scheitert der Vorsatz an (zumeist zeitlichen) Vorgaben des Kunden oder des Vorgesetzten. Die Haltung, man werde nicht für schönen, sondern lediglich für funktionierenden Code bezahlt, ist weit verbreitet. Hilfreich wäre daher eine Taktik, mit der Entwickler erst gar nicht in eine Situation geraten, in der sie um zusätzliche Zeit zum Aufräumen bitten müssen.
Die Aufgabenstellung verstehen
Zunächst gilt es, das eigentliche Problem zu verstehen und zu dessen Kern vorzudringen. Als Beispiel für den vorliegenden Artikel soll die Validierung einer IBAN dienen. Vermutlich haben Entwickler beim Lesen der Aufgabe eine ungefähre Vorstellung, was zu programmieren ist. Bei genauerer Überlegung wird jedoch klar, dass die konkreten Anforderungen gänzlich unklar sind. Vermutlich ist eine Funktion validateIban gesucht, die eine IBAN nach dem Erhalt auf Korrektheit überprüft. Allerdings ergeben sich selbst in solch einem vergleichsweise einfachen Fall zahlreiche Fragen:
- GenĂĽgt es, das Format zu kontrollieren oder ist die IBAN auch inhaltlich zu prĂĽfen?
- Ist die Existenz der eingegeben Nummer zu klären? Eine IBAN kann schließlich ein gültiges Format aufweisen und formal korrekt, aber nicht vergeben sein.
- Was erwartet das Programm als Ergebnis der Funktion? Soll der RĂĽckgabewert vom Typ Boolean sein oder gibt die Funktion void zurĂĽck und wirft im Fehlerfall eine Ausnahme?
- Wie steht es um eine automatische Korrektur? Falls sich beispielsweise Zahlendreher eindeutig erkennen ließen, könnte die Funktion sie nicht automatisch korrigieren?
Die Antworten auf die Fragen wirken sich nicht nur auf die Implementierung, sondern auch auf die Namensgebung aus: Gibt die Funktion beispielsweise eine korrigierte IBAN zurĂĽck, passt der Name validateIban nicht, da er nahelegt, dass ausschlieĂźlich eine ĂśberprĂĽfung stattfindet und die Funktion deren Ergebnis zurĂĽckgibt.
Um herauszufinden, was überhaupt gewünscht ist, bietet es sich an zunächst so zu tun, als ob es die gewünschte Funktion bereits gäbe, und sich zu überlegen, wie Anwender sie benutzen. Entwickler sollten sich also in die Rolle desjenigen versetzen, der die Funktion zukünftig verwenden muss und mit dem API-Design beginnen statt mit der Implementierung. Den Anwender in den Vordergrund zu rücken, weist einige Vorteile auf. Allen voran steht, dass das Ergebnis möglichst intuitiv benutzbar und die API später nicht an die Implementierung anzupassen ist. Das Vorgehen ist unter der Bezeichnung Client-first Design bekannt und wird unter anderem von Krzysztof Cwalina und Brad Abrams in ihrem Buch "Framework Design Guidelines" beschrieben [1].
Empathie fĂĽr den Anwender
Schnell ersichtlich ist vermutlich, dass die Eingabe als Zeichenkette vorliegt, wobei es unterschiedliche gültige Formate gibt. Beispielsweise kann "DE78 6001 0070 0946 4157 06" genauso gelten wie "de78600100700946415706". Damit trägt man unterschiedlichen Formatierungen Rechnung, beispielsweise in E-Mails und im Briefverkehr. Praktisch wäre es allerdings, die IBAN in einem ersten Schritt in eine einheitliche Form zu bringen. Naheliegend ist die Verwendung von Großbuchstaben und das Entfernen jeglicher Leerstellen. Das Ergebnis der Funktion sollte für die beiden genannten Fälle daher "DE78600100700946415706" lauten.
Durch die Entscheidung, eine einheitlich formatierte IBAN zurückzugeben, entfällt allerdings die Option, einen logischen Rückgabewert zu verwenden, der angibt, ob die IBAN gültig war oder nicht. Da magische Werte wie eine leere Zeichenkette zu vermeiden sind, bleibt für den Hinweis auf ungültige Eingaben lediglich der Einsatz von Ausnahmen. Das bedeutet aber auch, dass der Name validateIban für die Funktion nicht mehr passend ist, da sie nun nicht länger zum reinen Überprüfen dient.
Einen ersten Hinweis auf einen Namen liefert die Idee, die IBAN in ein einheitliches Format zu bringen. Vorausgesetzt, dass der Großteil der Funktionsaufrufe mit gültigen Werten stattfindet, erscheint die Validierung sogar nur noch als Nebeneffekt. Der Fokus der Funktion verschiebt sich, weshalb der Name anzupassen ist. Dass das Vorgehen durchaus berechtigt ist, lässt sich daran ablesen, dass die Funktion Fehler als Ausnahmen behandelt. Das sind sie auch unter der Annahme, dass der Großteil der Eingaben gültig sein wird.
Als Funktionsname bietet sich daher etwas wie ibanUniform an. Allerdings lässt sich an der Stelle ein Antipattern ablesen: Da eine Funktion eine Tätigkeit darstellt, sollte sie mit einem Verb benannt sein. Einheitliche Formatierung bekommt häufig die Bezeichnung "kanonisch", weshalb sich der Name canonicalizeIban anbietet. Er gibt deutlich an, welchen Zweck die Funktion erfüllt: Eine IBAN in eine kanonische Form zu bringen, wobei klar ist, dass das für ungültige Eingaben nicht funktionieren kann, was zu Ausnahmen führt.
Sonderheft "Altlasten im Griff"
Mehr Artikel zum Thema Legacy-Code sind im Sonderheft iX Developer 01/2017 zu finden, dass sich unter anderem im heise Shop erwerben lässt.
Mancher mag einwenden, dass das alles vorgegeben sein müsste, schließlich sind Produktverantwortliche oder Architekten mit Derartigem betraut. In vielen Fällen steht aber niemand zur Verfügung, der jegliche Entscheidung auf der Detailebene treffen kann. Insofern müssen Entwickler wohl oder übel selbst nachdenken.