Advanced Software Engineering WS0910 Kapitel3 Dr. Dominik Haneberg
REFACTORING 01.02.2010 Advanced Software Engineering 2
Inhalte dieses Kapitels Was ist Refactoring? Wozu und wann refactorn? Wie geht man vor? Beispiel für ein Refactoring (im Kleinen und im Großen) Code Smells und übliche Refactorings 01.02.2010 Advanced Software Engineering 3
GRUNDBEGRIFFE IM REFACTORING 01.02.2010 Advanced Software Engineering 4
Was ist Refactoring? 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 behavior. Refactoring (verb): To restructure software by applying a series of refactorings. Definition: Systematische Umstrukturierung von Code ohne dessen Verhalten zu ändern Nutzen: Bessere Lesbarkeit und Verständlichkeit Besseres Design Besser Wartbarkeit und Wiederverwendbarkeit M. Fowler: Refactoring Improving the Design of Existing Code, Addison- Wesley, 1999 01.02.2010 Advanced Software Engineering 5
Warum Refactorings anwenden Refactoring verbessert das Design der Software Oft geänderte Software verliert mit der Zeit ihre Struktur Redundanter Code erfordert vielfache Änderungen Extraktion gemeinsamer Teile ermöglicht jede Änderung an genau einer Stelle durchzuführen Refactoring macht die Software verständlicher Verständlichkeit für den Programmierer, der vielleicht später am Programm Erweiterungen vornimmt Wenn man sich in ein Programm einarbeitet, kann man es besser verstehen, wenn man es restrukturiert Refactoring hilft Bugs zu finden Im Prozess des Refactorings erwirbt man ein tiefes Verständnis für den Code. Man kann Bugs leichter finden Refactoring macht das Programmieren schneller Wenn der Code ein gutes Design hat, gut verständlich ist und daher auch wenig Bugs hat, kann man schneller programmieren 01.02.2010 Advanced Software Engineering 6
Vorgehen Wann refactorn? Ständig: Es gibt keinen festen Refactoring Zeitslot Wenn man Code liest und zu verstehen versucht Rule of Three Beim Hinzufügen neuer Funktionalität Beim Entfernen von Fehlern Bei Code Reviews Vorgehen Code Smell erkannt Ausreichend Unittests vorhanden und erfolgreich Refactoring anwenden Mit Unittests Erhalt der Funktionalität prüfen 01.02.2010 Advanced Software Engineering 7
Code Smells und Refactorings Code Smell: code smell is any symptom in the source code of a program that possibly indicates a deeper problem. Der Begriff geht auf Kent Beck zurück Bezeichnet Strukturen/Muster im Code, die auf schlechtes OO-Design, schlechte Wartbarkeit, Verständlichkeit usw. hindeuten Beispiele: Duplicate Code Large Class Inappropriate intimacy Large Method 01.02.2010 Advanced Software Engineering 8
Code Smells und Refactorings Refactoring: Präzise Vorgehensbeschreibung für eine Änderung am Code, mit dem Ziel einer Strukturverbesserung Bestandteile eines Refactorings: Problembeschreibung (Code Smell) Kurze Beschreibung der Codeänderung Schritt für Schritt Änderungsvorschrift Motivation für die Codeänderung (folgt oftmals aus den Grundprinzipien für gutes OO-Design) Ggf. Beispiele 01.02.2010 Advanced Software Engineering 9
Beispiel für ein Refactoring Introduce Explaining Variable Problem: You have a complicated expression Kurzfassung der Änderung: Put the result of the expression, or parts of the expression, in a temporary variable with a name that explains the purpose. Motivation: Expressions can become very complex and hard to read. In such situations temporary variables can be helpful to break down the expression into something more manageable. Introduce Explaining Variable is particularly valuable with conditional logic 01.02.2010 Advanced Software Engineering 10
Beispiel für ein Refactoring Introduce Explaining Variable Vorgehensweise: Declare final temporary variable, and set it to the result of a part of the complex expression. Replace the result part of the expression with the temp. If the result part of the expression is repeated, you can replace the repeats one at a time Compile and test. Repeat for other parts of the expression. 01.02.2010 Advanced Software Engineering 11
Beispiel für ein Refactoring Introduce Explaining Variable Beispiel: if ( (platform.touppercase().indexof("mac") > -1) && (browser.touppercase().indexof("ie") > -1) && wasinitialized() && resize > 0) { // do something 01.02.2010 Advanced Software Engineering 12
Beispiel für ein Refactoring Introduce Explaining Variable Beispiel: if ( (platform.touppercase().indexof("mac") > -1) && (browser.touppercase().indexof("ie") > -1) && wasinitialized() && resize > 0) { // do something final boolean ismaxos = platform.touppercase().indexof("mac") > -1; final boolean isiebrowser = browser.touppercase().indexof("ie") > -1; final boolean wasresized = resize > 0; if (ismacos && isiebrowser && wasinitialized() && wasresized) { // do something 01.02.2010 Advanced Software Engineering 13
EIN ANWENDUNGSBEISPIEL 01.02.2010 Advanced Software Engineering 14
Videos können im Laufe der Zeit zu verschiedenen Preiskategorien gehören (Normal, Kinder, Neuerscheinung) Jede Preiskategorie beinhaltet eine andere Preisberechnung Jede Ausleihe führt zur Gutschrift von Bonuspunkten, die am Jahresende abgerechnet werden Der Umfang der Gutschrift hängt ebenfalls von der Preiskategorie ab Für jeden Kunden soll es möglich sein, eine Rechnung für die ausgeliehenen Videos auszudrucken Titel und Preis eines jeden ausgeliehenen Videos Summe der Ausleihgebühren Summe der Bonuspunkte 01.02.2010 Advanced Software Engineering 15
name: String Customer statement:string getname(): String addrental(rental) 1 daysrented: int Rental getdaysrented(): int getmovie(): Movie * rentals * 1 movie title: String pricecode: int gettitle(): String getpricecode(): int setpricecode(int) Movie 01.02.2010 Advanced Software Engineering 16
Movie: Eine einfache Datenklasse public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String title; private int pricecode; public Movie(String title, int pricecode) { this.title = title; this.pricecode = pricecode; public int getpricecode() { return pricecode; public void setpricecode(int pricecode) { this.pricecode = pricecode; public String gettitle() { return title; 01.02.2010 Advanced Software Engineering 17
Rental: Repräsentiert eine Ausleihe public class Rental { private Movie movie; private int daysrented; public Rental(Movie movie, int daysrented) { this.movie = movie; this.daysrented = daysrented; public int getdaysrented() { return daysrented; public Movie getmovie() { return movie; 01.02.2010 Advanced Software Engineering 18
Customer: Repräsentiert einen Kunden public class Customer { private String name; private Vector rentals = new Vector(); public Customer(String name) { this.name = name; public void addrental(rental newrental) { rentals.addelement(newrental); public String getname() { return name; 01.02.2010 Advanced Software Engineering 19
Customer: Die Methode statement() 01.02.2010 Advanced Software Engineering 20
Customer: Die Methode statement() public String statement() { double totalamount = 0; int frequentrenterpoints = 0; Enumeration rented = rentals.elements(); String result = Rental Record for + getname() + \n ; while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); //determine amounts for each line switch (each.getmovie().getpricecode()) { case Movie.REGULAR: thisamount += 2; if (each.getdaysrented() > 2) thisamount += (each.getdaysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisamount += each.getdaysrented() * 3; break; case Movie.CHILDRENS: thisamount += 1.5; if (each.getdaysrented() > 3) thisamount += (each.getdaysrented() 3) * 1.5; break; 01.02.2010 Advanced Software Engineering 21
Customer: Die Methode statement() // add frequent renter points frequentrenterpoints++; // add bonus for a two day new release rental if ((each.getmovie().getpricecode() == Movie.NEW_RELEASE) && each.getdaysrented() > 1) frequentrenterpoints++; // show figures for this rental result += \t + each.getmovie().gettitle() + \t + String.valueOf(thisAmount) + \n ; totalamount += thisamount; // add footer result += Amount owned is + String.valueOf(totalAmount) + \n ; result += You earned + String.valueOf(frequentRenterPoints) + frequent renter points ; return result; Something is rotten in the state of Denmark. 01.02.2010 Advanced Software Engineering 22
Eine neue Kundenanforderung ist umzusetzen Zusätzlich zu statement() ist nun auch eine HTML- Ausgabe gewünscht Und nun: Den Code duplizieren?? 01.02.2010 Advanced Software Engineering 23
Aufteilung und Umverteilung der Methode statement() Refactoring: Extract Method //determine amounts for each line switch (each.getmovie().getpricecode()) { case Movie.REGULAR: thisamount += 2; if (each.getdaysrented() > 2) thisamount += (each.getdaysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisamount += each.getdaysrented() * 3; break; case Movie.CHILDRENS: thisamount += 1.5; if (each.getdaysrented() > 3) thisamount += (each.getdaysrented() 3) * 1.5; break; 01.02.2010 Advanced Software Engineering 24
Aufteilung und Umverteilung der Methode statement() Refactoring: Extract Method public int amountfor(rental each) { int thisamount = 0; switch (each.getmovie().getpricecode()) { case Movie.REGULAR: thisamount += 2; if (each.getdaysrented() > 2) thisamount += (each.getdaysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisamount += each.getdaysrented() * 3; break; case Movie.CHILDRENS: thisamount += 1.5; if (each.getdaysrented() > 3) thisamount += (each.getdaysrented() 3) * 1.5; break; return thisamount; 01.02.2010 Advanced Software Engineering 25
Aufteilung und Umverteilung der Methode statement() Refactoring: Extract Method public String statement() { double totalamount = 0; int frequentrenterpoints = 0; Enumeration rented = rentals.elements(); String result = Rental Record for + getname() + \n ; while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); thisamount = amountfor(each); 01.02.2010 Advanced Software Engineering 26
Aufteilung Falscher und Umverteilung Typ der Methode statement() Refactoring: Extract Method public int amountfor(rental each) { int thisamount = 0; switch (each.getmovie().getpricecode()) { case Movie.REGULAR: thisamount += 2; if (each.getdaysrented() > 2) thisamount += (each.getdaysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisamount += each.getdaysrented() * 3; break; case Movie.CHILDRENS: thisamount += 1.5; if (each.getdaysrented() > 3) thisamount += (each.getdaysrented() 3) * 1.5; break; return thisamount; 01.02.2010 Advanced Software Engineering 27
Gute Namen in amountfor() In der neuen Methode sind noch einige Variablennamen schlecht public double amountfor(rental each) { double thisamount = 0; switch (each.getmovie().getpricecode()) { case Movie.REGULAR: thisamount += 2; if (each.getdaysrented() > 2) thisamount += (each.getdaysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisamount += each.getdaysrented() * 3; break; case Movie.CHILDRENS: thisamount += 1.5; if (each.getdaysrented() > 3) thisamount += (each.getdaysrented() 3) * 1.5; break; return thisamount; 01.02.2010 Advanced Software Engineering 28
Gute Namen in amountfor() In der neuen Methode sind noch einige Variablennamen schlecht public double amountfor(rental arental) { double result = 0; switch (arental.getmovie().getpricecode()) { case Movie.REGULAR: result += 2; if (arental.getdaysrented() > 2) result += (arental.getdaysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: result += arental.getdaysrented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (arental.getdaysrented() > 3) result += (arental.getdaysrented() 3) * 1.5; break; return result; Any fool can write code that a computer can understand. Good programmers write code that humans can understand. 01.02.2010 Advanced Software Engineering 29
amountfor() in die Klasse Rental verschieben Refactoring: Move Method public class Rental {... public double getcharge() { double result = 0; switch (getmovie().getpricecode()) { case Movie.REGULAR: result += 2; if (getdaysrented() > 2) result += (getdaysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: result += getdaysrented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (getdaysrented() > 3) result += (getdaysrented() 3) * 1.5; break; return result; public class Customer { private double amountfor(rental arental) { return arental.getcharge(); 01.02.2010 Advanced Software Engineering 30
amountfor() in die Klasse Rental verschieben Refactoring: Move Method public String statement() { double totalamount = 0; int frequentrenterpoints = 0; Enumeration rented = rentals.elements(); String result = Rental Record for + getname() + \n ; while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); thisamount = each.getcharge(); 01.02.2010 Advanced Software Engineering 31
name: String Customer statement:string getname(): String addrental(rental) 1 daysrented: int Rental getdaysrented(): int getmovie(): Movie getcharge(): double * rentals * 1 movie title: String pricecode: int gettitle(): String getpricecode(): int setpricecode(int) Movie 01.02.2010 Advanced Software Engineering 32
Redundanz in der Methode statement() beseitigen Refactoring: Replace Temp with Query public String statement() { double totalamount = 0; int frequentrenterpoints = 0; Enumeration rented = rentals.elements(); String result = Rental Record for + getname() + \n ; while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); thisamount = each.getcharge(); // show figures for this rental result += \t + each.getmovie().gettitle() + \t + String.valueOf(thisAmount) + \n ; totalamount += thisamount; 01.02.2010 Advanced Software Engineering 33
Redundanz in der Methode statement() beseitigen Refactoring: Replace Temp with Query public String statement() { double totalamount = 0; int frequentrenterpoints = 0; Enumeration rented = rentals.elements(); String result = Rental Record for + getname() + \n ; while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); // show figures for this rental result += \t + each.getmovie().gettitle() + \t + String.valueOf(each.getCharge()) + \n ; totalamount += each.getcharge(); 01.02.2010 Advanced Software Engineering 34
Die frequent renter points in eigener Methode berechnen Refactoring: Extract Method while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); // add frequent renter points frequentrenterpoints++; // add bonus for a two day new release rental if ((each.getmovie().getpricecode() == Movie.NEW_RELEASE) && each.getdaysrented() > 1) frequentrenterpoints++; // show figures for this rental result += \t + each.getmovie().gettitle() + \t + String.valueOf(each.getCharge()) + \n ; totalamount += each.getcharge(); // add footer result += Amount owned is + String.valueOf(totalAmount) + \n ; result += You earned + String.valueOf(frequentRenterPoints) + frequent renter points ; return result; 01.02.2010 Advanced Software Engineering 35
Die frequent renter points in eigener Methode berechnen Refactoring: Extract Method und Move Method while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); frequentrenterpoints += each.getfrequentrenterpoints(); // show figures for this rental result += \t + each.getmovie().gettitle() + \t + String.valueOf(each.getCharge()) + \n ; totalamount += each.getcharge(); // add footer result += Amount owned is + String.valueOf(totalAmount) + \n ; result += You earned + String.valueOf(frequentRenterPoints) + frequent renter points ; return result; public class Rental { public int getfrequentrenterpoints() { if ((getmovie().getpricecode() == Movie.NEW_RELEASE) && getdaysrented() > 1) return 2; else return1; 01.02.2010 Advanced Software Engineering 36
name: String Customer statement:string getname(): String addrental(rental) 1 daysrented: int Rental getdaysrented(): int getmovie(): Movie getcharge(): double getfrequentrenterpoints(): int * rentals * 1 movie title: String pricecode: int gettitle(): String getpricecode(): int setpricecode(int) Movie 01.02.2010 Advanced Software Engineering 37
Die Methode statement() nach dem Refactoring 01.02.2010 Advanced Software Engineering 38
Weitere Hilfsvariablen eliminieren Refactoring: Replace Temp with Query while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); frequentrenterpoints += each.getfrequentrenterpoints(); // show figures for this rental result += \t + each.getmovie().gettitle() + \t + String.valueOf(each.getCharge()) + \n ; totalamount += each.getcharge(); // add footer result += Amount owned is + String.valueOf(totalAmount) + \n ; result += You earned + String.valueOf(frequentRenterPoints) + frequent renter points ; return result; 01.02.2010 Advanced Software Engineering 39
totalamount durch gettotalcharge() ersetzt Refactoring: Replace Temp with Query while (rented.hasmoreelements()) { double thisamount = 0; Rental each = (Rental) rented.nextelement(); frequentrenterpoints += each.getfrequentrenterpoints(); // show figures for this rental result += \t + each.getmovie().gettitle() + \t + String.valueOf(each.getCharge()) + \n ; // add footer result += Amount owned is + String.valueOf(getTotalCharge()) + \n ; result += You earned + String.valueOf(frequentRenterPoints) + frequent renter points ; return result; private double gettotalcharge() { double result = 0; Enumeration rented = rentals.elements(); while (rented.hasmoreelements()) { Rental each = (Rental) rented.nextelement(); result += each.getcharge(); return result; 01.02.2010 Advanced Software Engineering 40
name: String Customer statement:string getname(): String addrental(rental) gettotalcharge(): double gettotalfrequentrenterpoints(): int 1 daysrented: int Rental getdaysrented(): int getmovie(): Movie getcharge(): double getfrequentrenterpoints(): int * rentals * 1 movie title: String pricecode: int gettitle(): String getpricecode(): int setpricecode(int) Movie 01.02.2010 Advanced Software Engineering 41
Die Methode statement() nach dem Refactoring II 01.02.2010 Advanced Software Engineering 42
Eine neue Kundenanforderung ist umzusetzen Der Kunde denkt an eine Erweiterung seines Geschäfts. Es gibt neue Klassifikationen von Filmen und bestehende werden vielleicht geändert Und nun: Änderung in mehreren Methoden von Rental erforderlich (Fallunterscheidung über pricecode von Movie). Nichtlokale Änderungen sind nicht gut. 01.02.2010 Advanced Software Engineering 43
Schritt 1: getcharge und getfrequentrenterpoints in Klasse Movie public class Movie { public double getcharge(int daysrented) { double result = 0; switch (getpricecode()) { case Movie.REGULAR: result += 2; if (daysrented() > 2) result += (daysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: result += daysrented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (daysrented() > 3) result += (daysrented() 3) * 1.5; break; return result; public class Rental { public double getcharge() { return movie.getcharge(daysrented); 01.02.2010 Advanced Software Engineering 44
name: String Customer statement:string getname(): String addrental(rental) gettotalcharge(): double gettotalfrequentrenterpoints(): int 1 daysrented: int Rental getdaysrented(): int getmovie(): Movie getcharge(): double getfrequentrenterpoints(): int * rentals * movie 1 title: String pricecode: int Movie gettitle(): String getpricecode(): int setpricecode(int) getcharge(): double getfrequentrenterpoints(): int 01.02.2010 Advanced Software Engineering 45
title: String pricecode: int Movie gettitle(): String getpricecode(): int setpricecode(int) getcharge(): double getfrequentrenterpoints(): int 1 Price getcharge(int) : double RegularPrice ChildrensPrice NewReleasePrice getcharge(int) : double getcharge(int) : double getcharge(int) : double 01.02.2010 Advanced Software Engineering 46
Zugriff auf Statusinformation über getter/setter Refactoring: Self Encapsulate Field public class Movie { public Movie(String title, int pricecode) { this.title = title; setpricecode(pricecode); public abstract class Price { public abstract int getpricecode(); public abstract class ChildrensPrice extends Price { public abstract int getpricecode() { return Movie.CHILDRENS; public abstract class RegularePrice extends Price { public abstract int getpricecode() { return Movie.REGULAR; public abstract class NewReleasePrice extends Price { public abstract int getpricecode() { return Movie.NEW_RELEASE; 01.02.2010 Advanced Software Engineering 47
pricecode wird durch ein price-feld ersetzt public class Movie { private Price price; public int getpricecode() { return price.getpricecode(); public void setpricecode(int pricecode) { switch (pricecode) { case REGULAR: price = new RegularPrice(); break; case CHILDRENS: price = new ChildrensPrice(); break; case NEW_RELEASE: price = new NewReleasePrice(); break; default: throw new IllegalArgumentException("Incorrect Price Code"); 01.02.2010 Advanced Software Engineering 48
getcharge() in Klasse Price public class Movie { public double getcharge(int daysrented) { return price.getcharge(daysrented) ; public class Price { public double getcharge(int daysrented) { double result = 0; switch (getpricecode()) { case Movie.REGULAR: result += 2; if (daysrented() > 2) result += (daysrented() - 2) * 1.5; break; case Movie.NEW_RELEASE: result += daysrented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (daysrented() > 3) result += (daysrented() 3) * 1.5; break; return result; 01.02.2010 Advanced Software Engineering 49
Der letzte Schritt public class RegularPrice { public double getcharge(int daysrented) { double result = 2; if (daysrented > 2) result += (daysrented 2) * 1.5; return result; public class ChildrensPrice { public double getcharge(int daysrented) { double result = 1.5; if (daysrented > 3) result += (daysrented 3) * 1.5; return result; public class NewReleasePrice { public double getcharge(int daysrented) { return daysrented * 3; public class Price { public abstract double getcharge(int daysrented) ; 01.02.2010 Advanced Software Engineering 50
Die Aufrufsequenz nach den Refactorings 01.02.2010 Advanced Software Engineering 51
EINIGE REFACTORINGS 01.02.2010 Advanced Software Engineering 52
(Self) Encapsulate Field Zugriff auf Feld erfolgt direkt, aber die Koppelung an das Feld wird ungünstig Bei indirekten Zugriff übergang auf abgeleitetes Attribut möglich (z.b. in Unterklasse) Direkter Zugriff auf Feld von außen verlangt nach Wissen über Implementierungsdetail. Solche Informationen sollten verborgen werden. Zugriff über get-methode erlaubt lazy-initialization 01.02.2010 Advanced Software Engineering 53
(Self) Encapsulate Field Vorgehen get- und set-methoden anlegen Alle Referenzen auf das Feld suchen und durch Aufruf von get- bzw. set-methode ersetzen Feld als private deklarieren Nochmals prüfen, ob alle Verweise auf das Feld geändert wurden Kompilieren und testen 01.02.2010 Advanced Software Engineering 54
(Self) Encapsulate Field Beispiel private int low, high; boolean includes(int arg) { return arg >= low && arg <= high; private int low, high; boolean includes(int arg) { return arg >= getlow() && arg <= gethigh(); int getlow() { return low; int gethigh() { return high; 01.02.2010 Advanced Software Engineering 55
Move Method Eine Methode nutzt jetzt oder in Zukunft mehr Features einer anderen Klasse als derjenigen, in der sie definiert ist, oder die Methode wird häufiger von einer fremden Klasse benutzt als von der eigenen. Geschickte Positionierung von Methoden macht Klassen einfacher Coupling kann reduziert werden, Cohesion erhöht Verschieben von Feldern führt oft dazu, dass auch Methoden verschoben werden sollten 01.02.2010 Advanced Software Engineering 56
Move Method Vorgehen Alle Features, die die Quellmethode nutzt und die in der Quellklasse definiert sind, untersuchen, ob sie auch verschoben werden sollten Sub- und Superklassen auf weitere Definitionen der Methoden untersuchen Methode in der Zielklasse deklarieren Code in die Zielklasse kopieren. Ggf. an neue Umgebung anpassen, z.b. weitere Parameter Zielklasse kompilieren Festlegen, wie das korrekte Zielobjekt in der Quellklassen referenziert werden kann 01.02.2010 Advanced Software Engineering 57
Move Method Vorgehen Die Quellmethode in der Quellklasse in eine Methode abändern, die einen Aufruf an die neue Methode weiterleitet Kompilieren und testen Entscheiden ob die Quellmethode als Weiterleitungsmethode bleiben oder gelöscht werden soll Wenn die Quellmethode entfernt wird, alle Verweise auf sie mit Verweisen auf die neue Methode ersetzen Kompilieren und testen 01.02.2010 Advanced Software Engineering 58
Move Method Beispiel class Account double overdraftcharge() { if (type.ispremium()) { double result = 10; if (daysoverdrawn > 7) result += (daysoverdrawn 7) * 0,85; return result; else return daysoverdrawn * 1,75; double bankcharge() { double result = 4,5; if (daysoverdrawn > 0) result += overdraftcharge(); return result; private AccountType type; private int daysoverdrawn; 01.02.2010 Advanced Software Engineering 59
Move Method Beispiel class Account double bankcharge() { double result = 4,5; if (daysoverdrawn > 0) result += type.overdraftcharge(daysoverdrawn); return result; class AccountType double overdraftcharge(int daysoverdrawn) { if (ispremium()) { double result = 10; if (daysoverdrawn > 7) result += (daysoverdrawn 7) * 0,85; return result; else return daysoverdrawn * 1,75; 01.02.2010 Advanced Software Engineering 60
Push Down Method Verhalten einer Oberklasse ist nur für einige der Unterklassen relevant. Im Laufe der Zeit sind Unterklasse entstanden und ein Verhalten, dass einst in der Oberklasse definiert wurde ist nun nur noch in einigen Unterklasse relevant Mit dem Refactoring Extract Subclass wurde eine Unterklasse angelegt und einige Methoden gehören dorthin 01.02.2010 Advanced Software Engineering 61
Push Down Method Vorgehen Die Methode in allen Subklassen deklarieren und den Code hineinkopieren Methode aus Oberklasse löschen Kompilieren und testen Methode aus jeder Subklasse entfernen, die sie nicht benötigt Kompilieren und testen 01.02.2010 Advanced Software Engineering 62
Extract Interface Mehrere Klienten benutzen denselben Teil der Schnittstelle einer Klasse oder zwei Klassen haben einen Teil ihrer Schnittstellen gemeinsam. Zuständigkeiten von Klassen zerfallen oft in mehrere Bereiche. Oft ist es sinnvoll, diese einzelnen Bereiche als eigenständige Entitäten auszuzeichnen Programmieren gegen Interfaces besser als gegen Klassen, da so die Kopplung an änderbare Teile reduziert wird Interfaces erlauben eine schwache Mehrfachvererbung 01.02.2010 Advanced Software Engineering 63
Extract Interface Vorgehen Ein leeres Interface erstellen Alle gemeinsamen Operationen im neuen Interface deklarieren Alle relevanten Klassen als implementierende Klassen für das Interface deklarieren Typdeklarationen in den Klienten auf das neue Interface umstellen 01.02.2010 Advanced Software Engineering 64
Extract Interface Beispiel class Employee { public int getrate() { public boolean hasspecialskill() { double charge(employee emp, int days) { int base = emp.getrate() * days; if (emp.hasspecialskill()) return base * 1,05; else return base; 01.02.2010 Advanced Software Engineering 65
Extract Interface Beispiel interface Billable { public int getrate(); public boolean hasspecialskill(); class Employeee implements Billable { double charge(billable emp, int days) { int base = emp.getrate() * days; if (emp.hasspecialskill()) return base * 1,05; else return base; 01.02.2010 Advanced Software Engineering 66
Replace Magic Number with Symbolic Constant Im Code gibt es eine Zahlen-Literal mit einer spezifischen Bedeutung. Magic numbers machen jeden Code unverständlich und schlecht wartbar Änderungen an mehreren Orten erforderlich 01.02.2010 Advanced Software Engineering 67
Replace Magic Number with Symbolic Constant Vorgehen Eine Konstante deklarieren und auf den Wert der magic number setzen Alle Auftreten der magic number finden Prüfen, ob die Intention der magic number die Konstante ist. Wenn ja, ersetzen Kompilieren Wenn alle Auftreten der magic number ersetzt sind, kompilieren und testen 01.02.2010 Advanced Software Engineering 68
Replace Magic Number with Symbolic Constant Beispiel double potentialenergy(double mass, double height) { return mass * 9,81 * height; double potentialenergy(double mass, double height) { return mass * GRAVITATIONAL_CONSTANT * height; static final double GRAVITATIONAL_CONSTANT = 9,81; 01.02.2010 Advanced Software Engineering 69
Weitere Refactorings Es gibt noch viele, viele mehr www.refactoring.com 01.02.2010 Advanced Software Engineering 70
Literaturtipps M. Fowler: Refactoring Improving the Design of Existing Code; Addison-Wesley; 1999 R.C. Martin: Clean Code Refactoring, Patterns, Testen und Techniken für sauberen Code; mitp Verlag; 2009 Refactoring Home Page: http://www.refactoring.com/ Refactoring auf Wikipedia: http://de.wikipedia.org/wiki/refactoring 01.02.2010 Advanced Software Engineering 71