Refactoring: Code optimieren - Teil 2

15.04.2006 von André Minhorst
In Zusammenhang mit der Optimierung von Code ist besonders in der objektorientierten Welt oft von Refactoring die Rede. Dabei handelt es sich um eine Technik, bei der man bestehenden und vor allem funktionierenden Code Änderungen unterzieht, um ihn in puncto Verständlichkeit und Änderbarkeit zu optimieren, ohne sein Verhalten zu beeinflussen.

Die wichtigsten beiden Voraussetzungen für Refactoring sind:

Letzteres impliziert, dass Sie nur lauffähigen Code einem Refactoring unterziehen – andernfalls haben Sie ja nicht die Möglichkeit, vor und nach dem Refactoring zu prüfen, ob der Code wie gewünscht funktioniert.

In den folgenden Abschnitten lernen Sie einige häufig vorkommende Refactorings kennen, das bedeutet, dass es eine bestimmte Konstellation im Quellcode gibt, die man mit einer festgelegten Vorgehensweise in eine meist günstigere Variante umwandeln kann.

Zeit für Refactoring

Wann sollte man seinen Code einem Refactoring unterziehen? Diese Frage ist nicht eindeutig zu beantworten. Kent Beck und Martin Fowler haben in Martin Fowlers Buch „Refactoring“ (ISBN 3-8273-2278-2) über „Gerüche“ geschrieben, die der Code annehmen kann – und der ein Refactoring dringend erforderlich macht. Zu solchen „Gerüchen“ gehören beispielsweise folgende Besonderheiten im Code (viele dieser Gerüche beziehen sich speziell auf die objektorientierte Programmierung und ist auf die prozedurale Programmierung unter VBA angepasst):

Refactoring-Maßnahmen im Detail

Dies sind nur einige Beispiele für Code-Geruch. In den folgenden Abschnitten erfahren Sie, welche Refactoring-Maßnahmen es gibt und wie man sie unter VBA einsetzen kann.

Die Refactoring-Maßnahmen sind weit verbreitet; einen umfangreichen Katalog solcher Maßnahmen können Sie beispielsweise unter www.refactoring.com/catalog/index.html lesen. Bei den nachfolgend vorgestellten Refactoring- Maßnahmen handelt es sich um solche, die sich für den Einsatz in VBA gut eignen. Da die meisten Entwickler in VBA prozedural entwickeln, statt die objektorientierten Eigenschaften dieser Programmiersprache zu nutzen, sind die folgenden Refactoring-Maßnahmen allgemein einsetzbar und setzen nicht das Vorhandensein von Klassen voraus. Die Refactoring-Maßnahmen sind aus dem bereits oben genannten Buch Refactoring von Martin Fowler abgeleitet.

Routine extrahieren

Diese Refactoring-Maßnahme bezieht sich direkt auf zwei der weiter oben genannten „Code-Gerüche“ und kann beispielsweise eingesetzt werden, wenn ein Codestück mehr als einmal vorhanden ist oder eine Routine zu lang ist.

Ein Minimalbeispiel extrahiert eine einzigeAnweisung aus einer Routine und bringt es in einer neuen Routine unter. Nehmen wir an, Sie verwenden eine komplizierte DLookup-Anweisung Damit man diese nicht erst sezieren muss, um zu verstehen, was sie tut, erzeugen Sie einfach eine neue Routine – in dem Fall eine Funktion mit einer einzigen Anweisung. Geben Sie der Funktion einen Namen, die genau beschreibt, was die komplizierte DLookup-Anweisung tut.

Diese Refactoring-Maßnahme ist außerdem dann erforderlich, wenn Sie den gleichen Code an mehreren Stellen verwenden. Wenn Sie diesen Code in eine eigene Routine extrahieren, können Sie Änderungen an dem Code schnell an einer einzigen Stelle durchführen, anstatt jedes Auftreten dieses Codes zu berücksichtigen.

Verzeichnis der aktuellen Access-Anwendung ermitteln

Beispiel: Sie verwenden den folgenden Ausdruck an verschiedenen Stellen in der Anwendung, um das Verzeichnis der aktuellen Access- Anwendung zu ermitteln (in einer Zeile):

strAnwendungspfad = Left(CurrentDb.Name,
Len(CurrentDb.Name)-Len(Dir(CurrentDb.Name)))

Dann sollten Sie etwa folgende Funktion in einem Standardmodul erstellen, das öffentliche Funktionen beherbergt:

Public Function Anwendungspfad() As String
Anwendungspfad = Left(CurrentDb.Name,
Len(CurrentDb.Name) - Len(Dir(CurrentDb.Name)))
End Function

Den zuvor für das Ermitteln des Anwendungsverzeichnisses verwendeten Ausdruck ersetzen Sie wie folgt:

strAnwendungspfad = Anwendungspfad

Natürlich sollten Sie alle Stellen, an denen dieser Aufruf verwendet wird, vor und nach dem Refactoring testen.

Etwas komplizierter wird es, wenn die Routine lokale Variable verwendet. Diese müssen Siedann natürlich als Parameter übergeben. Angenommen, Sie möchten die Berechnung in folgendem Code in eine neue Routine extrahieren. Der Kommentar deutet bereits darauf hin, dass man den Sinn der Berechnung nicht auf einen Blick erfassen kann:

Dim intWert1 As Integer
Dim intWert2 As Integer
Dim sngErgebnis As Single
'...
'Komplizierte Berechnung
sngErgebnis = (intWert1 - intWert2) / (intWert1 +
intWert2) * 100
'...

Dann sieht die neue Funktion so aus:

Public Function KomplizierteBerechnung(intWert1 As
Integer, intWert2 As Integer) As Single
KomplizierteBerechnung = (intWert1 - intWert2) /
intWert2 * 100
End Function

Der neue Aufruf übergibt die beiden Parameter an die Funktion:

sngErgebnis = KomplizierteBerechnung(intWert1,
intWert2)

Auch hier erkennen Sie einen der weiter oben erwähnten „Code-Gerüche“ wieder: Der Sinn der Berechnung ist nur durch eine entsprechende Kommentarzeile ersichtlich. Dementsprechend kann der Name der neuen Funktion leicht aus der Kommentarzeile abgeleitet werden. „Komplizierte Berechnung“ steht dabei stellvertretend für alle Funktionen, deren Zweck sich nicht auf den ersten Blick erschließt.

Routine integrieren

Manchmal enthält eine Routine so wenig Code und ist so selbsterklärend, dass eigentlich keine Methode dafür erforderlich ist. Dabei darf diese Methode natürlich nicht von mehreren Stellen aus referenziert werden, sonst würde eine Integration der Routine in den Code dazu führen, dass Änderungen an selbiger an mehreren Stellen gleichzeitig durchzuführen wären. Wenn Sie beispielsweise irgendwo im Code eine Funktion finden, die etwa Multiplizieren heißt und zwei per Parameter übergebene Werte multipliziert, sollten Sie eine Integration der enthaltenen Funktionalität in die aufrufende(n) Routinen in Erwägung ziehen.

Dabei ersetzen Sie in der aufrufenden Routine den Aufruf durch den Inhalt der aufgerufenen Funktion. Wenn Tests zeigen, dass die aufrufende Funktion wie gewohnt funktioniert, können Sie die aufgerufene Funktion entfernen.

Im folgenden Codeausschnitt wird eine solche Funktion aufgerufen, um eine denkbar einfache Berechnung durchzuführen:

Dim intWert1 As Integer
Dim intWert2 As Integer
Dim intErgebnis As Integer
'....
intErgebnis = Multiplizieren(intWert1, intWert2)
'....

Die aufgerufene Funktion sieht so aus:

Public Function Multiplizieren(intWert1 As Integer,
intWert2 As Integer) As Integer
Multiplizieren = intWert1 * intWert2
End Function

Nach der Integration des in der aufgerufenen Funktion enthaltenen Algorithmus müssen Sie nur noch sicherstellen, dass die Namen der übergebenen Variablen mit den Namen der Parameter der aufgerufenen Funktion übereinstimmen. Ist das nicht der Fall, müssen Sie für die ehemaligen Parameter die im Aufruf verwendeten Variablennamen einsetzen.

Die folgende Zeile ersetzt nun den Aufruf der Funktion:

'....
intErgebnis = intWert1 * intWert2
'....

Algorithmus ersetzen

Manchmal stellt man fest, dass man mehrere Zeilen, die eine bestimmte Aufgabe erfüllen, komplett durch eine andere Variante ersetzen kann, die wesentlich verständlicher, schneller oder kompatibler ist als die bestehende.

In diesem Fall ersetzt man einfach den vorhandenen Algorithmus durch einen neuen. Wenn Sie die Routine, die den Algorithmus enthält, durch Tests abgesichert haben, kann ja nichts passieren!

In einem der vorherigen Beispiele wird der Anwendungspfad mittels folgender Anweisung ermittelt:

Left(CurrentDb.Name, Len(CurrentDb.Name) -
Len(Dir(CurrentDb.Name)))

Wenn Sie für Access 2000 und höher entwickeln, ist die folgende Variante allerdings deutlich schneller und auch besser lesbar:

CurrentProject.Path & "\"

Also ersetzen Sie einfach den ersten „Algorithmus“ durch den zweiten, so dass die Funktion wie folgt aussieht:

Public Function Anwendungspfad() As String
Anwendungspfad = CurrentProject.Path & "\"
End Function

Hier wird direkt der Vorteil offensichtlich, den Sie sich durch das Extrahieren einer Methode erarbeiten: Sie müssen Änderungen nur noch an einer Stelle durchführen. Gleichzeitig müssen Sieauch nur diese Routine testen: Da der Aufruf gleich bleibt, muss nur das Ergebnis gleich bleiben, damit auch alle Codeteile, die diese Routine aufrufen, weiter wie gewohnt funktionieren.

Temporäre Variable integrieren

Werte in Variablen zu speichern ist grundsätzlich keine schlechte Idee. Wenn eine Variable jedoch nur einmal mit einem Wert belegt und auch nur einmal verwendet wird, können Sie die Variable auch entfernen und direkt mit dem der Variablen zugewiesenen Wert arbeiten.

Die Variable intAnzahl in folgendem Codefragment ist ein Beispiel für eine solche wenig genutzte temporäre Variable. IrgendeinWert könnte beispielsweise ein Ausdruck wie Forms!f rmBeispielformular!txtAnzahl sein:

Dim i As Integer
Dim intAnzahl As Integer
intAnzahl = 12
For i = 1 To intAnzahl
'Do Something
Next i

Wenn intAnzahl nirgendwo anders verwendet wird, können Sie diese Variable auch entfernen und den Wert direkt ermitteln. Das Codefragment sieht dann wie folgt aus:

Dim i As Integer
For i = 1 To 12
'Do Something
Next i

Sie sparen zwei Zeilen, und die Übersichtlichkeit des Codes leidet auch nicht.

Wie geht es weiter?

Im zweiten Teil haben Sie erfahren, was Refactoring ist und welche Voraussetzungen dafür notwendig sind. Unter Zuhilfenahme des im Beitrag „Testgetriebene Entwicklung“ in dieser Ausgabe vorgestellten Unit-Testing-Framework können Sie nun Ihren Code einem Refactoring unterziehen und gleichzeitig die Funktionalität sicherstellen.

Im dritten und letzten Teil lernen Sie weitere Möglichkeiten zum Optimieren Ihres Codes kennen.