Refactoring
Refactoring bedeutet Änderung der inneren Struktur des Codes ohne Beeinflussung äußeren Verhaltens.
Mit Hilfe von Refactoring kann man Code Smells beheben, und Lesbarkeit, Verständlichkeit und Wartbarkeit von Software verbessern.
Es ist wichtig, immer nur einzelne Schritte zu machen und anschließend die Testsuite laufen zu lassen, damit nicht versehentlich Fehler oder Verhaltensänderungen beim Refactoring eingebaut werden.
Prinzipiell kann man Refactoring manuell mit Search&Replace durchführen, aber es bietet sich an, hier die IDE-Unterstützung zu nutzen. Es stehen verschiedene Methoden zur Verfügung, die nicht unbedingt einheitlich benannt sein müssen oder in jeder IDE vorkommen. Zu den häufig genutzten Methoden zählen Rename, Extract, Move und Push Up/Pull Down.
- (K2) Begriff, Notwendigkeit und Vorgehen des/beim Refactoring
- (K2) Bedeutung kleiner Schritte beim Refactoring
- (K2) Bedeutung einer sinnvollen Testsuite beim Refactoring
- (K2) Refactoring: Nur innere Struktur ändern, nicht äußeres Verhalten!
- (K3) Anwendung der wichtigsten Refactoring-Methoden: Rename, Extract, Move, Push Up/Pull Down
Was ist Refactoring?
Refactoring ist, wenn einem auffällt, daß der Funktionsname
foobar
ziemlich bescheuert ist, und man die Funktion insinus
umbenennt.Quelle: "356: Refactoring" by Andreas Bogk on Lutz Donnerhacke: "Fachbegriffe der Informatik"
Refactoring (noun): a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behaviour.
Quelle: [Fowler2011, p. 53]
Refactoring: Änderungen an der inneren Struktur einer Software
- Beobachtbares (äußeres) Verhalten ändert sich dabei nicht
- Keine neuen Features einführen
- Keine Bugs fixen
- Keine öffentliche Schnittstelle ändern (Anmerkung: Bis auf Umbenennungen oder Verschiebungen von Elementen innerhalb der Software)
- Ziel: Verbesserung von Verständlichkeit und Änderbarkeit
Anzeichen, dass Refactoring jetzt eine gute Idee wäre
-
Code "stinkt" (zeigt/enthält Code Smells)
Code Smells sind strukturelle Probleme, die im Laufe der Zeit zu Problemen führen können. Refactoring ändert die innere Struktur des Codes und kann entsprechend genutzt werden, um die Smells zu beheben.
-
Schwer erklärbarer Code
Könnten Sie Ihren Code ohne Vorbereitung in der Abgabe erklären? In einer Minute? In fünf Minuten? In zehn? Gar nicht?
In den letzten beiden Fällen sollten Sie definitiv über eine Vereinfachung der Strukturen nachdenken.
-
Verständnisprobleme, Erweiterungen
Sie grübeln in der Abgabe, was Ihr Code machen sollte?
Sie überlegen, was Ihr Code bedeutet, um herauszufinden, wo Sie die neue Funktionalität anbauen können?
Sie suchen nach Codeteilen, finden diese aber nicht, da die sich in anderen (falschen?) Stellen/Klassen befinden?
Nutzen Sie die (neuen) Erkenntnisse, um den Code leichter verständlich zu gestalten.
"Three strikes and you refactor."
Quelle: [Fowler2011, p. 58]: "The Rule of Three"
Wenn Sie sich zum dritten Mal über eine suboptimale Lösung ärgern, dann werden Sie sich vermutlich noch öfter darüber ärgern. Jetzt ist der Zeitpunkt für eine Verbesserung.
Schauen Sie sich die entsprechenden Kapitel in [Passig2013] und [Fowler2011] an, dort finden Sie noch viele weitere Anhaltspunkte, ob und wann Refactoring sinnvoll ist.
Bevor Sie loslegen ...
-
Unit Tests schreiben
- Normale und ungültige Eingaben
- Rand- und Spezialfälle
-
Coding Conventions einhalten
- Sourcecode formatieren (lassen)
-
Haben Sie die fragliche Codestelle auch wirklich verstanden?!
Vorgehen beim Refactoring
Überblick über die Methoden des Refactorings
Die Refactoring-Methoden sind nicht einheitlich definiert, es existiert ein großer und uneinheitlicher "Katalog" an möglichen Schritten. Teilweise benennt jede IDE die Schritte etwas anders, teilweise werden unterschiedliche Möglichkeiten angeboten.
Zu den am häufigsten genutzten Methoden zählen
- Rename Method/Class/Field
- Encapsulate Field
- Extract Method/Class
- Move Method
- Pull Up, Push Down (Field, Method)
Best Practice
Eine Best Practice (oder nennen Sie es einfach eine wichtige Erfahrung) ist, beim Refactoring langsam und gründlich vorzugehen. Sie ändern die Struktur der Software und können dabei leicht Fehler oder echte Probleme einbauen. Gehen Sie also langsam und sorgsam vor, machen Sie einen Schritt nach dem anderen und sichern Sie sich durch eine gute Testsuite ab, die Sie nach jedem Schritt erneut ausführen: Das Verhalten der Software soll sich ja nicht ändern, d.h. die Tests müssen nach jedem einzelnen Refactoring-Schritt immer grün sein (oder Sie haben einen Fehler gemacht).
-
Kleine Schritte: immer nur eine Änderung zu einer Zeit
-
Nach jedem Refactoring-Schritt Testsuite laufen lassen
=> Nächster Refactoring-Schritt erst, wenn alle Tests wieder "grün"
-
Versionskontrolle nutzen: Jeden Schritt einzeln committen
Refactoring-Methode: Rename Method/Class/Field
Motivation
Name einer Methode/Klasse/Attributs erklärt nicht ihren Zweck.
Durchführung
Name selektieren, "Refactor > Rename
"
Anschließend ggf. prüfen
Aufrufer? Superklassen?
Beispiel
Vorher
public String getTeN() {}
Nachher
public String getTelefonNummer() {}
Refactoring-Methode: Encapsulate Field
Motivation
Sichtbarkeit von Attributen reduzieren.
Durchführung
Attribut selektieren, "Refactor > Encapsulate Field
"
Anschließend ggf. prüfen
Superklassen? Referenzen? (Neue) JUnit-Tests?
Beispiel
Vorher
int cps;
public void printDetails() {
System.out.println("Credits: " + cps);
}
Nachher
private int cps;
int getCps() { return cps; }
void setCps(int cps) { this.cps = cps; }
public void printDetails() {
System.out.println("credits: " + getCps());
}
Refactoring-Methode: Extract Method/Class
Motivation
- Codefragment stellt eigenständige Methode dar
- "Überschriften-Code"
- Code-Duplizierung
- Code ist zu "groß"
- Klasse oder Methode erfüllt unterschiedliche Aufgaben
Durchführung
Codefragment selektieren, "Refactor > Extract Method
" bzw. "Refactor > Extract Class
"
Anschließend ggf. prüfen
- Aufruf der neuen Methode? Nutzung der neuen Klasse?
- Neue JUnit-Tests nötig? Veränderung bestehender Tests nötig?
- Speziell bei Methoden:
- Nutzung lokaler Variablen: Übergabe als Parameter!
- Veränderung lokaler Variablen: Rückgabewert in neuer Methode und Zuweisung bei Aufruf; evtl. neue Typen nötig!
Beispiel
Vorher
public void printInfos() {
printHeader();
// Details ausgeben
System.out.println("name: " + name);
System.out.println("credits: " + cps);
}
Nachher
public void printInfos() {
printHeader();
printDetails();
}
private void printDetails() {
System.out.println("name: " + name);
System.out.println("credits: " + cps);
}
Refactoring-Methode: Move Method
Motivation
Methode nutzt (oder wird genutzt von) mehr Eigenschaften einer fremden Klasse als der eigenen Klasse.
Durchführung
Methode selektieren, "Refactor > Move
"
(ggf. "Keep original method as delegate to moved method" aktivieren)
Anschließend ggf. prüfen
- Aufruf der neuen Methode (Delegation)?
- Neue JUnit-Tests nötig? Veränderung bestehender Tests nötig?
- Nutzung lokaler Variablen: Übergabe als Parameter!
- Veränderung lokaler Variablen: Rückgabewert in neuer Methode und Zuweisung bei Aufruf; evtl. neue Typen nötig!
Beispiel
Vorher
public class Kurs {
int cps;
String descr;
}
public class Studi extends Person {
String name;
int cps;
Kurs kurs;
public void printKursInfos() {
System.out.println("Kurs: " + kurs.descr);
System.out.println("Credits: " + kurs.cps);
}
}
Nachher
public class Kurs {
int cps;
String descr;
public void printKursInfos() {
System.out.println("Kurs: " + descr);
System.out.println("Credits: " + cps);
}
}
public class Studi extends Person {
String name;
int cps;
Kurs kurs;
public void printKursInfos() { kurs.printKursInfos(); }
}
Refactoring-Methode: Pull Up, Push Down (Field, Method)
Motivation
- Attribut/Methode nur für die Oberklasse relevant: Pull Up
- Subklassen haben identische Attribute/Methoden: Pull Up
- Attribut/Methode nur für eine Subklasse relevant: Push Down
Durchführung
Name selektieren, "Refactor > Pull Up
" oder "Refactor > Push Down
"
Anschließend ggf. prüfen
Referenzen/Aufrufer? JUnit-Tests?
Beispiel
Vorher
public class Person { }
public class Studi extends Person {
String name;
public void printDetails() { System.out.println("name: " + name); }
}
Nachher
public class Person { protected String name; }
public class Studi extends Person {
public void printDetails() { System.out.println("name: " + name); }
}
Wrap-Up
Behebung von Bad Smells durch Refactoring
=> Änderung der inneren Struktur ohne Beeinflussung des äußeren Verhaltens
- Verbessert Lesbarkeit, Verständlichkeit, Wartbarkeit
- Immer nur kleine Schritte machen
- Nach jedem Schritt Testsuite laufen lassen
- Katalog von Maßnahmen, beispielsweise Rename, Extract, Move, Push Up/Pull Down, ...
- Unterstützung durch IDEs wie Eclipse, Idea, ...
Betrachten Sie das Theatrical Players Refactoring Kata. Dort finden Sie im Unterordner java/ einige Klassen mit unübersichtlichem und schlecht strukturierten Code.
Welche Bad Smells können Sie hier identifizieren?
Beheben Sie die Smells durch die schrittweise Anwendung von den aus der Vorlesung bekannten Refactoring-Methoden. Denken Sie auch daran, dass Refactoring immer durch eine entsprechende Testsuite abgesichert sein muss - ergänzen Sie ggf. die Testfälle.
- [Fowler2011] Refactoring
Fowler, M., Addison-Wesley, 2011. ISBN 978-0-201-48567-7. - [Inden2013] Der Weg zum Java-Profi
Inden, M., dpunkt.verlag, 2013. ISBN 978-3-8649-1245-0.
Kapitel 11: Refactorings - [Passig2013] Weniger schlecht programmieren
Passig, K. und Jander, J., O'Reilly, 2013. ISBN 978-3-89721-567-2.