archetype | title | linkTitle | author | readings | tldr | outcomes | quizzes | youtube | fhmedia | challenges | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
lecture-cg |
Refactoring |
Refactoring |
Carsten Gips (HSBI) |
|
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_.
|
|
|
|
Betrachten Sie das [Theatrical Players Refactoring Kata](https://github.com/emilybache/Theatrical-Players-Refactoring-Kata).
Dort finden Sie im Unterordner [java/](https://github.com/emilybache/Theatrical-Players-Refactoring-Kata/tree/main/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.
|
Refactoring ist, wenn einem auffällt, daß der Funktionsname
foobar
ziemlich bescheuert ist, und man die Funktion insinus
umbenennt.\hfill\ [Quelle: "356: Refactoring" by Andreas Bogk on Lutz Donnerhacke: "Fachbegriffe der Informatik"]{.origin}
\pause \bigskip \vfill
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.
\hfill\ [Quelle: [@Fowler2011, p. 53]]{.origin}
::: notes Refactoring: Änderungen an der [inneren Struktur]{.alert} 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 :::
-
Code "stinkt" (zeigt/enthält Code Smells)
::: notes 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. :::
\bigskip
-
Schwer erklärbarer Code
::: notes 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
::: notes 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. :::
\bigskip \vfill
::: center
"Three strikes and you refactor."
\hfill\ [Quelle: [@Fowler2011, p. 58]: "The Rule of Three"]{.origin} :::
::: notes 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. :::
- Unit Tests schreiben
- Normale und ungültige Eingaben
- Rand- und Spezialfälle
\smallskip
- Coding Conventions einhalten
- Sourcecode formatieren (lassen)
\bigskip \smallskip
- Haben Sie die [fragliche Codestelle auch wirklich verstanden]{.alert}?!
::: notes
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)
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
::: notes
Name einer Methode/Klasse/Attributs erklärt nicht ihren Zweck.
Name selektieren, "Refactor > Rename
"
Aufrufer? Superklassen?
:::
Vorher
public String getTeN() {}
Nachher
public String getTelefonNummer() {}
::: notes
Sichtbarkeit von Attributen reduzieren.
Attribut selektieren, "Refactor > Encapsulate Field
"
Superklassen? Referenzen? (Neue) JUnit-Tests?
:::
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());
}
::: notes
- Codefragment stellt eigenständige Methode dar
- "Überschriften-Code"
- Code-Duplizierung
- Code ist zu "groß"
- Klasse oder Methode erfüllt unterschiedliche Aufgaben
Codefragment selektieren, "Refactor > Extract Method
" bzw. "Refactor > Extract Class
"
- 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!
:::
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);
}
::: notes
Methode nutzt (oder wird genutzt von) mehr Eigenschaften einer fremden Klasse als der eigenen Klasse.
Methode selektieren, "Refactor > Move
"
(ggf. "Keep original method as delegate to moved method" aktivieren)
- 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!
:::
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);
}
}
::: slides
:::
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(); }
}
::: notes
- 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
Name selektieren, "Refactor > Pull Up
" oder "Refactor > Push Down
"
Referenzen/Aufrufer? JUnit-Tests?
:::
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); }
}
Behebung von Bad Smells durch Refactoring
\smallskip
=> Änderung der inneren Struktur ohne Beeinflussung des äußeren Verhaltens
\bigskip
- 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, ...
::: slides
Unless otherwise noted, this work is licensed under CC BY-SA 4.0.
\bigskip
- Citation "Refactoring ist, wenn ...": "356: Refactoring" by Andreas Bogk on Lutz Donnerhacke: "Fachbegriffe der Informatik"
- Citation "Refactoring (noun): a change ...": [@Fowler2011, p. 53]
- Citation "The Rule of Three": [@Fowler2011, p. 58] :::