Tải bản đầy đủ
4 Temporäre Variable durch Abfrage ersetzen

4 Temporäre Variable durch Abfrage ersetzen

Tải bản đầy đủ

Sandini Bib
118

6 Methoden zusammenstellen

In den einfachen Fällen dieser Refaktorisierung werden die temporären Variablen
nur einmal zugewiesen, oder der Ausdruck, der zugewiesen wird, ist frei von Seiteneffekten. Andere Fälle sind schwieriger, aber auch möglich. Es kann sein, dass
Sie zunächst Temporäre Variable zerlegen (125) oder Abfrage von Veränderung trennen
(285) einsetzen müssen, um die Verhältnisse zu vereinfachen. Wird die temporäre
Variable verwendet, um ein Ergebnis zu sammeln (wie eine Summe in einer
Schleife), so müssen Sie einige Logik in die Abfragemethode kopieren.

6.4.2

Vorgehen

Hier ist der einfachste Fall:


Suchen Sie eine temporäre Variable, der einmal etwas zugewiesen wird.

➾ Wird eine temporäre Variable mehr als einmal gesetzt, so sollten Sie erwägen,
Temporäre Variable zerlegen (125) einzusetzen.


Deklarieren Sie die Variable als final.



Wandeln Sie den Code um.

➾ Das stellt sicher, dass die Variable wirklich nur einmal gesetzt wird.


Extrahieren Sie die rechte Seite der Zuweisung in eine Methode.

➾ Deklarieren Sie die Methode zunächst als privat. Sie können später weitere Verwendungsmöglichkeiten finden, aber es ist ein Leichtes, den Schutz zu reduzieren.

➾ Stellen Sie sicher, dass die extrahierte Methode frei von Seiteneffekten ist, d.h. dass
sie kein anderes Objekt verändert. Ist sie nicht frei von Seiteneffekten, verwenden
Sie Abfrage von Veränderung trennen (285).


Wandeln Sie um und testen Sie.



Wenden Sie Temporäre Variable durch Abfrage ersetzen (117) auf die temporäre
Variable an.

Temporäre Variablen werden häufig verwendet, um zusammenfassend Informationen in Schleifen zu speichern. Die ganze Schleife kann in eine Methode extrahiert werden; das entfernt einige Zeilen störenden Codes. Manchmal dient eine
Schleife dazu, mehrere Werte aufzusummieren. In diesem Fall duplizieren Sie die
Schleife für jede temporäre Variable, so dass Sie jede temporäre Variable durch
eine Abfrage ersetzen können. Die Schleife sollte sehr einfach sein, so dass mit der
Duplikation des Codes wenig Gefahren verbunden sind.

Sandini Bib
6.4 Temporäre Variable durch Abfrage ersetzen

119

Sie mögen sich in diesem Fall Sorgen über die Performance machen. Lassen Sie
dies wie auch andere Performance-Fragen für den Augenblick außer Betracht. In
neun von zehn Fällen wird es keine Rolle spielen. Und wenn es eine Rolle spielt,
beheben Sie das Problem während der Optimierung. Mit Ihrem besser refaktorisierten Code werden Sie oft leistungsfähigere Optimierungen finden, die Sie ohne
Refaktorisieren übersehen hätten. Wenn alles schief geht, können Sie immer
noch leicht die temporäre Variable wieder einführen.

6.4.3

Beispiel

Ich beginne mit einer einfachen Methode:
double getPrice() {
int basePrice = _quantity * _itemPrice;
double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}

Ich neige dazu, beide temporären Variablen auf einmal zu ersetzen.
Obwohl es in diesem Fall ziemlich klar ist, kann ich testen, ob beiden temporären
Variablen nur einmal zugewiesen wird, indem ich sie als final deklariere.
double getPrice() {
final int basePrice = _quantity * _itemPrice;
final double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}

Das Umwandeln wird mich auf etwaige Probleme hinweisen. Ich mache dies als
erstes, denn wenn es ein Problem gibt, so sollte ich diese Refaktorisierung nicht
durchführen. Ich extrahiere deshalb nur jeweils eine temporäre Variable. Zuerst
extrahiere ich die rechte Seite der Zuweisung:
double getPrice() {
final int basePrice = basePrice();
final double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;

Sandini Bib
120

6 Methoden zusammenstellen

}
private int basePrice() {
return _quantity * _itemPrice;
}

Ich wandle um und teste, dann beginne ich mit Temporäre Variable durch Abfrage
(117) ersetzen. Als erstes ersetze ich die erste Referenz auf die temporäre Variable:
double getPrice() {
final int basePrice = basePrice();
final double discountFactor;
if (basePrice() > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}

Umwandeln, testen und die nächste (das hört sich an wie der Anführer bei einer
Polonaise). Da dies die letzte Referenz ist, entferne ich auch gleich die Deklaration
der temporären Variablen:
double getPrice() {
final double discountFactor;
if (basePrice() > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice() * discountFactor;
}

Nachdem diese Deklaration verschwunden ist, kann ich mit discountFactor ähnlich verfahren:
double getPrice() {
final double discountFactor = discountFactor();
return basePrice() * discountFactor;
}
private double discountFactor() {
if (basePrice() > 1000) return 0.95;
else return 0.98;
}

Beachten Sie, wie schwierig es gewesen wäre, discountFactor zu extrahieren,
wenn ich basePrice nicht durch eine Abfrage ersetzt hätte.

Sandini Bib
6.5 Erklärende Variable einführen

121

Die getPrice-Methode sieht nun so aus:
double getPrice() {
return basePrice() * discountFactor();
}

6.5

Erklärende Variable einführen

Sie haben einen komplizierten Ausdruck.
Stecken Sie das Ergebnis des Ausdrucks oder eines Teils davon in eine temporäre Variable
mit einem Namen, der ihre Aufgabe erläutert.
if ((platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}


final boolean isMacOs
= 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
}

6.5.1

Motivation

Ausdrücke können sehr komplex werden und sind dann schwer zu lesen. In solchen Situationen können temporäre Variablen hilfreich sein, um den Ausdruck so
zu zerlegen, dass er besser zu handhaben wird.
Erklärende Variable einführen ist besonders bei Bedingungen hilfreich, in denen es
nützlich ist, eine Klausel der Bedingung zu nehmen und durch eine sinnvoll benannte temporäre Variable zu erläutern. Ein anderer Fall ist ein langer Algorithmus, in dem jeder Schritt der Berechnung durch eine temporäre Variable erläutert
werden kann.

Sandini Bib
122

6 Methoden zusammenstellen

Erklärende Variable einführen ist eine sehr häufig vorkommende Refaktorisierung,
aber ich gestehe, dass ich sie nicht oft verwende. Fast immer ziehe ich es vor, Methode extrahieren (106) zu verwenden, sofern dies möglich ist. Eine temporäre Variable ist nur im Kontext einer Methode nützlich. Eine Methode ist durch das ganze
Objekt und für andere Objekte nützlich. Es gibt aber Fälle, in denen lokale Variablen es schwierig machen, Methode extrahieren (106) zu verwenden. In diesen Fällen
verwende ich Erklärende Variable einführen (121).

6.5.2

Vorgehen



Deklarieren Sie eine finale temporäre Variable, und setzen Sie gleich dem Ergebnis eines Teils eines komplexen Ausdrucks.



Ersetzen Sie den entsprechenden Teil des Ausdrucks durch den Wert der Variablen.

➾ Wird das Ergebnis wiederholt, so können Sie jeweils eine Wiederholung ersetzen.


Wandeln Sie um und testen Sie.



Wiederholen Sie dies für andere Teile des Ausdrucks.

6.5.3

Beispiel

Ich beginne mit einer einfachen Berechnung:
double price() {
// price is base price – quantity discount + shipping
return _quantity * _itemPrice –
Math.max(0, _quantity – 500) * _itemPrice * 0.05 +
Math.min(_quantity * _itemPrice * 0.1, 100.0);
}

So einfach dies auch sein mag, ich kann diese Berechnung noch leichter verständlich machen. Zuerst identifziere ich den Basispreis als Menge mal Stückpreis. Diesen Teil der Berechnung speichere ich in einer temporären Variablen basePrice:
double price() {
// price is base price – quantity discount + shipping
final double basePrice = _quantity * _itemPrice;
return basePrice –
Math.max(0, _quantity – 500) * _itemPrice * 0.05 +
Math.min(_quantity * _itemPrice * 0.1, 100.0);
}

Sandini Bib
6.5 Erklärende Variable einführen

123

Menge (quantity) mal Stückpreis (itemPrice) wird auch später noch benutzt, also
kann ich es auch dort durch die temporäre Variable ersetzen:
double price() {
// price is base price – quantity discount + shipping
final double basePrice = _quantity * _itemPrice;
return basePrice –
Math.max(0, _quantity – 500) * _itemPrice * 0.05 +
Math.min(basePrice * 0.1, 100.0);
}

Als nächstes nehme ich den Mengenrabatt (quantityDiscount):
double price() {
// price is base price – quantity discount + shipping
final double basePrice = _quantity * _itemPrice;
final double quantityDiscount = Math.max(0, _quantity – 500) * _itemPrice *
0.05;
return basePrice – quantityDiscount +
Math.min(basePrice * 0.1, 100.0);
}

Schließlich höre ich mit den Versandkosten (shipping) auf. Während ich das mache, kann ich auch den Kommentar entfernen, da er nicht mehr aussagt als der
Code:
double price() {
final double basePrice = _quantity * _itemPrice;
final double quantityDiscount = Math.max(0, _quantity – 500) *
_itemPrice * 0.05;
final double shipping = Math.min(basePrice * 0.1, 100.0);
return basePrice – quantityDiscount + shipping;
}

6.5.4

Beispiel mit Methode extrahieren

In diesem Beispiel hätte ich für gewöhnlich nicht die erklärenden temporären Variablen gewählt. Ich hätte es vorgezogen Methode extrahieren (106) einzusetzen.
Ich beginne wieder mit
double price() {
// price is base price – quantity discount + shipping
return _quantity * _itemPrice –
Math.max(0, _quantity – 500) * _itemPrice * 0.05 +
Math.min(_quantity * _itemPrice * 0.1, 100.0);
}

Sandini Bib
124

6 Methoden zusammenstellen

aber dieses Mal extrahiere ich eine Methode für den Basispreis (basePrice):
double price() {
// price is base price – quantity discount + shipping
return basePrice() –
Math.max(0, _quantity – 500) * _itemPrice * 0.05 +
Math.min(basePrice() * 0.1, 100.0);
}
private double basePrice() {
return _quantity * _itemPrice;
}

Ich mache wieder jeweils einen Schritt. Wenn ich fertig bin, habe ich:
double price() {
return basePrice() – quantityDiscount() + shipping();
}
private double quantityDiscount() {
return Math.max(0, _quantity – 500) * _itemPrice * 0.05;
}
private double shipping() {
return Math.min(basePrice() * 0.1, 100.0);
}
private double basePrice() {
return _quantity * _itemPrice;
}

Ich bevorzuge Methode extrahieren (106), da diese Methoden nun für alle andere
Teile des Objekts verfügbar sind, die sie benötigen. Für den Anfang mache ich sie
privat, aber ich kann das immer abschwächen, wenn andere Objekte sie benötigen. Ich finde, es ist meistens keine größere Anstrengung, Methode extrahieren
(106) einzusetzen als Erklärende Variable einführen (121).
Wann also verwende ich Erklärende Variable einführen (121)? Die lautet: wenn Methode extrahieren (106) mehr Arbeit macht. Stecke ich in einem Algorithmus mit
vielen lokalen Variablen, so kann ich Methode extrahieren (106) vielleicht nicht
einfach anwenden. In diesem Fall verwende ich Erklärende Variable einführen
(121), um mir zu helfen zu verstehen, was vorgeht. Wird die Logik weniger verworren, so kann ich immer noch Temporäre Variable durch Abfragen ersetzen (117)
anwenden. Die temporäre Variable ist auch nützlich, wenn ich am Ende Methode
durch Methodenobjekt ersetzen (132) anwenden muss.

Sandini Bib
6.6 Temporäre Variable zerlegen

6.6

125

Temporäre Variable zerlegen

Sie haben eine temporäre Variable, der mehrfach etwas zugewiesen wird; es ist
aber weder eine Schleifenvariable noch eine Ergebnisse sammelnde temporäre
Variable.
Definieren Sie für jede Zuweisung eine temporäre Variable.
double temp = 2 * (_height + _width);
System.out.println (temp);
temp = _height * _width;
System.out.println (temp);


final double perimeter = 2 * (_height + _width);
System.out.println (perimeter);
final double area = _height * _width;
System.out.println (area);

6.6.1

Motivation

Temporäre Variablen sind für verschiedene Aufgaben da. Einige dieser Aufgaben
führen auf natürliche Weise dazu, dass der Variablen mehrfach etwas zugewiesen
wird. Schleifenvariablen [Beck] ändern sich mit jedem Schleifendurchlauf (wie
das i in for (int i=0; i<10; i++)). Sammelnde Variablen [Beck] sammeln einen
Wert ein, der in der Methode aufgebaut wird.
Viele andere temporäre Variablen halten das Ergebnis eines lang sich dahinwindenden Stücks Code fest, um später leicht darauf zugreifen zu können. Solchen
Variablen sollte nur einmal etwas zugewiesen werden. Wird ihnen mehrfach etwas zugewiesen, so ist dies ein Zeichen, dass sie in der Methode mehr als eine Verantwortlichkeit haben. Jede Variable mit mehr als einer Verantwortlichkeit sollte
durch eine eigene Variable für jede der Verantwortlichkeiten ersetzt werden. Eine
temporäre Variable für zwei verschiedene Dinge zu verwenden, verwirrt den Leser.

Sandini Bib
126

6 Methoden zusammenstellen

6.6.2


Vorgehen

Ändern Sie den Namen der temporären Variablen in der Deklaration und der
ersten Zuweisung.

➾ Wenn die späteren Zuweisungen die Form »i = i + ein Ausdruck« haben, so weist
dies auf eine sammelnde Variable hin, also zerlegen Sie sie nicht. Die Operatoren
für sammelnde Variablen sind meistens Addition, String-Verkettung, Schreiben
auf einen Stream oder Hinzufügen zu einer Collection.


Deklarieren Sie die neue Variable als final.



Ändern Sie alle Referenzen auf die Variable bis zu ihrer zweiten Zuweisung.



Deklarieren Sie die Variable bei ihrer zweiten Zuweisung.



Wandeln Sie um und testen Sie.



Wiederholen Sie dies in Stufen, indem Sie auf jeder Stufe bei der Deklaration
die Variable umbenennen und die Referenzen bis zur nächsten neuen Zuweisung anpassen.

6.6.3

Beispiel

Für dieses Beispiel berechne ich die Entfernung, die ein Haggis1 zurücklegt. Aus
der Ruhelage erfährt der Haggis eine Anfangsbeschleunigung. Nach einer Wartezeit kommt eine zweite Kraft, die den Haggis weiter beschleunigt. Mittels der allgemeinen Bewegungsgesetze kann ich die zurückgelegte Entfernung wie folgt berechnen:
double getDistanceTravelled (int time) {
double result;
double acc = _primaryForce / _mass;
int primaryTime = Math.min(time, _delay);
result = 0.5 * acc * primaryTime * primaryTime;
int secondaryTime = time – _delay;
if (secondaryTime > 0) {
double primaryVel = acc * _delay;
acc = (_primaryForce + _secondaryForce) / _mass;

1. Anm. d. Ü.: Haggis ist eine schottische Spezialität: im Schafsmagen gegarte Schafsinnereien. Die Form ähnelt einem Ball.

Sandini Bib
6.6 Temporäre Variable zerlegen

127

result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime *
secondaryTime;
}
return result;
}

Dies ist eine schrecklich nette kleine Funktion. Die interessante Sache für unser
Beispiel ist die Variable acc, die zweimal gesetzt wird. Sie hat zwei Aufgaben: Erst
speichert sie die Anfangsbeschleunigung und später die Beschleunigung durch
zwei Kräfte. Diese Variable will ich zerlegen.
Ich beginne, indem ich den Namen der Variablen ändere und den neuen Namen
als final deklariere. Dann ändere ich alle Referenzen der Variablen bis zur nächsten Zuweisung. Bei der nächsten Zuweisung deklariere ich die alte Variable:
double getDistanceTravelled (int time) {
double result;
final double primaryAcc = _primaryForce / _mass;
int primaryTime = Math.min(time, _delay);
result = 0.5 * primaryAcc * primaryTime * primaryTime;
int secondaryTime = time – _delay;
if (secondaryTime > 0) {
double primaryVel = primaryAcc * _delay;
double acc = (_primaryForce + _secondaryForce) / _mass;
result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime *
secondaryTime;
}
return result;
}

Ich wählte den neuen Namen, um nur die erste Verwendung der Variablen zu
charakterisieren. Ich deklariere die Variable als final, um sicherzustellen, dass sie
nur einmal gesetzt wird. Ich kann dann die Originalvariable bei ihrer zweiten Zuweisung deklarieren. Jetzt kann ich umwandeln und testen, und alles sollte funktionieren.
Ich fahre mit der zweiten Zuweisung der Variablen fort. Die entfernt den Originalnamen der Variablen ganz und ersetzt ihn durch einen Namen für die zweite Verwendung.
double getDistanceTravelled (int time) {
double result;
final double primaryAcc = _primaryForce / _mass;
int primaryTime = Math.min(time, _delay);
result = 0.5 * primaryAcc * primaryTime * primaryTime;

Sandini Bib
128

6 Methoden zusammenstellen

int secondaryTime = time – _delay;
if (secondaryTime > 0) {
double primaryVel = primaryAcc * _delay;
final double secondaryAcc = (_primaryForce + _secondaryForce) / _mass;
result += primaryVel * secondaryTime + 0.5 *
secondaryAcc * secondaryTime * secondaryTime;
}
return result;
}

Ich bin sicher, Ihnen fallen hierfür noch viele weitere Refaktorisierungen ein. Viel
Spaß dabei. (Ich bin sicher, es ist besser, als den Haggis zu essen.)

6.7

Zuweisungen zu Parametern entfernen

In Ihrem Code wird einem Parameter etwas zugewiesen.
Verwenden Sie statt dessen eine temporäre Variable.
int discount (int inputVal, int quantity, int yearToDate) {
if (inputVal > 50) inputVal -= 2;


int discount (int inputVal, int quantity, int yearToDate) {
int result = inputVal;
if (inputVal > 50) result -= 2;

6.7.1

Motivation

Lassen Sie mich zuerst erklären, was der Ausdruck »einem Parameter etwas zuweisen« bedeutet. Wenn Sie ein Objekt namens foo übergeben, heißt »dem Parameter
etwas zuweisen«, foo so zu ändern, dass foo auf ein anderes Objekt verweist. Ich
habe kein Problem damit, etwas mit dem übergebenen Objekt zu machen; ich
mache das laufend. Ich wehre mich nur dagegen, foo ganz durch ein anderes Objekt zu ersetzen:
void aMethod(Object foo) {
foo.modifyInSomeWay(); // Das ist OK
foo = anotherObject;
// Ärger und Verzweiflung werden folgen