Abfrage von Verọnderung trennen

Một phần của tài liệu AW refactoring improving the design of existing code (Trang 309 - 313)

Sie haben eine Methode, die einen Wert zurückliefert, aber auch den Zustand des Objekts ọndert.

Erstellen Sie zwei Methoden, eine für die Abfrage und eine für die Änderung.

10.4.1 Motivation

Eine Funktion, die Ihnen einen Wert liefert und keine erkennbaren Seiteneffekte hat, ist eine sehr nỹtzliche Sache. Sie kửnnen diese Funktion so oft aufrufen, wie Sie wollen. Sie kửnnen den Aufruf an andere Stellen der Methode verschieben.

Kurz gesagt, Sie haben sehr viel weniger, worüber Sie sich Sorgen machen müssen.

Der Unterschied zwischen Methoden mit Seiteneffekten und solchen ohne sollte klar zu erkennen sein. Eine gute Regel ist, dass eine Methode, die einen Wert zu- rückliefert, keine erkennbaren Seiteneffekte haben sollte. Einige Programmierer behandeln dies als eine unbedingt einzuhaltende Regel [Meyer]. Ich halte mich nicht hundertprozentig daran (wie bei allen Dingen), aber ich versuche meistens, dieser Regel zu folgen, und bin gut damit gefahren.

Begegnet Ihnen eine Methode, die einen Wert zurückliefert, aber auch Seitenef- fekte hat, so sollten Sie versuchen, die Abfrage von der Änderung zu trennen.

Beachten Sie die Formulierung erkennbare Seiteneffekte. Eine gebrọuchliche Form der Optimierung besteht darin, das Ergebnis einer Abfrage in einem Feld zwi- schenzuspeichern, damit wiederholte Aufrufe schneller sind. Obwohl dies den Zustand des Objekts mit dem Puffer ọndert, ist die Änderung nicht erkennbar.

Jede Folge von Abfragen wird die gleichen Ergebnisse für jede Abfrage liefern [Meyer].

10.4.2 Vorgehen

• Erstellen Sie eine Abfrage, die den gleichen Wert zurückliefert, wie die Origi- nalmethode.

Suchen Sie in der Originalmethode, was sie zurückliefert. Wenn der Rückgabewert eine temporọre Variable ist, suchen Sie deren Zuweisung.

getTotalOutstandingAndSetReadyForSummaries Customer

getTotalOutstanding setReadyForSummaries

Customer

• Modifizieren Sie die Originalmethode so, dass sie das Ergebnis eines Aufrufs der Abfrage zurückliefert.

Jede Rückgabe der Originalmethode sollte return newQuery() lauten, statt irgen- detwas anderes zu liefern.

Wenn die Methode eine temporọre Variable mit einer einzigen Zuweisung verwen- dete, um den Rỹckgabewert festzuhalten, so sollten Sie sie jetzt entfernen kửnnen.

• Wandeln Sie um und testen Sie.

• Ersetzen Sie jeden Aufruf der Originalmethode durch einen Aufruf der Abfrage.

Fügen Sie vor der Zeile mit der Abfrage einen Aufruf der Originalmethode ein.

Wandeln Sie nach jeder Änderung eines Aufrufs um und testen Sie.

• Deklarieren Sie den Rückgabewert der Originalmethode als void, und entfer- nen Sie die return-Ausdrücke.

10.4.3 Beispiel

Hier ist eine Funktion, die mir für ein Sicherheitssystem den Namen eines Misse- tọters (Miscreant) liefert und einen Alarm auslửst. Die Regel lautet, dass nur ein Alarm ausgelửst wird, auch wenn es sich um mehr als einen Missetọter handelt:

String foundMiscreant(String[] people){

for (int i = 0; i < people.length; i++) { if (people[i].equals ("Don")){

sendAlert();

return "Don";

}

if (people[i].equals ("John")){

sendAlert();

return "John";

} }

return "";

}

Sie wird aufgerufen von:

void checkSecurity(String[] people) { String found = foundMiscreant(people);

someLaterCode(found);

}

Um die Abfrage von der Änderung zu trennen, muss ich als Erstes eine geeignete Abfrage erstellen, die den gleichen Wert liefert wie die Änderung, dies aber ohne Seiteneffekte tut.

String foundPerson(String[] people){

for (int i = 0; i < people.length; i++) { if (people[i].equals ("Don")){

return "Don";

}

if (people[i].equals ("John")){

return "John";

} }

return "";

}

Dann ersetze ich jeden return-Befehl in der Originalmethode, immer nur einen auf einmal, durch Aufrufe der neuen Abfrage. Ich teste nach jeder Ersetzung.

Wenn ich fertig bin, sieht die Originalmethode wie folgt aus:

String foundMiscreant(String[] people){

for (int i = 0; i < people.length; i++) { if (people[i].equals ("Don")){

sendAlert();

return foundPerson(people);

}

if (people[i].equals ("John")){

sendAlert();

return foundPerson(people);

} }

return foundPerson(people);

}

Nun ọndere ich die aufrufende Methode so ab, dass sie zwei Aufrufe enthọlt: erst einen für die Änderung und dann einen für die Abfrage:

void checkSecurity(String[] people) { foundMiscreant(people);

String found = foundPerson(people);

someLaterCode(found);

}

Nachdem ich das für alle Aufrufe getan habe, kann ich die Änderung anpassen und den Rückgabewert als void deklarieren:

void foundMiscreant (String[] people){

for (int i = 0; i < people.length; i++) { if (people[i].equals ("Don")){

sendAlert();

return;

}

if (people[i].equals ("John")){

sendAlert();

return;

} } }

Nun ist es besser, den Namen des Originals zu ọndern:

void sendAlert (String[] people){

for (int i = 0; i < people.length; i++) { if (people[i].equals ("Don")){

sendAlert();

return;

}

if (people[i].equals ("John")){

sendAlert();

return;

} } }

Natürlich haben wir in diesem Fall eine Menge Coderedundanz, da die Änderung den Rumpf der Abfrage verwendet, um ihre Arbeit zu erledigen. Ich kann nun aber Algorithmus ersetzen (136) auf die Änderung anwenden, um daraus einen Vor- teil zu ziehen:

void sendAlert(String[] people){

if (! foundPerson(people).equals("")) sendAlert();

}

10.4.4 Nebenlọufigkeitsfragen

Wenn Sie in einem nebenlọufigen System mit mehreren Threads arbeiten, so wis- sen Sie, dass es ein wichtiges Idiom ist, Test- und set-Methoden als eine einzige Aktion auszuführen. Widerspricht dies Abfrage von Änderung trennen (285)? Ich habe das Thema mit Doug Lea diskutiert, und daraus hat sich ergeben, dass sie sich nicht widersprechen, Sie aber einige zusọtzliche Dinge tun mỹssen. Es ist wei- terhin nützlich, getrennte Methoden für Abfragen und Änderungen zu haben. Sie müssen aber auch eine dritte Methode behalten, die beides macht. Die Abfrage- und-Änderungsmethode ruft die getrennten Abfrage- und Änderungsmethoden auf und ist synchronisiert. Sind die Abfrage und die Änderungsmethode nicht synchronisert, so kửnnen Sie ihre Sichtbarkeit auf Paket- oder Klassenebene ein- schrọnken. Auf diese Weise haben Sie eine sichere synchronisierte Methode in zwei einfach zu verstehende Methoden zerlegt. Die elementaren Methoden ste- hen nun für andere Verwendungen zu Verfügung.

Một phần của tài liệu AW refactoring improving the design of existing code (Trang 309 - 313)

Tải bản đầy đủ (PDF)

(468 trang)