Refactoring

André Minhorst, Duisburg

Refactoring ist eines der Schlagwörter des Extreme Programming und untrennbar mit dem Unit-Testing und der testgetriebenen Entwicklung verbunden. Durch Refactoring verbessern Sie die Struktur und Lesbarkeit des Codes einer Anwendung unter Beibehaltung der Funktionalität und erleichtern damit änderungen und Erweiterungen der Funktion der Anwendung.

Refactoring ist eine Tätigkeit, die Chefs und Managern vermutlich nicht gefallen wird, weil sie erstmal keinen Gewinn bringt und ein Projekt nicht sichtbar vorantreibt – immerhin bezeichnet man Refactoring ausdrücklich als Vorgehensweise, die sich nicht auf die Funktion einer Anwendung auswirkt. Genau genommen kann man es als Aufräumen bezeichnen – man sortiert hier ein wenig aus, stellt dort ein wenig um und wirft vielleicht sogar ein paar Dinge weg. Die Analogie zum wirklichen Leben hinkt nur insofern, als dass Sie nicht Platz für etwas Neues, sondern die Voraussetzungen für dessen einfache Integration schaffen.

Refactoring optimiert noch nicht einmal die Performance – im Gegenteil: Viele Refactoring-Maßnahmen führen zu einer Verlangsamung der betroffenen Teile um wenige Prozentpunkte.

Aber – und das ist das Wichtigste – Sie bereiten den Quellcode durch Refactoring auf die Anwendung von Performance-Optimierungen vor, weil Sie diesen wesentlich besser lesbar machen und damit das Erkennen und Beseitigen von Performance-Bremsen erleichtern.

Die wichtigsten Ziele des Refactoring sind folgende:

  • Der Code soll für Menschen besser lesbar gemacht werden.
  • Die Struktur (Architektur) des Codes soll verbessert werden.
  • Der Code soll keine redundanten Abschnitte enthalten.
  • Der Code soll von Fehlern befreit und das Auffinden von Fehlern erleichtert werden.
  • Die Anwendung soll robuster werden.
  • Refactoring besteht aus einem oder mehreren Refactoring-Maßnahmen – je nachdem, wie gute Vorarbeit man geleistet hat und wie viel Verbesserungspozential der Code noch bietet. Mit diesen Maßnahmen können Sie den Code Stück für Stück besser strukturieren und lesbarer machen.

    Refactoring ist in Zusammenhang mit objektorientierten Programmiersprachen entstanden. Da VBA nicht alle Aspekte der Objektorientiertheit beinhaltet, lassen sich längst nicht alle in Literatur und Internet beschriebenen Refactoring-Maßnahmen auf VBA-Code anwenden.

    In den nachfolgenden Kapiteln lernen Sie ausschließlich solche Refactorings kennen, die für mit VBA entwickelte Anwendungen geeignet sind. Zu jedem Refactoring finden Sie ein Beispiel und – soweit sinnvoll – eine Abbildung zur Veranschaulichung der Vorgehensweise.

    In manchen Fällen stellen wir auch Refactorings vor, die in der Literatur nicht beschrieben sind – beispielsweise die Umwandlung einer Funktion mit mehreren Rückgabeparametern in eine Klasse mit entsprechenden Eigenschaften.

    Unbewusst werden Sie vielleicht schon das eine oder andere Refactoring an Ihrem Code vorgenommen haben, weil es Ihnen einfach sinnvoll erschien. Ein gutes Beispiel dafür ist das Extrahieren einer Methode, bei dem man eine oder mehrere Zeilen Code, die an mehreren Stellen in einer oder mehreren Prozeduren vorkommen, in eine neue Prozedur ausgliedert.

    Hinweis

    Im Internet gibt es eine Menge Informationen zum Thema Refactoring, meist in Verbindung mit objektorientierten Sprachen wie Java oder VB.NET. Wenn Sie im Internet nach zusätzlichem Informationsmaterial suchen, sollten Sie als Suchbegriff zusätzlich noch den Namen Martin Fowler eingeben. Die in diesem Beitrag und den geplanten Fortsetzungen vorgestellten Refactoring-Maßnahmen lehnen sich an die auf der folgenden Internetseite aufgeführten Informationen an und sind den speziellen Gegebenheiten unter Microsoft Access beziehungsweise VBA angepasst: http://www.refactoring.com/catalog/index.html

    Refactoring sollte unter zwei Voraussetzungen erfolgen:

  • Der zu refaktorierende Code ist – soweit wie unter VBA möglich – objektorientiert.
  • Es gibt automatisierte Tests für den zu ändernden Code, mit dem man den Code vor und nach einer Refactoring-Maßnahme testen kann.
  • Objektorientierung

    Die erste Voraussetzung ist nirgends festgelegt, weil Refactoring ohnehin aus der objektorientierten Programmierung stammt und dort zusammenhängende Methoden und Eigenschaften in Klassenmodulen untergebracht sind. Standardmodule wie in VBA werden dort nicht verwendet. Viele Refactoring-Maßnahmen beziehen sich ausdrücklich auf Klassen. Es gibt zwar einige, die auf Eigenschafts- oder Methodenebene anwendbar und damit auch für VBA-Prozeduren in Standardmodulen geeignet sind, aber die Kapselung von Funktionalität in Klassen kommt dem Refactoring-Prozess in den meisten Fällen entgegen.

    Tests

    Die zweite Voraussetzung ist die Absicherung der vom Refactoring betroffenen Funktionalität. Und hier kommen die weiter oben erwähnten Unit-Tests und die testgetriebene Entwicklung ins Spiel: Wenn Sie die Anforderungen an die Eigenschaften und Methoden durch das vorherige Schreiben von Tests festlegen und auch überprüfen, können Sie auch ohne Sorge änderungen in Form von Refactoring-Maßnahmen durchführen. Dazu stellen Sie einfach sicher, dass die Anwendung vor dem Refactoring funktioniert, führen die änderungen durch und testen anschließend, ob weiterhin alles reibungslos läuft. Dazu können Sie die Tests verwenden, mit denen Sie auch die entsprechenden Methoden entwickelt haben – der einzige Unterschied ist, dass die Tests in diesem Zusammenhang “Regressionstests” genannt werden.

    Testen Sie so oft wie möglich – je kleiner die änderungen zwischen zwei Tests sind, umso schneller finden Sie eventuell auftretende Fehler.

    Hinweis

    Weitere Informationen über die testgetriebene Entwicklung finden Sie im Beitrag Testgetriebene Entwicklung mit Access in Ausgabe 4/2004 von Access im Unternehmen.

    In den folgenden Abschnitten geht es los: Sie lernen die ersten Refactoring-Maßnahmen kennen. In diesem Beitrag stellen wir Ihnen gängige Maßnahmen für das Refactoring von Variablen und Methoden vor.

    Variablen bieten gute Ansatzpunkte für Refactoring. Nachfolgend finden Sie einige Refactoring-Maßnahmen, die je nach Situation Variablen hinzufügen, entfernen oder einfach ändern.

    Public Sub EinsBisZehn()
        Dim i As Integer
        For i = 1 To 10
            Debug.Print i
        Next i
    End Sub

    Quellcode 1

    Private Sub KombinationsfeldFuellen(intErstesJahr As Integer, intLetztesJahr As Integer)
        Dim i As Integer
        Dim strJahre As String
        For i = intErstesJahr To intLetztesJahr
            strJahre = strJahre & i & ";"
        Next i
        strJahre = Mid(strJahre, 1, Len(strJahre) - 1)
        Me.cboJahreszahlen.RowSource = strJahre
    End Sub

    Quellcode 2

    Public Sub Rechteck(a As Integer, b As Integer)
        Dim temp As Integer
        temp = a * b
        Debug.Print "Fläche: " & temp
        temp = 2 * (a + b)
        Debug.Print "Umfang: " & temp
    End Sub

    Quellcode 3

    Hinweis

    In den Schritt-für-Schritt-Anweisungen der einzelnen Refactoring-Maßnahmen finden Sie oft den Hinweis, die Anwendung zu kompilieren. Damit sollen meist bei der änderung von Variablen vergessene Vorkommen der jeweiligen Variablen gefunden werden. Damit der Debugger sich meldet, wenn eine nicht deklarierte Variable im Quellcode enthalten ist, müssen Sie zu Beginn des Moduls die Zeile Option Explicit einfügen. Es dürfen dann keine Variablen mehr ohne Deklaration verwendet werden.

    Temporäre Variable zerlegen

    Es gibt mehrere Möglichkeiten, temporäre Variablen mehrfach zu verwenden. Die erste Möglichkeit ist die Verwendung in einer Schleife, was vollkommen legitim und außerdem unausweichlich ist, wie das Beispiel aus Quellcode 1 zeigt.

    Die zweite Möglichkeit ist gegeben, wenn der Inhalt der Variablen sukzessiv erweitert wird, etwa um den Inhalt eines Kombinationsfeldes zu erzeugen (im Fall des Beispiels aus Quellcode 2 handelt es sich um Jahreszahlen).

    Die dritte Möglichkeit besteht in der Verwendung einer Variablen für verschiedene Zwecke, wie in dem Beispiel aus Quellcode 3.

    Die Methode speichert nacheinander zwei völlig unterschiedliche Informationen in einer Variablen.

    Im vorliegenden Fall ist das noch überschaubar, aber wenn Sie sich noch 30 Zeilen Code zwischen den beiden Verwendungen der Variablen temp vorstellen, nimmt die übersichtlichkeit rasch ab.

    Beschreibung der Vorgehensweise

    Verwenden Sie jede Variable nur einmal und vergeben Sie außerdem sprechende Namen für jede Variable. Damit erhöhen Sie die übersichtlichkeit und können außerdem später auf alle temporären Variablen zugreifen, ohne deren Werte erneut ermitteln zu müssen. Gehen Sie folgendermaßen vor:

  • Benennen Sie die temporäre Variable in der Deklaration nach dem Zweck des ersten Vorkommens.
  • ändern Sie den Variablennamen an allen Stellen im Code, die sich auf die erste temporäre Variable beziehen.
  • Public Function AnzahlPersonen()
        AnzahlPersonen = DCount("[Kunden-Code]", "Kunden") _        + DCount("[Personal-Nr]", "Personal")
    End Function

    Quellcode 4

    Public Function AnzahlPersonen()
        Dim intAnzahlKunden As Integer
        Dim intAnzahlMitarbeiter As Integer
        intAnzahlKunden = DCount("[Kunden-Code]", "Kunden")
        intAnzahlMitarbeiter = DCount("[Personal-Nr]", "Personal")
        AnzahlPersonen = intAnzahlKunden + intAnzahlMitarbeiter
    End Function

    Quellcode 5

  • Deklarieren Sie eine neue Variable mit einem Namen, der sich nach dem Zweck des zweiten Auftretens dieser Variablen richtet.
  • Passen Sie den Namen der Variablen im Code an.
  • Verfahren Sie genauso für alle weiteren Vorkommen.
  • Kompilieren Sie um sicherzugehen, dass Sie die ursprüngliche Variable an allen Stellen im Code ersetzt haben. (
  • Die Beispielprozedur aus Quellcode 3 sieht nach dieser Behandlung wie folgt aus (änderungen fett gedruckt):

    Public Sub Rechteck(a As Integer, b As Integer)
        Dim intFlaeche As Integer
        Dim intUmfang As Integer
        intFlaeche = a * b
        Debug.Print "Fläche: " & intFlaeche
        intUmfang = 2 * (a + b)
        Debug.Print "Umfang: " & intUmfang
    End Sub 

    Erklärende Variable einführen

    Man verwendet oft komplizierte und aus mehreren Teilen bestehende Ausdrücke in Methoden, deren Ergebnis nicht unbedingt offensichtlich ist.

    Auf eine Domänenfunktion müssen Sie schon einen genaueren Blick werfen, um genau zu erkennen, welche Information sie ermittelt, um welche Domänenfunktion es sich handelt (DLookup, DMax, DCount, …), auf welche Datenherkunft sie sich bezieht, welches Feld die Funktion betrifft und wie ein eventuell vorhandenes Kriterium die Datenherkunft einschränkt. Folgende Funktion soll beispielsweise die Gesamtanzahl der Mitarbeiter und Kunden einer Firma ermitteln – zum Beispiel, um die richtige Menge Weihnachtskarten zu beschaffen (s. Quellcode 4).

    Zugegeben: Die Funktion lässt sich noch relativ schnell erfassen. Folgende Variante ist aber wesentlich freundlicher. Sie sehen, dass dort mit irgendeiner Funktion die Anzahl der Kunden und mit einer weiteren Funktion die Anzahl der Mitarbeiter ermittelt wird und dass diese beiden Werte addiert werden (s. Quellcode 5).

    Beschreibung der Vorgehensweise

    Diese Refactoring-Maßnahme enthält folgende Schritte:

  • Deklarieren Sie eine temporäre Variable mit einem aussagekräftigen Namen für jede Teilfunktion, für die es lohnenswert erscheint.
  • Weisen Sie den temporären Variablen den entsprechenden Teilausdruck zu.
  • Public Function AnzahlPersonen()
        Dim intAnzahlKunden As Integer
        Dim intAnzahlMitarbeiter As Integer
        intAnzahlKunden = AnzahlKunden
        intAnzahlMitarbeiter = DCount("[Personal-Nr]", "Personal")
        AnzahlPersonen = intAnzahlKunden + intAnzahlMitarbeiter
    End Function
    Private Function AnzahlKunden() As Integer
        AnzahlKunden = DCount("[Kunden-Code]", "Kunden")
    End Function

    Quellcode 6

    Public Function AnzahlPersonen()
        Dim intAnzahlMitarbeiter As Integer
        intAnzahlMitarbeiter = DCount("[Personal-Nr]", "Personal")
        AnzahlPersonen = AnzahlKunden + intAnzahlMitarbeiter
    End Function

    Quellcode 7

    Private Function AnzahlMitarbeiter() As Integer
        AnzahlMitarbeiter = DCount("[Personal-Nr]", "Personal")
    End Function

    Quellcode 8

    Public Function AnzahlPersonen()
        AnzahlPersonen = AnzahlKunden + AnzahlMitarbeiter
    End Function

    Quellcode 9

  • Setzen Sie den ursprünglichen Ausdruck aus den Teilausdrücken zusammen. (
  • Temporäre Variable durch Funktion ersetzen

    Diese Refactoring-Maßnahme heißt eigentlich Temporäre Variable durch Abfrage ersetzen. Mit Abfrage ist hier allerdings keine SQL-Abfrage, sondern das Pendant anderer Programmiersprachen zu einer VBA-Funktion gemeint.

    Als Beispiel greifen wir die Funktion auf, die durch die Refactoring-Maßnahme Erklärende Variable einführen entstanden ist (s. Quellcode 5). Voraussetzung ist weiterhin, dass die Methode der temporären Variablen nur einmal einen Wert zuweist. Ist das gegeben, gehen Sie beispielsweise folgendermaßen vor:

    Erstellen Sie eine private Funktion für die Ermittlung der Anzahl der Kunden und ersetzen Sie die DCount-Anweisung in der ursprünglichen Funktion durch die neue Funktion (s. Quellcode 6).

    Setzen Sie die Funktion AnzahlKunden überall ein, wo dieser Wert benötigt wird – außer bei der temporären Variablen intAnzahlKunden. Diese entfernen Sie ebenso wie die entsprechende Zuweisung (s. Quellcode 7).

    Erstellen Sie eine entsprechende private Funktion für die Variable intAnzahlMitarbeiter wie in Quellcode 8 und verwenden Sie den Aufruf dieser Funktion statt der Variablen wie in Quellcode 9.

    Damit hat die ursprüngliche Funktion deutlich an Gewicht verloren und ist wesentlich überschaubarer geworden. Zusätzlich haben Sie zwei Funktionen zur Ermitlung der Anzahl der Mitarbeiter und Kunden gewonnen, die Sie auch an anderer Stelle in dieser Klasse einsetzen können.

    Public Sub SpielereiMitNamen(strName As String)
        Dim strVorname As String
        Dim strNachname As String
        Call NameAufteilen(strName, strVorname, strNachname)
        Debug.Print "Vorname: " & strVorname
        Debug.Print "Nachname: " & strNachname
    End Sub
    Public Sub NameAufteilen(strName As String, _    strVorname As String, strNachname As String)
        Dim intPos As Integer
        intPos = InStr(1, strName, " ")
        strVorname = Mid(strName, 1, intPos)
        strNachname = Mid(strName, intPos + 1)
    End Sub

    Quellcode 10

    Public Sub SpielereiMitNamen_OO(strName As String)
        Dim objPerson As clsPerson
        Set objPerson = New clsPerson
        objPerson.NameAufteilen strName
        Debug.Print "Vorname: " & objPerson.Vorname
        Debug.Print "Nachname: " & objPerson.Nachname
        Set objPerson = Nothing
    End Sub

    Quellcode 11

    Beschreibung der Vorgehensweise

    Mit den nachfolgenden Schritten ersetzen Sie eine Variable durch eine Funktion:

    Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

    den kompletten Artikel im PDF-Format mit Beispieldatenbank

    diesen und alle anderen Artikel mit dem Jahresabo

    Schreibe einen Kommentar