Refactoring: Code Smells erkennen und in sicheren Schritten umbauen

https://bildung.social/@oerinformatik/

https://oer-informatik.de/refactoring

Clean Code, Refactoring, Code Smell, Charakterisierungstest, Methode extrahieren, Parameterobjekt, Pfadfinderregel, 5-mal-Warum, Ursachenanalyse, IDE

tl/dr; (ca. 39 min Lesezeit): Refactoring verbessert die Struktur von Code, ohne sein Verhalten zu ändern. Damit das gelingt, brauchst Du ein Sicherheitsnetz aus Tests, kleine Schritte und einen Commit nach jedem grünen Testlauf. Du lernst typische Code Smells kennen, ordnest ihnen passende Refactorings zu und zerlegst die lange Methode der CSB-Rad-Kata mit „Methode extrahieren“ und einem Parameterobjekt. Dazu kommen die Werkzeuge der IDE und ihre Grenzen in Python, die Pfadfinderregel und die Ursachenanalyse mit dem 5-mal-Warum [AI-Assisted]. (Zuletzt geändert am 10.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über Clean Code: Überblick / Lesbarer Code / Einfach bleiben / Refactoring / Methoden gliedern / Klassen schneiden / Vererbung richtig einsetzen / Erweitern statt ändern / SOLID / Vom Entwurf zum Code

Dieser Artikel baut auf der Kann-Liste des Artikels Einfach bleiben: KISS, YAGNI, DRY und Beware of Premature Optimization auf.

Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.

Als Grundlagen des Refactorings kann ich nach Durchlesen des Artikels…

  • … Refactoring als Verbesserung der Struktur bei gleichem Verhalten definieren und von Fehlerbehebung und neuer Funktion abgrenzen.

  • … ein Sicherheitsnetz aufbauen: Charakterisierungstests für vorhandenen Code schreiben, in kleinen Schritten umbauen und nach jedem grünen Schritt committen.

  • … einfache Refactorings durchführen: Umbenennen, Methode extrahieren, Variable extrahieren, Inline, Parameterobjekt einführen und magische Zahl durch Konstante ersetzen.

  • … typische Code Smells erkennen und einem passenden Refactoring zuordnen.

  • … die Refactoring-Werkzeuge der IDE nutzen und ihre Grenzen bei dynamischer Typisierung in Python beschreiben.

Darüber hinaus kann ich als Vertiefung…

  • … die Pfadfinderregel erklären und im Teamalltag einordnen.

  • … bei einem Fehler mit dem 5-mal-Warum eine Ursachenanalyse durchführen, statt nur das Symptom zu beheben.

  • … komplexe Refactorings (Klasse extrahieren, Bedingung durch Polymorphie ersetzen, Vererbung durch Delegation ersetzen) benennen und einordnen, wofür sie gebraucht werden.

In den beiden letzten Artikeln ist der Bestellabschluss der Kata lesbarer und schlanker geworden. Trotzdem ist die Methode abschliessen in Stand 03 noch rund 70 Zeilen lang: Sie summiert den Warenwert, prüft den Gutscheincode, rechnet drei Versandarten, die Mehrwertsteuer, vergibt die Bestellnummer, baut die Mail und speichert. Wer die Freigrenze für Pakete ändern will, muss das ganze Stück lesen. Und wer daran etwas ändert, riskiert, nebenbei die Spedition kaputtzumachen.

Bisher hast Du die Kata „einfach umgebaut“ und Dich darauf verlassen, dass die Charakterisierungstests grün bleiben. In diesem Artikel geht es um das Handwerk dahinter: Was ist ein Refactoring überhaupt, wie baust Du das Sicherheitsnetz, welche Code Smells weisen auf welchen Umbau hin, und wie zerlegst Du eine lange Methode, ohne dass ein einziger Test rot wird? Am Beispiel der Code-Kata der CSB-Rad GmbH holst Du die Charakterisierungstests in den Testordner und baust Stand 03 zu Stand 04 um. Zum Üben gibt es Übungsaufgaben zum Refactoring.

Das Aktivitätsdiagramm zeigt den Kreislauf, der jedem Refactoring zugrunde liegt: Erst wenn die Tests grün sind, kommt der nächste kleine Schritt, und jeder grüne Schritt wird festgehalten.

Aktivitätsdiagramm Refactoring-Kreislauf: Charakterisierungstests schreiben, dann wiederholt einen kleinen Schritt umbauen und die Tests ausführen; bei Grün den Schritt committen, bei Rot den Schritt zurücknehmen, bis der Umbau fertig ist (mit KI erstellt)

Was ist Refactoring?

Systematisch beschrieben hat das Refactoring zuerst William Opdyke 1992 in seiner Doktorarbeit „Refactoring Object-Oriented Frameworks“ an der University of Illinois. Bekannt gemacht hat es Martin Fowler mit dem Buch „Refactoring. Improving the Design of Existing Code“ (1999, 2. Auflage 2018 mit Beispielen in JavaScript). Fowler definiert es zweimal, als Substantiv und als Verb:

  • Refactoring (Substantiv): eine Änderung an der inneren Struktur von Software, die sie leichter verständlich und billiger änderbar macht, ohne ihr beobachtbares Verhalten zu verändern.

  • refaktorisieren (Verb): Software durch eine Folge solcher Refactorings umstrukturieren, ohne ihr beobachtbares Verhalten zu verändern.

Der Kern steckt in den letzten Worten: ohne das beobachtbare Verhalten zu verändern. Beobachtbar ist alles, was ein Aufrufer, ein Test oder eine Nutzerin sehen kann: Rückgabewerte, Ausnahmen, Ausgaben, gespeicherte Daten. Ob eine Berechnung in einer Methode oder in dreien steht, sieht von außen niemand. Dass die Mail plötzlich „Versand: paket“ statt „Versand: Paket“ schreibt, sieht man sehr wohl.

Zwei Hüte: Refactoring, Fehlerbehebung und neue Funktion

Kent Beck beschreibt das Arbeiten mit dem Bild von zwei Hüten (two hats): Mit dem einen Hut fügst Du Funktionen hinzu, mit dem anderen refaktorisierst Du. Du kannst schnell wechseln, aber nie beide gleichzeitig tragen. Die Tabelle grenzt die drei Arten von Änderungen ab, die im Alltag oft durcheinandergehen:

Refactoring, Fehlerbehebung und neue Funktion im Vergleich
Art der Änderung Verhalten danach Tests Beispiel in der Kata
Refactoring gleich bleiben unverändert grün berechneVersandkosten aus abschliessen herauslösen
Fehlerbehebung (Bugfix) anders, nämlich endlich so, wie gefordert ein neuer Test ist zuerst rot, danach grün Spedition ohne Rad kostet 0 € statt 49 € (siehe 5-mal-Warum)
neue Funktion (Feature) erweitert neue Tests kommen hinzu Versandart Kurier ab Stand 07

Die Trennung hat einen praktischen Grund. Wird ein Test während eines Refactorings rot, weißt Du sofort: Der letzte Umbauschritt war falsch. Hast Du gleichzeitig einen Fehler behoben, weißt Du es nicht mehr, denn der rote Test könnte auch die gewollte Verhaltensänderung zeigen. Deshalb gilt: Wenn Du beim Umbauen einen Fehler findest, notiere ihn, beende den Umbauschritt und behebe den Fehler danach in einem eigenen Commit.

Auch in der testgetriebenen Entwicklung hat Refactoring einen festen Platz: Im Kreislauf Red – Green – Refactor ist der dritte Schritt das Aufräumen, nachdem der neue Test grün ist (siehe Testgetriebene Entwicklung mit pytest und Extreme Programming).

Das Sicherheitsnetz: Charakterisierungstests

Problem: Code ohne Tests

Refactoring ohne Tests ist wie Klettern ohne Seil: Es geht oft gut, aber wenn nicht, merkst Du es zu spät. Für neuen Code schreibst Du die Tests aus der Anforderung (siehe Unit-Tests). Vorhandener Code hat aber oft keine Tests, und die Anforderung ist nur noch im Code selbst nachzulesen. Michael Feathers nennt solchen Code in „Working Effectively with Legacy Code“ (2004) Legacy Code: „Code ohne Tests“, egal wie alt er ist.

Hier steckst Du in einem Dilemma: Um den Code sicher ändern zu können, brauchst Du Tests. Um Tests schreiben zu können, müsstest Du oft erst den Code ändern (etwa eine Abhängigkeit lösen).

Prinzip: festhalten, was der Code heute tut

Feathers’ Ausweg ist der Charakterisierungstest (characterization test). Er prüft nicht, was der Code tun soll, sondern hält fest, was er heute tatsächlich tut. Das Vorgehen:

  1. Ein Codestück auswählen, das Du umbauen willst.

  2. Einen Test schreiben, der es aufruft, mit einer Erwartung, von der Du weißt, dass sie falsch ist (zum Beispiel 0).

  3. Den Test ausführen. Die Fehlermeldung verrät das tatsächliche Ergebnis.

  4. Dieses Ergebnis als Erwartung eintragen. Der Test ist jetzt grün und beschreibt das heutige Verhalten.

  5. Wiederholen, bis alle Zweige abgedeckt sind, die Du umbauen willst: jede Versandart, jede Ablehnung, Grenzwerte wie die Freigrenze.

Am Beispiel der Spedition ohne Rad sieht der erste Versuch in Stand 03 so aus:

@Test
void speditionOhneRad() {
    Position lichtset = new Position("LIC-SET-01", "Lichtset Nabendynamo StVZO", 7563, 1, 350, false);
    int[] ergebnis = new Shop().abschliessen(List.of(lichtset), "SPEDITION", null, "mira.engel@example.org");
    assertEquals(0, ergebnis[2]); // Versandkosten: erst einmal raten
}
// Ausgabe: expected: <0> but was: <4900>

Der Test sagt Dir: 49,00 € Versandkosten. Das trägst Du ein. Ob 49,00 € fachlich richtig sind, ist eine zweite Frage, die Du mit der Fachabteilung klärst. Für das Refactoring zählt nur: Nach dem Umbau muss wieder 4900 herauskommen.

Ein guter Satz Charakterisierungstests deckt jeden Zweig mindestens einmal ab. Ob das gelungen ist, zeigt die Testabdeckung (siehe Code Coverage): Zweige, die kein Test durchläuft, kannst Du nicht sicher umbauen.

Umbau der Kata: Charakterisierungstests in den Testordner

In der Kata liegen die Charakterisierungstests bis Stand 03 im Ordner tests-charakterisierung, außerhalb der normalen Testordner. Sie sind mit dem Vorgehen oben entstanden: 16 Fälle, die jede Versandart, den Gutscheincode, die Freigrenze vor und nach Rabatt, die vier Ablehnungsgründe, die beiden Mailtexte und die fortlaufende Nummer festhalten. Ab Stand 04 liegen sie im Testordner (src/test/java bzw. tests) und laufen bei jedem mvn test bzw. python -m pytest mit. Ein Test aus der Sammlung:

@Test
void speditionOhneRadKostetEinePauschale() {
    assertArrayEquals(new int[] {7563, 0, 4900, 2368, 14831},
            shop.abschliessen(new Bestellanfrage(List.of(LICHTSET), "SPEDITION", null, MAIL)));
}

Kleine Schritte und ein Commit nach jedem grünen Schritt

Tests allein reichen nicht. Wer zwanzig Minuten am Stück umbaut und dann die Tests startet, sucht bei Rot in zwanzig Minuten Änderungen. Die Regel lautet deshalb: ein kleiner Schritt, Tests ausführen, bei Grün committen, bei Rot den Schritt zurücknehmen (git restore . bzw. git checkout -- ., siehe Git: Grundlagen). Ein Schritt ist so klein, dass er sich in einem Satz beschreiben lässt: „Methode berechneRabatt extrahiert“. Dieser Satz ist zugleich die Commit-Nachricht.

So entsteht eine Kette von Sicherungspunkten. Geht ein Schritt schief, verlierst Du nur diesen einen. Im Merge Request kann die Kollegin jeden Schritt einzeln nachvollziehen (siehe Git: Branches und Merge). Kent Beck fasst das in dem oft zitierten Satz zusammen: „Make the change easy (warning: this may be hard), then make the easy change.“ Erst die Struktur so umbauen, dass die neue Anforderung leicht wird, dann die neue Anforderung umsetzen.

Grenzen und Zielkonflikte

  • Charakterisierungstests konservieren auch Fehler. Ist das heutige Verhalten falsch, hält der Test den Fehler fest. Das ist gewollt: Während des Refactorings soll sich nichts ändern. Den Fehler behebst Du danach bewusst, indem Du den Test anpasst.

  • Grobe Tests sind ein Anfang. Die Kata-Tests prüfen nur das Ergebnis von abschliessen. Sie sagen nicht, welche Teilberechnung falsch ist. Nach dem Umbau lohnt es sich, feinere Unit-Tests zu ergänzen.

  • Nicht alles lässt sich sofort testen. Hängt Code an Datenbank, Uhrzeit oder Netzwerk, brauchst Du zuerst eine Naht (seam), an der Du einen Stub einsetzen kannst. Das ist Thema im Artikel SOLID und in Dependency Injection.

Code Smells: Wann lohnt sich ein Refactoring?

Begriff

Den Begriff Code Smell (wörtlich „Codegeruch“) hat Kent Beck geprägt; zusammen mit Martin Fowler hat er im Kapitel „Bad Smells in Code“ des Buches „Refactoring“ (1999) eine Liste solcher Gerüche zusammengestellt. Ein Smell ist kein Fehler, sondern ein Hinweis: An dieser Stelle lohnt es sich, genauer hinzusehen. Wie beim Geruch im Kühlschrank kann die Ursache harmlos sein, meist ist sie es nicht.

Zu jedem Smell gibt Fowler die Refactorings an, die üblicherweise helfen. Das macht die Liste so praktisch: Du musst nicht kreativ werden, sondern erkennst ein Muster und wendest einen erprobten Umbau an.

Smell und passendes Refactoring

Die Tabelle fasst die wichtigsten Smells mit ihrer englischen Originalbezeichnung, einem Erkennungsmerkmal und den passenden Refactorings zusammen. Die Namen der Refactorings stehen auf Deutsch mit Fowlers englischem Namen:

Code Smells und die passenden Refactorings nach Fowler
Code Smell Woran erkennst Du ihn? Passendes Refactoring Stelle in der Kata
rätselhafter Name (Mysterious Name) Name sagt nicht, was die Sache ist oder tut (s, vk, txt) Umbenennen (Rename Variable, Change Function Declaration) Stand 01, behoben in Lesbarer Code
magische Zahl (Magic Number) unerklärte Zahl oder Zeichenkette im Code (690, 31500) magische Zahl durch Konstante ersetzen (Replace Magic Number with Symbolic Constant, 2. Aufl.: Replace Magic Literal) Stand 01, behoben in Lesbarer Code
doppelter Code (Duplicated Code) gleiche oder fast gleiche Zeilen an mehreren Stellen Methode extrahieren (Extract Function), Anweisungen verschieben (Slide Statements) Stand 02, behoben in Einfach bleiben
lange Methode (Long Function) Methode passt nicht auf den Bildschirm, Kommentare trennen Abschnitte Methode extrahieren (Extract Function), Bedingung zerlegen (Decompose Conditional) Stand 03: abschliessen
lange Parameterliste (Long Parameter List) vier und mehr Parameter, oft vom gleichen Typ Parameterobjekt einführen (Introduce Parameter Object), ganzes Objekt übergeben (Preserve Whole Object) Stand 03: abschliessen(positionen, versandart, gutscheincode, kundenEmail)
Datenklumpen (Data Clumps) dieselben Werte treten immer gemeinsam auf Klasse extrahieren (Extract Class), Parameterobjekt einführen Stand 03: Positionen, Versandart, Code und Mail
Basistyp-Besessenheit (Primitive Obsession) Fachbegriffe als int, String oder Array ("PAKET", Object[]) Basistyp durch Objekt ersetzen (Replace Primitive with Object) Versandart als String, Bestellung als Object[]
wiederholte Fallunterscheidung (Repeated Switches, 1. Aufl.: Switch Statements) dieselbe switch- oder if-Kette an mehreren Stellen Bedingung durch Polymorphie ersetzen (Replace Conditional with Polymorphism) Stand 04: berechneVersandkosten und versandbezeichnung
Neid auf fremde Daten (Feature Envy) Methode rechnet fast nur mit Daten eines anderen Objekts Methode verschieben (Move Function) Stand 05 bis 06, siehe Klassen schneiden
große Klasse (Large Class) viele Felder und Methoden, mehrere Zuständigkeiten Klasse extrahieren (Extract Class) Klasse Shop bis Stand 05
erklärender Kommentar (Comments) Kommentar erklärt, was der folgende Block tut Methode extrahieren, Variable extrahieren (Extract Variable) „// ohne Rad wird eine Speditionspauschale berechnet“
spekulative Allgemeinheit (Speculative Generality) Parameter, Klassen, Haken „für später“ Inline (Inline Function, Inline Class), Parameter entfernen (Remove Parameter) Parameter express, behoben in Einfach bleiben
Schrotflinten-Änderung (Shotgun Surgery) eine fachliche Änderung erfordert Eingriffe an vielen Stellen Methode oder Feld verschieben (Move Function, Move Field), Inline Class neue Versandart vor Stand 08

Für die Kata in Stand 03 riecht es an drei Stellen am deutlichsten: Die Methode abschliessen ist zu lang, sie hat vier Parameter, die immer zusammen auftreten, und Kommentare wie „ohne Rad wird eine Speditionspauschale berechnet“ trennen Abschnitte, die eigentlich eigene Methoden sein wollen. Genau diese drei Smells beseitigst Du in diesem Artikel. Die wiederholte Fallunterscheidung über die Versandart bleibt bewusst stehen, sie ist ein Thema für Erweitern statt ändern.

Grenzen

Smells sind Heuristiken, keine Messwerte. Eine Methode mit 25 Zeilen kann klar sein, eine mit acht Zeilen verworren. Statische Analysewerkzeuge wie SonarQube, PMD oder Ruff melden viele Smells automatisch, ersetzen aber nicht das Urteil: Ob eine Fallunterscheidung durch Polymorphie ersetzt werden sollte, hängt davon ab, ob weitere Fälle zu erwarten sind (siehe YAGNI).

Einfache Refactorings

Die folgenden sechs Refactorings brauchst Du fast täglich. Jedes hat bei Fowler eine feste Mechanik, also eine Schrittfolge, die das Verhalten erhält. Moderne IDEs führen die meisten davon auf Knopfdruck aus; es lohnt sich trotzdem, die Mechanik zu kennen, denn sie erklärt, worauf das Werkzeug achten muss.

Umbenennen (Rename)

Problem: Ein Name sagt nicht, was die Sache ist. In Stand 01 hieß der Parameter für die Versandart v, die Liste der Bestellungen getListe().

Mechanik: Neuen Namen festlegen; Deklaration und alle Verwendungen ändern; bei öffentlichen Methoden alle Aufrufer finden, auch in Tests und anderen Modulen. Bei Methoden heißt das Refactoring Change Function Declaration (früher Rename Method); bei veröffentlichten Schnittstellen bleibt der alte Name eine Zeit lang als Weiterleitung stehen und wird als veraltet markiert (@Deprecated, warnings.warn(..., DeprecationWarning)).

Grenzen: Ein Name, den andere Systeme verwenden (REST-Pfad, Spaltenname, Konfigurationsschlüssel), ist Teil des Verhaltens. Ihn zu ändern ist kein reines Refactoring mehr.

Variable extrahieren (Extract Variable)

Problem: Ein Ausdruck ist schwer zu lesen oder steht mehrfach da. In Stand 03 vergleicht die Paketberechnung warenwert - rabatt >= VERSANDKOSTENFREI_AB_CENT; was warenwert - rabatt fachlich bedeutet, musst Du Dir selbst denken.

Mechanik: Den Ausdruck einer neuen, sprechend benannten Variablen zuweisen; den Ausdruck durch die Variable ersetzen; testen.

// vorher (Stand 03)
if (warenwert - rabatt >= VERSANDKOSTENFREI_AB_CENT) {

// nachher
int warenwertNachRabatt = warenwert - rabatt;
if (warenwertNachRabatt >= VERSANDKOSTENFREI_AB_CENT) {

Der Name warenwertNachRabatt dokumentiert die Fachregel „frei ab 100 € Warenwert nach Rabatt“ direkt im Code. In Stand 04 wird daraus sogar ein Parameter der extrahierten Methode berechneVersandkosten.

Grenzen: Eine Variable, deren Name nicht mehr sagt als der Ausdruck selbst (int summe = a + b;), bringt nichts. Dann ist das Gegenstück dran: Inline.

Inline (Inline Variable, Inline Function)

Problem: Eine Variable oder Methode ist überflüssig geworden: Ihr Name sagt nicht mehr als ihr Inhalt, oder sie leitet nur weiter. Das passiert oft als Zwischenergebnis anderer Refactorings.

Mechanik: Prüfen, dass der Wert nur einmal zugewiesen wird bzw. die Methode nicht überschrieben wird; jede Verwendung durch den Inhalt ersetzen; Deklaration löschen; testen.

// vorher: zwei Variablen ohne zusätzliche Aussage
private int berechneRabatt(int warenwert, String gutscheincode) {
    if (GUTSCHEINCODE_KURIER.equals(gutscheincode)) {
        int rabattProzent = GUTSCHEIN_RABATT_PROZENT;
        int rabatt = prozentKaufmaennischGerundet(warenwert, rabattProzent);
        return rabatt;
    }
    return 0;
}

// nachher (Stand 04)
private int berechneRabatt(int warenwert, String gutscheincode) {
    if (GUTSCHEINCODE_KURIER.equals(gutscheincode)) {
        return prozentKaufmaennischGerundet(warenwert, GUTSCHEIN_RABATT_PROZENT);
    }
    return 0;
}

Grenzen: Inline nur, wenn der Inhalt so klar ist wie der Name. Eine Methode istVersandkostenfrei() mit einer Zeile Vergleich bleibt, denn sie benennt eine Fachregel.

Magische Zahl durch Konstante ersetzen (Replace Magic Number with Symbolic Constant)

Problem: Eine Zahl oder Zeichenkette steht ohne Erklärung im Code. Diesen Umbau kennst Du aus Lesbarer Code: In Stand 02 wurden 690, 31500, 10000 und 4900 zu PAKETPAUSCHALE_CENT, HOECHSTGEWICHT_PAKET_GRAMM, VERSANDKOSTENFREI_AB_CENT und SPEDITION_JE_RAD_CENT.

Mechanik: Konstante mit sprechendem Namen anlegen; jede Verwendung prüfen, ob sie dieselbe Bedeutung hat (sonst eine eigene Konstante); ersetzen; testen.

Grenzen: Nicht jede Zahl ist magisch. In der Rundungsformel (betragCent * prozent + 50) / 100 bedeuten 50 und 100 „kaufmännisch auf ganze Cent runden“. Eine Konstante HUNDERT = 100 würde nichts erklären. Besser ist hier, die ganze Formel als Methode mit sprechendem Namen zu extrahieren: prozentKaufmaennischGerundet(betragCent, prozent). Auch 0 und 1 als Start- oder Grenzwerte sind meist selbsterklärend.

Methode extrahieren (Extract Method)

Problem: Eine Methode ist zu lang, oder ein Kommentar erklärt, was der nächste Block tut. In Stand 03 steht die gesamte Versandkostenberechnung mitten in abschliessen, eingeleitet von einer if-Kette über die Versandart.

Mechanik (bei Fowler 2018 Extract Function):

  1. Einen zusammenhängenden Block wählen, der eine Aufgabe erledigt, und eine Methode mit einem Namen anlegen, der sagt, was der Block tut (nicht wie).

  2. Den Block in die neue Methode kopieren.

  3. Prüfen, welche lokalen Variablen der Block liest (werden Parameter) und welche er schreibt (werden Rückgabewert). Schreibt er mehr als eine Variable, ist der Block zu groß oder falsch geschnitten.

  4. Den Block in der alten Methode durch den Aufruf ersetzen, übersetzen, testen, committen.

In der Kata liest der Versandblock die Positionen, die Versandart und den Warenwert nach Rabatt und schreibt nur versandkosten. Daraus wird berechneVersandkosten(positionen, versandart, warenwertNachRabatt):

// Auszug aus src/kata/java/stand-04, Klasse Shop
private int berechneVersandkosten(List<Position> positionen, String versandart, int warenwertNachRabatt) {
    switch (versandart) {
        case "PAKET":
            int anzahlPakete = berechneAnzahlPakete(positionen);
            if (warenwertNachRabatt >= VERSANDKOSTENFREI_AB_CENT) {
                return 0;
            }
            return anzahlPakete * PAKETPAUSCHALE_CENT;
        case "SPEDITION":
            // ohne Rad wird eine Speditionspauschale berechnet
            return Math.max(1, zaehleRaeder(positionen)) * SPEDITION_JE_RAD_CENT;
        case "ABHOLUNG":
            return 0;
        default:
            throw new IllegalArgumentException("Unbekannte Versandart: " + versandart);
    }
}

Zwei Details fallen auf. Die Paketzahl wird auch dann berechnet, wenn der Versand frei ist; das ist Absicht, denn berechneAnzahlPakete lehnt Räder im Paket ab, und diese Prüfung darf durch den Umbau nicht verschwinden (der Test radAlsPaketWirdAbgelehnt würde es sofort melden). Und die Versandbezeichnung für die Mail, die in Stand 03 im selben Block gesetzt wurde, hat eine eigene Methode versandbezeichnung bekommen, weil der Block sonst zwei Variablen geschrieben hätte.

Grenzen: Zu kleine Methoden zerreißen den Lesefluss; wer für jede Zeile springen muss, versteht weniger. Faustregel: Extrahiere, wenn Du für den Block einen Namen findest, der mehr sagt als sein Inhalt. Wie Du Methoden auf eine Abstraktionsebene bringst, zeigt Methoden gliedern.

Parameterobjekt einführen (Introduce Parameter Object)

Problem: Mehrere Parameter treten immer gemeinsam auf (Datenklumpen). In Stand 03 erwartet abschliessen vier Parameter, drei davon vom Typ String. Ein vertauschter Aufruf wie abschliessen(positionen, "KURIER10", "PAKET", mail) übersetzt ohne Fehler und lehnt die Bestellung dann als „unbekannte Versandart“ ab.

Mechanik: Eine unveränderliche Datenklasse für die zusammengehörenden Werte anlegen; der Methode einen Parameter dieses Typs geben (die IDE kann alte und neue Signatur kurz parallel halten); alle Aufrufer umstellen; die einzelnen Parameter entfernen; testen.

// neu in src/kata/java/stand-04
/** Parameterobjekt: alles, was der Bestellabschluss als Eingabe braucht. */
public record Bestellanfrage(List<Position> positionen, String versandart, String gutscheincode, String kundenEmail) {
}

// Signatur vorher (Stand 03)
public int[] abschliessen(List<Position> positionen, String versandart, String gutscheincode, String kundenEmail)

// Signatur nachher (Stand 04)
public int[] abschliessen(Bestellanfrage anfrage)

Das Parameterobjekt ist mehr als Kosmetik. Es gibt dem Datenklumpen einen Namen aus der Fachsprache und wird zum natürlichen Ort für Verhalten, das mit diesen Daten rechnet. Genau das passiert in Klassen schneiden, wo aus der Anfrage eine Bestellung wird, die ihren Warenwert selbst kennt.

Grenzen: Die Änderung der Signatur betrifft alle Aufrufer; in einer öffentlichen Schnittstelle ist das eine inkompatible Änderung. Und ein Parameterobjekt, das nur aus Bequemlichkeit Werte bündelt, die fachlich nichts miteinander zu tun haben, versteckt eine zu breite Methode, statt sie zu verbessern.

Umbau der Kata: von Stand 03 zu Stand 04

Der Umbau besteht aus einer Folge kleiner Schritte, jeder mit grünen Tests und eigenem Commit. Die Reihenfolge ist so gewählt, dass jeder Schritt auf dem vorigen aufbaut:

Die Schritte von Stand 03 zu Stand 04
Nr. Schritt (= Commit-Nachricht) Refactoring
1 Charakterisierungstests in den Testordner übernommen (Sicherheitsnetz)
2 berechneWarenwert extrahiert Methode extrahieren
3 prozentKaufmaennischGerundet extrahiert, berechneRabatt und berechneMehrwertsteuer nutzen es Methode extrahieren
4 berechneAnzahlPakete und zaehleRaeder extrahiert, Paketzahl mit Aufrunden statt if Methode extrahieren, Algorithmus ersetzen (Substitute Algorithm)
5 berechneVersandkosten extrahiert, Versandbezeichnung in eigene Methode Methode extrahieren, Variable extrahieren
6 erstelleMailtext und alsEuroText extrahiert Methode extrahieren
7 Parameterobjekt Bestellanfrage eingeführt, Tests auf neuen Aufruf umgestellt Parameterobjekt einführen

Schritt 4 verdient einen zweiten Blick: Die Formel (gesamtgewicht + HOECHSTGEWICHT_PAKET_GRAMM - 1) / HOECHSTGEWICHT_PAKET_GRAMM rundet auf ganze Pakete auf und ersetzt zusammen mit Math.max(1, …) die if-Abfrage mit Rest und Sonderfall. Ein Algorithmus-Tausch ist riskanter als eine Extraktion, und hier zeigt sich eine Lücke im Netz: Mit den Produkten der Kata liegt jede Paketbestellung über 31,5 kg auch über der Freigrenze, die Paketzahl wirkt sich in keinem der 16 Tests auf das Ergebnis aus. Deshalb hat dieser Schritt einen eigenen Commit, und alte und neue Formel wurden zusätzlich für alle Gewichte von 0 bis 100 kg (grammgenau) verglichen: Sie liefern überall dieselbe Paketzahl. Einen echten Test für die Paketzahl bekommt die Kata erst mit dem Expressversand in Stand 08.

Das Ergebnis ist eine Methode abschliessen, die man in einem Zug lesen kann:

// Auszug aus src/kata/java/stand-04, Klasse Shop
public int[] abschliessen(Bestellanfrage anfrage) {
    int warenwert = berechneWarenwert(anfrage.positionen());
    int rabatt = berechneRabatt(warenwert, anfrage.gutscheincode());
    int versandkosten = berechneVersandkosten(anfrage.positionen(), anfrage.versandart(), warenwert - rabatt);
    if (warenwert == 0) {
        throw new IllegalArgumentException("Bestellung ohne Warenwert");
    }
    int nettobetrag = warenwert - rabatt + versandkosten;
    int mehrwertsteuer = berechneMehrwertsteuer(nettobetrag);
    int gesamtbetrag = nettobetrag + mehrwertsteuer;
    int bestellnummer = vergibBestellnummer();
    postausgang.add(erstelleMailtext(anfrage, bestellnummer, gesamtbetrag));
    bestellungen.add(new Object[] {"CSB-" + bestellnummer, anfrage.kundenEmail(), anfrage.positionen(), gesamtbetrag});
    return new int[] {warenwert, rabatt, versandkosten, mehrwertsteuer, gesamtbetrag};
}

Die Klassendiagramme zeigen, wie sich die Struktur verändert hat. Vorher hat Shop eine einzige große Operation mit vier Parametern und nur eine private Hilfsmethode:

UML-Klassendiagramm der Kata in Stand 03: Klasse Shop mit Konstanten, Feldern naechsteBestellnummer, bestellungen, postausgang, Operation abschliessen mit vier Parametern und private Operation vergibBestellnummer, Datentyp Position (mit KI erstellt)

Nachher gibt es den Datentyp Bestellanfrage als Parameterobjekt, und Shop hat elf private Operationen, deren Namen zusammen den Ablauf erzählen:

UML-Klassendiagramm der Kata in Stand 04: Klasse Shop mit Operation abschliessen(anfrage: Bestellanfrage) und privaten Operationen berechneWarenwert, berechneRabatt, berechneVersandkosten, berechneAnzahlPakete, zaehleRaeder, berechneMehrwertsteuer, prozentKaufmaennischGerundet, vergibBestellnummer, erstelleMailtext, versandbezeichnung, alsEuroText; Bestellanfrage nutzt Position (mit KI erstellt)

Was noch fehlt: Die Prüfungen stecken weiter in den Rechenmethoden (die Menge in berechneWarenwert, das Rad in berechneAnzahlPakete, der Warenwert 0 erst nach den Versandkosten). Und abschliessen mischt Berechnung mit Nummernvergabe, Mail und Speichern. Beides räumt der nächste Artikel Methoden gliedern auf.

Refactoring-Werkzeuge der IDE

Was die IDE übernimmt

Die meisten einfachen Refactorings musst Du nicht von Hand ausführen. Die IDE kennt den Syntaxbaum und alle Verwendungen eines Namens und ändert sie in einem Schritt. Die Tabelle nennt die Tastenkürzel unter Windows und Linux in den Standardeinstellungen; über das Kontextmenü „Refactor“ (Eclipse: „Refactor“, VS Code: „Refactor…“ bzw. die Glühbirne) erreichst Du alle:

Tastenkürzel für Refactorings in verbreiteten IDEs (Standardbelegung, Stand 2026)
Refactoring IntelliJ IDEA, PyCharm Eclipse VS Code (Java, Python mit Pylance)
Umbenennen Umschalt+F6 Alt+Umschalt+R F2
Methode extrahieren Strg+Alt+M Alt+Umschalt+M Strg+Umschalt+R (Refactor-Menü)
Variable extrahieren Strg+Alt+V Alt+Umschalt+L Strg+Umschalt+R
Konstante extrahieren Strg+Alt+C Menü Refactor Strg+Umschalt+R (Java)
Inline Strg+Alt+N Alt+Umschalt+I Strg+Umschalt+R (Java)
Signatur ändern, Parameterobjekt Strg+F6, Menü Refactor Alt+Umschalt+C, Menü Refactor (nicht eingebaut)

Ein Werkzeug-Refactoring ist zuverlässiger als ein Umbau von Hand: Es vergisst keine Verwendung, und viele IDEs prüfen vorher Konflikte (etwa einen Namen, den es im Gültigkeitsbereich schon gibt). Testen musst Du trotzdem, und committen auch.

Grenzen: dynamische Typisierung in Python

In Java weiß die IDE bei jedem Aufruf shop.abschliessen(...), dass shop vom Typ Shop ist. In Python steht der Typ erst zur Laufzeit fest (Duck Typing: Was eine Methode abschliessen hat, kann abgeschlossen werden). Die IDE muss raten. Drei typische Fallen:

  • Namen als Text: Aufrufe über getattr(objekt, "abschliessen"), Einträge in Konfigurationsdateien oder Templates sieht kein Umbenennen-Werkzeug.

  • gleichnamige Methoden fremder Klassen: Hat eine zweite Klasse ebenfalls eine Methode abschliessen, kann die IDE nicht sicher sagen, welche ein untypisierter Parameter beleg meint. Sie benennt dann zu viel oder zu wenig um und fragt im besten Fall nach.

  • Fehler erst zur Laufzeit: Ein übersehener Aufrufer fällt nicht beim Übersetzen auf, sondern erst als AttributeError, wenn die Zeile läuft.

// Java: Ein vergessener Aufrufer ist ein Übersetzungsfehler.
// Nach dem Umbenennen von abschliessen in schliesseAb:
shop.abschliessen(anfrage);   // error: cannot find symbol

Was hilft: Typannotationen an allen Parametern (dann weiß die IDE, welche Klasse gemeint ist), ein Typprüfer wie mypy oder Pyright im Build, eine Suche nach dem alten Namen als Text nach jedem Umbenennen und vor allem Tests, die jeden Aufrufer durchlaufen. Deshalb hat die Python-Fassung der Kata Typannotationen, und mypy prüft sie im strengen Modus ohne Befund.

Die Pfadfinderregel

Problem: Aufräumen, wenn Zeit ist

„Das räumen wir auf, wenn Zeit ist“ heißt in der Praxis: nie. Code wird mit jeder Änderung etwas unordentlicher, wenn niemand dagegenhält. Große Aufräumprojekte sind schwer zu begründen, blockieren das Team und scheitern oft.

Prinzip

Robert C. Martin formuliert in „Clean Code“ (2008) die Pfadfinderregel (Boy Scout Rule), angelehnt an eine Regel der Pfadfinder: „Leave the campground cleaner than you found it.“ Ähnlich schrieb schon der Gründer der Pfadfinderbewegung, Robert Baden-Powell, man solle die Welt ein wenig besser hinterlassen, als man sie vorgefunden hat. Übertragen auf Code: Hinterlasse jede Stelle, an der Du arbeitest, ein wenig sauberer, als Du sie vorgefunden hast. Ein besserer Name, eine extrahierte Methode, ein fehlender Test. Viele kleine Verbesserungen summieren sich, ohne dass jemand ein Aufräumprojekt genehmigen muss.

Im Teamalltag

  • Nur dort, wo Du ohnehin arbeitest. Die Regel gilt für den Code, den Deine Aufgabe berührt, nicht für das ganze Projekt.

  • Getrennte Commits. Das Aufräumen kommt in einen eigenen Commit vor oder nach der fachlichen Änderung (zwei Hüte). Im Review sieht man dann auf einen Blick, was Verhalten ändert und was nicht.

  • Erst das Netz, dann der Umbau. Ohne Tests an der Stelle ist der erste Aufräumschritt ein Charakterisierungstest.

  • Absprechen bei gemeinsamem Code. Umbenennungen in viel genutzten Klassen erzeugen Merge-Konflikte in den Branches der anderen. Größere Umbauten gehören in die Planung, etwa als eigenes Ticket.

Grenzen

Die Pfadfinderregel ersetzt keinen Entwurf. Wer jedes Mal „ein bisschen“ an einer Architekturfrage dreht, ohne Ziel, erzeugt Unruhe statt Ordnung. Und sie ist kein Freibrief, den eigenen Geschmack durchzusetzen: Wo das Team Konventionen hat, gelten diese.

Ursachenanalyse mit dem 5-mal-Warum

Problem: das Symptom behoben, die Ursache nicht

Ein Fehler wird gemeldet, Du findest die Zeile, korrigierst sie, fertig. Drei Wochen später taucht derselbe Fehler an einer anderen Stelle wieder auf. Die Korrektur hat das Symptom beseitigt, nicht die Ursache.

Prinzip

Die Methode 5-mal-Warum (5 Whys) geht auf Sakichi Toyoda zurück; bekannt gemacht hat sie Taiichi Ohno, der sie im Toyota-Produktionssystem einsetzte und in seinem Buch „Toyota Production System“ (japanisch 1978, englisch 1988) beschreibt. Du fragst ausgehend vom Symptom so lange „Warum?“, bis Du bei einer Ursache ankommst, an der eine Maßnahme den Fehler dauerhaft verhindert. Die Zahl Fünf ist ein Richtwert, kein Gesetz. Wichtig ist, jede Antwort mit Fakten zu belegen (Code, Commit-Historie, Tests), statt zu vermuten, und bei Prozessen zu landen statt bei Schuldigen.

Beispiel aus der Kata

Angenommen, in einer frühen Fassung des Shops (noch vor Stand 01) fällt der Buchhaltung der CSB-Rad GmbH auf, dass eine Bestellung mit Spedition, aber ohne Rad, keine Versandkosten berechnet hat. Die Zeile, die das bewirkte, sah so aus (absichtlich fehlerhaft):

// FEHLERHAFT: ohne Rad ergibt das 0 statt 4900
return zaehleRaeder(positionen) * SPEDITION_JE_RAD_CENT;

Die Ursachenanalyse:

  1. Warum wurden keine Versandkosten berechnet? Weil die Formel „Anzahl Räder × 49 €“ ohne Rad 0 ergibt.

  2. Warum steht die Formel so da? Weil die Regel „ohne Rad einmal 49 €“ beim Programmieren nicht umgesetzt wurde.

  3. Warum wurde sie nicht umgesetzt? Weil sie nur in einer E-Mail der Logistik stand und nicht im Ticket.

  4. Warum fiel es vor der Auslieferung nicht auf? Weil es für die Spedition nur einen Test mit Rädern gab.

  5. Warum gab es nur diesen Test? Weil die Testfälle aus dem Code abgeleitet wurden, nicht aus den Regeln; Sonderfälle der Anforderung hatte niemand gesammelt.

Die Maßnahmen setzen an mehreren Ebenen an. Unmittelbar: ein Test, der den Fehler zeigt (speditionOhneRadKostetEinePauschale, zuerst rot), dann die Korrektur Math.max(1, zaehleRaeder(positionen)) bzw. max(1, …), Test grün. Dauerhaft: Fachregeln gehören ins Ticket, und zu jeder Regel gehört ein Testfall, auch für Sonderfälle (Grenzwertanalyse, siehe Unit-Tests). Genau dieser Test ist heute Teil der Charakterisierungstests der Kata.

Die Grafik fasst die Kette zusammen: Die Korrektur der Formel behebt nur das Symptom, erst die Maßnahme am Ende der Kette verhindert ähnliche Fehler.

5-mal-Warum am Beispiel Spedition ohne Rad: vom Symptom „0 € statt 49 €“ über fünf Warum-Fragen bis zur Ursache „Testfälle aus dem Code abgeleitet, nicht aus den Regeln“; Test und Korrektur beheben das Symptom, Regeln im Ticket mit Testfall je Regel beheben die Ursache (mit KI erstellt)

Ursachenanalyse und Refactoring

Oft führt die Kette zu einem Code Smell. Steht eine Regel an zwei Stellen und wurde nur eine korrigiert, ist die Ursache doppelter Code; die Maßnahme ist ein Refactoring (Methode extrahieren oder verschieben), damit es die Regel nur noch einmal gibt. Die Reihenfolge bleibt dabei sauber getrennt: erst ein Test, der den Fehler zeigt, dann die Korrektur, dann der Umbau mit grünen Tests. Ein solcher Fall ist Thema der Übungsaufgaben.

Grenzen

Das 5-mal-Warum folgt einer einzigen Kette. Haben Fehler mehrere Ursachen, die zusammenwirken, hilft ein Ursache-Wirkungs-Diagramm (Ishikawa-Diagramm) mit mehreren Ästen. Und die Analyse ist nur so gut wie die Fakten: Wer bei „Warum?“ rät, landet bei der Lieblingsursache.

Komplexe Refactorings: ein Ausblick

Die bisherigen Refactorings verändern eine Methode oder Signatur. Die folgenden verändern den Entwurf, also die Aufteilung auf Klassen und ihre Beziehungen. Sie bestehen aus vielen einfachen Schritten und bekommen in dieser Reihe eigene Artikel:

Komplexe Refactorings und die Artikel, in denen sie vorkommen
Refactoring Wann? Artikel
Methoden nach Abstraktionsebene trennen, Bedingung zerlegen (Decompose Conditional), Phase aufteilen (Split Phase) Eine Methode mischt Prüfen, Rechnen und Ausgeben. Methoden gliedern
Klasse extrahieren (Extract Class), Methode verschieben (Move Function) Eine Klasse hat mehrere Zuständigkeiten, Methoden rechnen mit fremden Daten. Klassen schneiden
Vererbung durch Delegation ersetzen (Replace Inheritance with Delegation, 2. Aufl.: Replace Superclass with Delegate) Eine Unterklasse kann nicht alles, was die Oberklasse verspricht. Vererbung richtig einsetzen
Bedingung durch Polymorphie ersetzen (Replace Conditional with Polymorphism) Dieselbe Fallunterscheidung wächst mit jeder neuen Variante. Erweitern statt ändern

Für alle gilt dasselbe wie für die einfachen: erst das Netz, dann kleine Schritte, nach jedem grünen Schritt ein Commit.

In Java und Python

  • Parameterobjekt: In Java ist ein record (seit Java 16) die kürzeste Form einer unveränderlichen Datenklasse mit equals, hashCode und toString. In Python übernimmt das @dataclass(frozen=True); ein typing.NamedTuple ginge auch, ist aber zugleich ein Tupel und lässt sich versehentlich über Indizes lesen.

  • Benannte Argumente: Python kennt Schlüsselwortargumente (abschliessen(positionen=..., versandart="PAKET", ...)) und mit * in der Signatur sogar erzwungene. Das entschärft lange Parameterlisten, weil Verwechslungen auffallen; ein Parameterobjekt ersetzt es nicht, denn es gibt dem Datenklumpen keinen Namen und keinen Ort für Verhalten. Java hat keine benannten Argumente; dort ist das Parameterobjekt oder ein Builder der übliche Weg.

  • Sichtbarkeit beim Extrahieren: Extrahierte Hilfsmethoden sind in Java private, damit niemand von außen von ihnen abhängt. Python hat keine erzwungene Sichtbarkeit; der führende Unterstrich (_berechne_rabatt) ist eine Konvention, an die sich IDEs und Linter halten. Methoden ohne Zugriff auf das Objekt werden in Java static, in Python Funktionen auf Modulebene (wie _als_euro_text).

  • Signatur ändern: In Java meldet der Compiler jeden Aufrufer mit alter Signatur. In Python fällt er erst zur Laufzeit als TypeError auf, außer mypy oder die Tests decken ihn vorher auf.

Welche Refactorings helfen?

Die Refactorings dieses Artikels im Überblick, jeweils mit deutschem und englischem Namen nach Fowler:

  • Umbenennen (Rename Variable, Change Function Declaration): gibt Variablen und Methoden Namen, die sagen, was sie sind oder tun, siehe Umbenennen.

  • Variable extrahieren (Extract Variable): benennt einen schwer lesbaren Ausdruck mit einer Variablen, siehe Variable extrahieren.

  • Inline (Inline Variable, Inline Function): entfernt Variablen und Methoden, die nicht mehr sagen als ihr Inhalt, siehe Inline.

  • Magische Zahl durch Konstante ersetzen (Replace Magic Number with Symbolic Constant, 2. Aufl.: Replace Magic Literal): gibt unerklärten Werten einen Namen, siehe Konstante.

  • Methode extrahieren (Extract Function, früher Extract Method): macht aus einem zusammenhängenden Block eine benannte Methode, siehe Methode extrahieren.

  • Parameterobjekt einführen (Introduce Parameter Object): bündelt Werte, die immer zusammen auftreten, zu einem benannten Typ, siehe Parameterobjekt.

  • Algorithmus ersetzen (Substitute Algorithm): tauscht eine umständliche Berechnung gegen eine klarere mit gleichem Ergebnis, siehe Umbau der Kata.

Fazit

Refactoring heißt, die Struktur zu verbessern, ohne das Verhalten zu ändern, und zwar in Schritten, die so klein sind, dass ein roter Test sofort auf die Ursache zeigt. Das Sicherheitsnetz dafür sind Tests; wo es keine gibt, halten Charakterisierungstests fest, was der Code heute tut. Code Smells zeigen Dir, wo sich ein Umbau lohnt, und Fowlers Katalog sagt Dir, welcher. In der Kata ist aus einer Methode mit 70 Zeilen und vier losen Parametern eine lesbare Abfolge benannter Schritte mit einem Parameterobjekt geworden, ohne dass ein einziger der 16 Tests rot wurde. Die Pfadfinderregel sorgt dafür, dass das im Alltag nicht die Ausnahme bleibt, und das 5-mal-Warum dafür, dass ein behobener Fehler nicht zurückkommt. Mit den Übungsaufgaben zum Refactoring kannst Du das an der Kata und an der Mitfahr-App Mitfahrlabor üben.

Leitfragen

  1. Was ist ein Refactoring, und wie unterscheidet es sich von einer Fehlerbehebung und einer neuen Funktion?

Ein Refactoring ändert die innere Struktur von Code so, dass er leichter verständlich und änderbar wird, ohne das beobachtbare Verhalten zu ändern (Fowler). Eine Fehlerbehebung ändert das Verhalten bewusst, sodass es der Anforderung entspricht; eine neue Funktion erweitert es. Beim Refactoring bleiben die Tests unverändert grün, bei Fehlerbehebung und neuer Funktion kommen neue Tests hinzu. Die Arten werden getrennt (zwei Hüte, getrennte Commits). siehe Abschnitt „Was ist Refactoring?“

  1. Was ist ein Charakterisierungstest, und wie schreibst Du ihn?

Ein Charakterisierungstest (Feathers 2004) hält fest, was vorhandener Code heute tatsächlich tut, nicht was er tun soll. Du rufst den Code mit einer bewusst falschen Erwartung auf, liest das tatsächliche Ergebnis aus der Fehlermeldung, trägst es als Erwartung ein und wiederholst das für jeden Zweig. Er dient als Sicherheitsnetz für das Refactoring. siehe Abschnitt „Das Sicherheitsnetz: Charakterisierungstests“

  1. Warum baust Du in kleinen Schritten um und committest nach jedem grünen Schritt?

Wird ein Test rot, liegt die Ursache im letzten kleinen Schritt; Du nimmst ihn zurück, statt lange zu suchen. Jeder Commit ist ein Sicherungspunkt, und im Review lässt sich jeder Schritt einzeln nachvollziehen. Die Commit-Nachricht beschreibt den Schritt in einem Satz. siehe Abschnitt „Kleine Schritte und ein Commit nach jedem grünen Schritt“

  1. Welche Code Smells kennst Du, und welches Refactoring passt jeweils?

Zum Beispiel: rätselhafter Name → Umbenennen; magische Zahl → Konstante; doppelter Code und lange Methode → Methode extrahieren; lange Parameterliste und Datenklumpen → Parameterobjekt einführen; erklärender Kommentar → Methode oder Variable extrahieren; Primitive Obsession → Basistyp durch Objekt ersetzen; wiederholte Fallunterscheidung → Bedingung durch Polymorphie ersetzen; Feature Envy → Methode verschieben; große Klasse → Klasse extrahieren. siehe Abschnitt „Smell und passendes Refactoring“

  1. Wie gehst Du beim Extrahieren einer Methode vor, und wann lässt Du es bleiben?

Block mit einer Aufgabe wählen, Methode mit einem Namen anlegen, der sagt, was der Block tut, gelesene Variablen als Parameter, die eine geschriebene als Rückgabewert, Block durch Aufruf ersetzen, testen, committen. Schreibt der Block mehrere Variablen, ist er falsch geschnitten. Lass es bleiben, wenn der Name nicht mehr sagt als der Inhalt. siehe Abschnitt „Methode extrahieren“

  1. Was übernimmt die IDE beim Refactoring, und wo liegen die Grenzen in Python?

Die IDE führt Umbenennen, Extrahieren, Inline und Signaturänderungen über alle Verwendungen hinweg aus und prüft Konflikte. In Python steht der Typ erst zur Laufzeit fest: Aufrufe über Namen als Text (getattr), gleichnamige Methoden anderer Klassen und untypisierte Parameter kann die IDE nicht sicher zuordnen; vergessene Aufrufer fallen erst als AttributeError oder TypeError auf. Abhilfe: Typannotationen, mypy, Textsuche nach dem alten Namen, Tests. siehe Abschnitt „Refactoring-Werkzeuge der IDE“

  1. Was besagt die Pfadfinderregel, und was ist im Team dabei zu beachten?

Hinterlasse Code, an dem Du arbeitest, etwas sauberer, als Du ihn vorgefunden hast (Robert C. Martin, 2008, nach einer Regel der Pfadfinder). Im Team: nur im berührten Code, Aufräumen in eigenen Commits, zuerst Tests, größere Umbauten absprechen und planen, Konventionen des Teams beachten. siehe Abschnitt „Die Pfadfinderregel“

  1. Wie führst Du eine Ursachenanalyse mit dem 5-mal-Warum durch?

Vom Symptom aus wiederholt „Warum?“ fragen (Taiichi Ohno, Toyota-Produktionssystem), jede Antwort mit Fakten belegen, bis eine Ursache erreicht ist, an der eine Maßnahme den Fehler dauerhaft verhindert, meist ein Prozess (fehlender Testfall, Regel nicht im Ticket, doppelter Code). Dann zuerst einen roten Test schreiben, korrigieren, bei Bedarf refaktorisieren und die Prozessmaßnahme umsetzen. siehe Abschnitt „Ursachenanalyse mit dem 5-mal-Warum“

  1. Welche komplexen Refactorings gibt es, und wofür werden sie gebraucht?

Klasse extrahieren (Klasse mit mehreren Zuständigkeiten aufteilen), Bedingung durch Polymorphie ersetzen (wachsende Fallunterscheidung durch Klassen mit gemeinsamer Schnittstelle ersetzen), Vererbung durch Delegation ersetzen (Unterklasse, die nicht alles kann, durch ein Objekt ersetzen, an das delegiert wird). Sie verändern den Entwurf und bestehen aus vielen einfachen Schritten. siehe Abschnitt „Komplexe Refactorings: ein Ausblick“


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Refactoring: Code Smells erkennen und in sicheren Schritten umbauen” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/refactoring veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/design-prinzipien/clean-code. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 10.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: