09.11.2009
16:02

Refactoring Snippet: Closure

Sebastian Petzelberger Senior Consultant bei aformatik

von Sebastian Petzelberger

Ein Refactoring Snippet zeigt ein kleines Problem auf und gibt eine kleine Lösung dazu an. Die Problemstellungen kommen aus der Projektarbeit. Die Snippets sollen kleine Hilfen sein. Außerdem sollen sie generell zum Refactoring anregen, aber auch zu einer Diskussion darüber einladen.

 

 

 

Refactoring Snippet: Closure

Vorstellung

Folgende Methodeninhalte kommen in dem Client-Code vor.

Ganz allgemein formuliert haben wir an einer Stelle:

	a();
x1();
b();

an einer anderen:

	a();
x2();
b();

usw. mit weiteren xn.

 

kleine Probleme

Redundanz: Das Problem ist hier bereits minimiert aufgeführt, weil die wiederkehrenden Aufrufe in den Methode a() und b() zusammengefasst sind. Ein Rest an Redundanz liegt noch vor.

Abhängigkeit: Hier nach dem Motto: "Wer a sagt, der muss auch b sagen!". Das halte ich für noch gewichtiger als die Redundanz. Obwohl oft entgegengehalten wird, dass doch jeder weiß, dass auf a() auch b() folgen muss. Aber genau das sollte der Code widerspiegeln. Es soll kein externes Wissen geben, sondern der Code soll exakt das sprechen, was eben zu sprechen ist. So verstehe ich gute Programmier-Sprache.

Fehleranfälligkeit: Eine Java-Methode darf - entgegen älterer Konventionen - mehrere Ausgänge haben. Diese werden bekannterweise mit return realisiert. Nun kann es passieren, dass bei einem Ausgang der Methode das b() nicht mehr erfolgt. Wenn eine Methode kurz und übersichtlich ist, wie sie sein sollte, kann dies noch recht einfach vermieden werden. In der Praxis können Methoden jedoch durchaus komplexer und unübersichtlicher werden und somit durchaus die Ausführung von b() mal unter den Tisch fallen.

Praktisch relevanter formuliert sieht das Problem wie folgt aus:

	open();
x1();
close();

kleine Lösung

Zuerst einmal wird ein Interface benötigt:

public interface Executor 
{
public void execute();
}

 

Dann wird eine Utility-Methode erstellt. Sie hat eine Instanz des Interfaces als Parameter und sorgt für die a/b Klammer, hier jetzt open/close.

public static void handle(Executor executor) 
{
open();
executor.execute();
close();
}

 

Jetzt können die Stellen im Client-Code verbessert werden, durch:

Executor executor =
new Executor()
{
public void execute() {x1();}
};

Utils.handle(executor);

 

Beurteilung

Vorteile: Auflösung der Redundanz. Keine Abhängigkeiten mehr. Im Client-Code wird nicht mehr geöffnet und geschlossen, und dadurch die Fehleranfälligkeit minimiert.

Nachteile: So klein ist die Lösung dann doch nicht. Der Code ist schwieriger zu verstehen.

Mein Urteil: Vielleicht gibt es mal in einer späteren Java-Version für die Umsetzung eine kürzere Notation. Es wird zwar viel beschrieben, aber inhaltlich eben eingespart. Ich bin für eine Einführung in ein Projekt, vor allem dann, wenn Probleme mit fehlendem close auftauchen. Da darf dann auch das Argument mit dem schwierigerem Code nicht ziehen, letztlich gehört die hier beschriebene Lösung zum Java-Standard. Es muss ja nicht jeder Mitarbeiter eine derartige Lösung selber entwickeln. Es muss nur die vorgefertigte Lösung anhand eines Beispiels nachvollzogen werden. Ich bin ein Pragmatiker und würde diese Lösung nicht für ein Projekt vorschlagen, wenn dies nur wenige Stellen betreffen würde.

praktisches Beispiel

Hier wird die Verbindung zu einer Datei verborgen. Auch möglich wäre eine derartige Einführung für die Verbindung zur Datenbank. Das Executor-Interface ist wie folgt deklariert:

import java.io.IOException;
import java.io.PrintWriter;

public interface WriteExecutor
{
public void execute(PrintWriter out) throws IOException;
}

Die Utility-Methode ist hier in einer Util-Klasse implementiert:

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public abstract class IOUtilsExample
{
public static void write(String filename,
WriteExecutor executor)
throws IOException
{
PrintWriter out =
new PrintWriter(new FileWriter(filename));
executor.execute(out);
out.close();
}
}

 

Jetzt der Client-Code:

WriteExecutor executor =
new WriteExecutor()
{
public void execute(PrintWriter out)
{
// jetzt kann mit out gearbeitet werden
}
};

IOUtilsExample.write("example.txt", executor);

 

Das Beispiel selbst ist natürlich noch nicht perfekt. Zum Beispiel kann hier eine Exception beim Schreiben dazu führen, dass das close() nicht aufgerufen wird. Aber der Nutzen des Closure Refactoring Snippet kommt dennoch sehr gut heraus.