Refactoring: Code sicher verbessern
Du lernst, Programme innen umzubauen, ihr beobachtbares Verhalten zu bewahren und Änderungen mit passenden Tests zu prüfen.
Kurz gesagt
Refactoring ordnet den Code, also die geschriebenen Programmanweisungen, neu, ohne das beobachtbare Verhalten zu ändern. Kleine Schritte und Tests senken das Fehlerrisiko, geben aber keine Garantie.
Auch ein funktionierendes Programm kann schwer verständlich sein. Beim Refactoring verbesserst du seinen inneren Aufbau. Tests sind Probeläufe mit erwarteten Ergebnissen; sie helfen dir, Fehler beim Umbau zu entdecken.
Deine Lernziele
Beim Refactoring veränderst du den inneren Aufbau eines bestehenden Programms. Sein beobachtbares Verhalten soll dabei gleich bleiben: Unter denselben Voraussetzungen und mit derselben Eingabe liefert es weiterhin dieselben Ergebnisse und Wirkungen. Eine Eingabe sind die Daten, die das Programm erhält. Beobachtbar sind zum Beispiel angezeigte Texte, zurückgegebene Ergebnisse und Änderungen an gespeicherten Daten.
Auch Fehlermeldungen gehören zum beobachtbaren Verhalten. Gibt ein Programm nach dem Umbau bei leerer Eingabe eine zusätzliche Fehlermeldung aus, hat sich dieses Verhalten verändert. Eine neue Funktion oder eine Fehlerkorrektur kann sinnvoll sein. Sobald sie das beobachtbare Verhalten verändert, ist diese Änderung jedoch kein Refactoring.
Eine Variable ist ein Name für einen gespeicherten Wert. Du kannst einen rein internen Variablennamen ändern und alle seine Verwendungen passend ersetzen. Bleiben dadurch Ergebnisse und Wirkungen gleich, ist das ein Beispiel für Refactoring. Eine sichtbare Meldung um einen Begrüßungssatz zu erweitern, verändert dagegen die Ausgabe.
Die erfundenen Modelle dieser Lektion haben außer den ausdrücklich genannten Ausgaben und Ergebnissen keine weiteren Wirkungen. Wir untersuchen an ihnen keine Ausführungszeiten.
Refactoring
Eine Umstrukturierung von bestehendem Code, bei der das beobachtbare Programmverhalten unter gleichen Voraussetzungen erhalten bleibt.
Teste dich
Ein Code-Smell ist ein auffälliges Merkmal im Code, das auf ein tieferes Problem hinweisen kann. Der englische Ausdruck bedeutet sinngemäß „verdächtiger Geruch im Code“. Er ist ein Anlass zum Prüfen, kein Beweis für einen Fehler.
Eine Funktion ist eine benannte Teilaufgabe eines Programms. Sie kann Eingaben erhalten und ein Ergebnis zurückgeben. Eine Methode ist eine solche Teilaufgabe innerhalb einer Klasse. Eine Klasse fasst hier zusammengehörige Methoden unter einem Namen zusammen.
Bei Code-Duplizierung erledigen mehrere Codeabschnitte dieselbe Arbeit. Die Abschnitte können fast gleich aussehen oder trotz unterschiedlicher Schreibweise dieselbe Aufgabe erfüllen. Steht derselbe benötigte Ablauf in mehreren Methoden derselben Klasse, kannst du ihn in eine neue Methode auslagern. An jeder bisherigen Stelle ersetzt ein Aufruf den ausgelagerten Ablauf. Ein Aufruf bedeutet: Das Programm führt die benannte Teilaufgabe aus. Die benötigten Ausführungen und ihre Reihenfolge bleiben erhalten. Den alten Ablauf stehen zu lassen und zusätzlich aufzurufen, würde die Arbeit doppelt ausführen.
Eine lange Methode kann mehrere schwer überschaubare Teilaufgaben enthalten. Prüfe, ob du zusammengehörige Anweisungen sinnvoll in eigene Methoden auslagern kannst. Teile nach Aufgaben auf, nicht nach einer festen Zeilenzahl. Benötigte Arbeit darf beim Verkürzen nicht entfallen.
Eine lange Methode kann auch gut verständlich sein. Ebenso kann das Zusammenführen ähnlicher Abschnitte die Verständlichkeit verschlechtern, wenn ihre Aufgaben eigentlich verschieden sind. Entscheidend ist deshalb die Prüfung des konkreten Falls.
| Warnzeichen | Prüffrage | Mögliche Maßnahme |
|---|---|---|
| Code-Duplizierung | Erledigen die Abschnitte dieselbe zusammengehörige Aufgabe? | In derselben Klasse gemeinsam auslagern und an den bisherigen Stellen aufrufen. |
| Lange Methode | Vermischt die Methode mehrere eigenständige Teilaufgaben? | Zusammengehörige Anweisungen in verständlich benannte Methoden auslagern. |
Merke: Ein Warnzeichen verlangt eine Prüfung. Länge oder Ähnlichkeit allein entscheiden nicht darüber, ob ein Umbau sinnvoll ist.
Teste dich
Lege zuerst fest, welche Eingaben erlaubt sind und welche Ergebnisse du erwartest. Prüfe diese Erwartungen am bestehenden Programm. Weicht es schon vorher davon ab, klärst du die Abweichung, bevor du den Umbau bewertest.
Wähle dann eine kleine, zusammengehörige Änderung. Du kannst zum Beispiel eine Teilaufgabe auslagern und an ihrer bisherigen Stelle aufrufen. Prüfe anschließend erneut die erwarteten Ergebnisse. Bei einer Abweichung hältst du an: Untersuche und korrigiere die letzte Änderung oder nimm sie zurück. Passe die Erwartung nicht bloß an die neue Ausgabe an. Erst nach der Prüfung folgt der nächste kleine Schritt. Kleine Schritte erleichtern es, die Ursache einer Abweichung einzugrenzen; sie garantieren keine Fehlerfreiheit.
Das folgende Beispiel ist Pseudocode: eine vereinfachte Beschreibung eines Ablaufs, kein direkt ausführbares Programm einer bestimmten Sprache. FUNKTION beginnt eine benannte Teilaufgabe. In den Klammern steht ihr Eingabename. Ein Doppelpunkt und eingerückte Zeilen zeigen, welche Anweisungen dazugehören. Ein Name mit Klammern innerhalb einer Anweisung ruft die entsprechende Funktion auf.
Das Zeichen ← weist einer Variablen einen Wert zu. Texte stehen in deutschen Anführungszeichen. Das Zeichen + verbindet hier Texte in ihrer Reihenfolge. RÜCKGABE liefert ein Ergebnis an die aufrufende Stelle; AUSGABE zeigt einen Text an. Ein zurückgegebener Text wird also nicht schon dadurch angezeigt. Leerer Text enthält kein Zeichen und fügt beim Verbinden nichts hinzu.
Im Beispiel ist jeder Text als Eingabe erlaubt, auch leerer Text. Jeder Probelauf beginnt mit dem Aufruf von zeigeHinweis. Die aufgeführten Funktionen bilden den vollständigen Ablauf dieses Modells. Vorher und nachher soll genau ein Text ausgegeben werden.
Beispiel: Einen Hinweistext auslagern (erfundenes Modell)
- 1Vorher:
- 2FUNKTION zeigeHinweis(wort):
- 3 text ← „Hinweis:“ + wort
- 4 AUSGABE text
- 5Nachher:
- 6FUNKTION baueHinweis(wort):
- 7 RÜCKGABE „Hinweis:“ + wort
- 8FUNKTION zeigeHinweis(wort):
- 9 text ← baueHinweis(wort)
- 10 AUSGABE text
- 11Mit der Eingabe „Ruhe“ entsteht vorher der Text „Hinweis:Ruhe“. Nachher gibt baueHinweis denselben Text zurück, und zeigeHinweis zeigt ihn einmal an.
- 12Mit leerem Text entsteht vorher und nachher die Ausgabe „Hinweis:“. Diese beiden Probeläufe belegen keine vollständige Fehlerfreiheit.
Teste dich
Ein Test führt das Programm mit einer gewählten Eingabe aus und vergleicht das tatsächliche mit dem erwarteten Ergebnis. Stimmen beide überein, passt das beobachtete Ergebnis für diesen Probelauf. Tests vor und nach einer Umstrukturierung können zeigen, dass sich geprüfte Ergebnisse verändert haben. So helfen sie, Fehler früh zu entdecken.
Ein bestandener Test untersucht die tatsächlich ausgeführten Fälle. Für ungeprüfte Eingaben liefert sein Erfolg keine Garantie. Auch eine falsche Erwartung im Test kann einen Fehler verdecken. Die Zahl bestandener Tests allein macht ein Programm deshalb nicht sicher.
Wähle Tests passend zur Aufgabe: typische Eingaben und erlaubte Randfälle. Ein Randfall liegt an einer Grenze des erlaubten Eingabebereichs, etwa bei der kleinsten oder größten erlaubten Textlänge. Prüfe leeren Text, wenn er erlaubt ist. Eine Abweichung bei einem solchen Fall kann unentdeckt bleiben, wenn du ihn nicht ausführst.
Tests vergleichen Verhalten mit Erwartungen. Sie messen nicht, wie verständlich Methodennamen oder der Aufbau des Codes sind. Dafür musst du den Code zusätzlich lesen und beurteilen. Auch nach erfolgreichen Tests bleibt eine Restunsicherheit.
Beispiel: Zwei Farbtexte prüfen (erfundenes Modell)
- 1Ein Programm erlaubt die Eingaben „rot“, „gelb“ und „blau“. Es soll den eingegebenen Text unverändert ausgeben.
- 2Vor und nach dem Umbau liefern Tests mit „rot“ die Ausgabe „rot“ und mit „blau“ die Ausgabe „blau“.
- 3Diese Ergebnisse passen für die beiden geprüften Eingaben.
- 4Die Eingabe „gelb“ wurde noch nicht geprüft. Die bisherigen Testergebnisse garantieren ihre richtige Ausgabe nicht.
Merke: Passende Tests und kleine Schritte senken das Risiko. Bestandene Tests sind eine Hilfe bei der Prüfung, keine Garantie für Fehlerfreiheit.
Teste dich
Alles auf einen Blick
Refactoring
Beim Refactoring bleiben beobachtbare Ergebnisse und Wirkungen unter gleichen Voraussetzungen erhalten.
Code-Duplizierung und lange Methoden geben Anlass zur Prüfung, beweisen aber keinen Fehler.
Eine gemeinsame Aufgabe wird an den bisherigen Stellen aufgerufen; benötigte Ausführungen bleiben erhalten.
Prüfe den Ausgangszustand, ändere eine kleine Einheit und kläre Abweichungen vor dem nächsten Schritt.
Typische Eingaben und erlaubte Randfälle helfen bei der Fehlersuche, liefern aber keine Garantie.
Einen gemeinsamen Treffpunkthinweis auslagern (erfundenes Modell)
- 1Aufgabe: In der Klasse „Werkstattplan“ soll ein gemeinsamer Ausgabeschritt ausgelagert werden. Die Methoden erhalten keine Eingaben und haben außer ihren Ausgaben keine weiteren Wirkungen.
- 2Vorher: Die Methode „zeigeFrühplan“ gibt zuerst „Früh“ und dann „Treffpunkt: Halle“ aus. Die Methode „zeigeSpätplan“ gibt zuerst „Spät“ und dann „Treffpunkt: Halle“ aus.
- 3Erwartungen prüfen: Rufe jede Methode einzeln auf. Die Tests erwarten genau die jeweils genannten zwei Ausgaben in dieser Reihenfolge und bestätigen sie vor dem Umbau.
- 4Eine neue Methode „zeigeTreffpunkt“ anlegen: Sie gibt einmal „Treffpunkt: Halle“ aus. Die bisherigen Methoden sind noch unverändert. Prüfe ihre Ausgaben erneut.
- 5Ersten Aufruf einsetzen: Ersetze in „zeigeFrühplan“ den bisherigen Treffpunkthinweis durch einen Aufruf von „zeigeTreffpunkt“. Die Ausgabe „Früh“ bleibt davor stehen. Prüfe beide bisherigen Methoden erneut.
- 6Zweiten Aufruf einsetzen: Ersetze auch in „zeigeSpätplan“ den bisherigen Treffpunkthinweis durch den neuen Aufruf. Die Ausgabe „Spät“ bleibt davor stehen. Prüfe wieder beide Methoden.
- 7Ablauf nachverfolgen: „zeigeFrühplan“ zeigt „Früh“ und ruft dann „zeigeTreffpunkt“ auf. Dieser Aufruf zeigt „Treffpunkt: Halle“. Beim Spätplan entsteht entsprechend die Folge „Spät“, „Treffpunkt: Halle“.
- 8Ergebnis: Beide benötigten Ausgabefolgen bleiben in diesem Modell erhalten. Die Tests unterstützen die Prüfung; die Erklärung des Ablaufs zeigt zusätzlich, warum jeder Hinweis weiterhin an seiner bisherigen Stelle erscheint.
Fehler finden
Fehler in Zeile 3Die beiden ausgeführten Tests prüfen den leeren Text nicht. Richtig: Die unveränderten Testergebnisse geben für den ungeprüften leeren Text keine Garantie.
Lückentext
Ein Programm gibt nach dem Umbau für dieselbe erlaubte Eingabe einen anderen Text aus. Diese Änderung ist . Zwei Methoden derselben Klasse enthalten denselben benötigten Ausgabeschritt. Beide sollen ihn weiterhin einmal ausführen. Nach dem Auslagern rufen an den bisherigen Stellen die neue Methode auf. Ein Test prüft die Eingabe „Wolke“. Für den noch ungeprüften leeren Text liefert sein Erfolg .