Besseres Code-Layout

Wenn Sie ein Abonnement des Magazins 'Access im Unternehmen' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.

Die Lesbarkeit von Programmcode beschleunigt sowohl die Entwicklung, wie auch die Wartung Ihrer Anwendungen. Wenn Sie ein Freund der VBA-Programmierung sind, so überprüfen Sie Ihre Routinen doch einmal auf Struktur und Gestalt. Gibt es hier Verbesserungspotential?

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1507_CodeLayout.accdb

Strukturierung

Umso logischer Ihr Code aufgebaut ist, desto mehr Freude werden Sie später an ihm haben. Später kann dabei bedeuten, dass Sie schon am nächsten Tag möglichst schnell wieder in die Routinen hineinfinden und nicht unnötig Zeit damit verbringen müssen, die Funktionsabfolgen mühsam nachzuvollziehen. Erst recht gilt dies, wenn Sie sich zur Wartung oder Weiterentwicklung nach einem Jahr wieder an eine Datenbank machen müssen. Früher galt einmal das Credo, den Code ausführlich zu kommentieren, um später so einen Leitfaden vorzufinden, doch was nützt eine Kommentierung oder Dokumentation, wenn die Struktur unzureichend ist?

Modulstrukturen

Sie finden Funktionen leichter auf, wenn Sie sie auf mehrere Module aufteilen, statt alles in ein Modul zu packen. Sie haben eine Sammlung von immer wieder gebrauchten Routinen, etwa Dateifunktionen, Datumsberechnungen, String-Operationen und solche für den Datenzugriff? Leicht können dies Hunderte werden, auf die der Zugriff, falls in nur in einem Modul gespeichert, umständlich wird. Die Navigation ist behindert und ausdauerndes Scrollen wird notwendig. Der Überblick geht verloren.

Da ist es günstiger, wenn Sie Routinen mit ähnlicher Funktionalität in thematisch passenden Modulen vereinen. Legen Sie ein Modul für die Dateioperationen an, eines für die String-Bearbeitung und eines für für den Datenzugriff. Damit Sie dann wissen, um was es sich handelt, sind sprechende Namen für die Module angeraten. Eine Bezeichnung, wie mdlFunktionen, sagt wenig über den Inhalt aus. Besser fahren Sie da etwa mit mdlFileAccess, mdlDataAccessDAO, mdlStringHandling.

Ähnlich, wie bei Variablen- oder Steuerelementnamen hat sich die Verwendung von Präfixen eingebürgert. Für normale Module kommt meist das Präfix mdl oder mod zum Einsatz, für Klassenmodule ein cls oder schlicht ein großes C. Englische Bezeichnungen oder deutsche? Das bleibt Ihnen überlassen. Da jedoch davon auszugehen ist, dass die meisten Programmierer mit englischen Fachbegriffen vertraut sind, liegt man mit englischen Bezeichnungen auf der sicheren Seite. So ist sichergestellt, dass es nicht zu Konfusionen kommt, wenn die Datenbankanwendung oder einzelne Module einmal die Ländergrenzen überschreiten sollten.

Die Frage, wie kleinteilig man Routinen auf Module verstreut, ist schwer zu beantworten. Natürlich könnten Sie Ihre Module so gestalten, dass der Code immer komplett auf eine Bildschirmseite passt. Das aber würde zu überhäufigem Umschalten zwischen den Modulen führen – schließlich werden die Prozeduren ja wahrscheinlich von anderen aufgerufen. Je umfangreicher andererseits ein Modul ausfällt, desto mehr Scrollen ist angesagt. Finden Sie hier also einen Mittelweg.

Prozedurstrukturen

Innerhalb eines Moduls macht eine logische Anordnung der Prozeduren ebenfalls Sinn. Was hier logisch ist, oder nicht, hängt von der Bedeutung der Routinen ab. Nehmen Sie etwa ein Formularmodul. Sinnvoll wäre hier eine Anordnung in der chronologischen Abfolge von Ereignissen. Form_Open (Beim Öffnen) ist das erste Ereignis, das ein Formular auslöst. Gefolgt wird es von Form_Load (Beim Laden) und schließlich Form_Current (Beim Anzeigen). Das Schließen des Formulars löst Form_Close (Beim Schließen) und Form_Unload (Beim Entladen) aus. Also würde eine Anordnung solcher Ereignisprozeduren von oben nach unten später nützlich sein. Ähnliches gilt für die Steuerelementereignisse, die sich an die Formularereignisse anschließen könnten. Hilfsfunktionen, die von diesen Ereignisprozeduren aufgerufen werden, dürfen getrost ganz unten im Modul ihren Raum finden.

Auch bei normalen Modulen ist eine Anordnung der Routinen von bedeutsam bis Hilfsfunktion in vertikaler Richtung praktisch. Denn beim Öffnen eines Moduls werden Sie in der Regel mit den ersten Zeilen konfrontiert. Da macht es sich gut, wenn hier gleich die relevantesten Code-Teile zu finden sind.

Allerdings gibt es hier in punkto Lesbarkeit auch Einschränkungen. Nehmen Sie etwa folgenden Pseudocode:

Funktion A()
     [Code]
     Call Funktion B
     [Code]
Ende Funktion A
Funktion B()
     [Code]
Ende Funktion B

Funktion B wird also von Funktion A benötigt und aufgerufen. Als Hilfsprozedur fände Sie weiter unten im Modul ihren Platz. Stießen Sie in Funktion A bei Durchsicht später auf den Aufruf von B, so müssten Sie weit nach unten scrollen, oder über das Kontextmenü den Eintrag Definition auswählen, um zur Hilfsfunktion B zu gelangen. Besser ist es da, wenn die Hilfsfunktion direkt nach der aufrufenden steht. Versuchen Sie, die Prozedurreihenfolge möglichst kompakt zu halten. Würde Funktion C von mehreren Prozeduren benötigt, sähe das zum Beispiel so aus:

Funktion A()

     [Code]

     Call Funktion C

     [Code]

Ende Funktion A

Funktion B()

     [Code]

     Call Funktion C

Ende Funktion B

Funktion C()

     [Code]

Ende Funktion C

Funktion D()

     [Code]

Ende Funktion D

Überhaupt ist die Frage, in wie viele Prozeduren eine Aufgabe aufgeteilt werden kann und soll. Im Beispiel hätte man auf die Hilfsfunktion C ja auch verzichten und deren Code wiederholt in Prozedur A und B einsetzen können. Hier gilt aber der Grundsatz, dass wiederverwendbarer Code sich nicht wiederholen, sondern in eigene Funktionen ausgelagert werden sollte. Erstens macht es die Routinen abermals besser wartbar, denn Änderungen oder Weiterentwicklungen müssen so nur an einer Stelle (Funktion C) vollzogen werden, statt an mehreren. Und zweitens sind kürzere Prozeduren leichter überschaubar, als Spaghetti-Code, der sich länglich hinzieht.

Dennoch sollte man diese Strukturierung auch nicht zu weit treiben. Wenn Funktion A die Funktion B aufruft, diese wiederum Funktion C, und C verlangt nach D, dann ist zum Verstehen von A die ganze verteilte Kette B bis D zu überblicken. Ist das Ganze dann auch noch über mehrere Module verteilt, dann kann von Überblick eben keine Rede mehr sein. Von sehr komplexen Aufgaben abgesehen, sollte daher in der Regel die Verteilung der Funktionalität auf ein bis zwei Unterebenen ausreichen.

Prozedurstrukturierung

Innerhalb einer Prozedur gibt es eine Menge Gestaltungspotential. Neben dem reinen Layout, auf das wir später noch zu sprechen kommen, fragt sich etwa, wo sich die Dim-Anweisungen zur lokalen Variablendeklaration befinden sollten.

Meist stehen diese ganz am Anfang, müssen dies aber nicht. VBA ist es egal, ob die Variablen alle versammelt zu Anfang deklariert werden, oder irgendwo mitten im Code stehen. Zwar ist es weniger fehleranfällig, wenn die Dim-Anweisungen in einem Block stehen, bei Routinen mit 20 Variablen aber ist dieser Block dann schon ziemlich umfangreich. Stoßen Sie in der Prozedur auf eine Variablenzuweisung, haben den Datentyp der Variablen aber nicht mehr im Kopf, so ist abermals Scrollen zum Prozedurkopf angesagt. Aus diesem Grund kann es sinnvoll sein, die Variablendeklarationen in langen Prozeduren in mehrere Blöcke aufzuteilen. Variablen, die erst weiter unten verwendet werden, könnten dazu eben auch erst später in einem gesonderten Block deklariert werden. Das erhöht wieder die Übersichtlichkeit.

Andere Deklarationen

Im Kopf eines Moduls stehen die Deklarationen zu modulweit oder global gültigen Variablen, sowie gegebenenfalls die API-Deklarationen. Unter Umständen sammelt sich hier viel an, wie etwa Listing 1 zeigt.

Option Compare Database

Option Explicit

'Öffentliche Konstanten

Public Const GWL_STYLE As Long = -16

Public Const GWL_WNDPROC As Long = -4

'Private Konstanten

Private Const GW_CHILD As Long = 5

Private Const GW_HWNDFIRST As Long = 0

Private Const GW_HWNDLAST As Long = 1

Private Const GW_HWNDPREV As Long = 3

'Öffentliche Typen

Public Type Guid

     Data1 As Long

     Data2 As Integer

     Data3 As Integer

     Data4(0 To 7) As Byte

End Type

'Private Typen

Private Type LUID

     LowPart As Long

     HighPart As Long

End Type

'Öffentliche API-Funktionen

Public Declare Function GetWindow Lib "user32.dll" ( _

     ByVal hwnd As Long, _

     ByVal wCmd As Long) As Long

'Private API-Funktionen

Private Declare Function SetWindowPos Lib "user32.dll" ( _

     ByVal hwnd As Long, _

     ByVal hWndInsertAfter As Long, _

     ByVal x As Long, _

     ByVal y As Long, _

     ByVal cx As Long, _

     ByVal cy As Long, _

     ByVal wFlags As Long) _

     As Long

     

'Öffentliche Variablen

Public dbs As DAO.Database

'Private Variablen

Private i As Long, j As Long

'Prozeduren --------------------

Listing 1: Beispiel für Deklarationen in einem Modulkopf

Es ist nützlich, wenn Sie wissen, wo Sie hier was finden können. Stoßen Sie im Code einer Prozedur auf eine Konstantenbezeichnung und möchten deren Wert erfahren, so ist es einfacher, einen Block von Konstanten zu begutachten, als den gesamten Modulkopf nach ihr abzusuchen.

Packen Sie also besser alle Konstanten in einen Block und verfahren Sie mit Typdefinitionen, API-Deklarationen und Variablen genauso. Wenn Sie immer den gleichen Aufbau erzeugen, finden Sie später schneller die gewünschte Stelle im Modulkopf.

Als geeignet hat sich dabei dieser Aufbau erwiesen: Stellen Sie Konstanten ganz oben ins Modul. Das ist nicht unwichtig, denn unter Umständen kommen diese Konstanten im weiteren Verlauf in Typen oder API-Funktionen zum Einsatz. VBA findet diese Konstanten aber nur dann, wenn Sie vor diesen Definitionen deklariert wurden. Dasselbe gilt für die Typendefinitionen im folgenden Block. API-Funktionen oder Variablen könnten ebenfalls nach benutzerdefinierten Typen verlangen, die dann bereits zuvor im Modul definiert sein müssen. Für Typdefinitionen gibt es noch eine weitere Besonderheit, wie folgendes Beispiel zeigt:

Private Type XYZ

     X As Long

     Y As Long

     N As ABC

End Type

Private Type ABC

     A As Integer

     B As Integer

     C As Long

End Type

Das wird VBA nicht zulassen, weil der Typ XYZ im Element N den Typ ABC benötigt, jener aber erst später folgt. Die umgekehrte Reihenfolge ist nötig!

An den Block der Typdefinitionen schließen Sie die API-Deklarationen an. Zum Schluss folgen die Variablendeklarationen, denn diese stehen den Prozeduren quasi am nächsten.

Da alle Deklarationen sowohl private, wie auch öffentliche Gültigkeit aufweisen können, ist zudem ein Aufteilen in diese Gültigkeitsbereiche sinnvoll. Stellen Sie etwa erst alle öffentlichen Elemente zusammen, dann die privaten, so, wie auch im Listing geschehen.

Prozedur-Layout

Hier geht es um die optische Gestaltung des Code. Führen Sie sich dazu das Negativbeispiel in Listing 2 zu Gemüte.

Public Function fuCodeLayout(Optional ByVal x As Long, Optional _

ByVal y As Long) As Boolean

Dim i As Long, j As Long, dbs As DAO.Database, rs As DAO.Recordset

On Error GoTo Fehler

Set dbs = CurrentDb

Set rs = dbs.OpenRecordset("SELECT [Name],[Id],DateCreate,DateUpdate " & _

"FROM MSysObjects WHERE [Type]=1 AND Flags>0", dbOpenDynaset)

With rs

Do While Not .EOF

For j = 0 To 3

Debug.Print .Fields(j).Value,

Next j

i = i + 1

.MoveNext

Loop

.Close

End With

fuCodeLayout = True

Debug.Print i & " Datensätze"

Ende:

On Error Resume Next

rs.Close

Set dbs = Nothing

Exit Function

Fehler:

MsgBox "Fehler in Zeile " & Erl & vbCrLf & Err.description, vbCritical

Resume Ende

End Function

Listing 2: Eine Code-Wüste ohne jegliche optische Strukturierung

Hier gibt es keine Strukturierung durch Leerzeilen oder Einrückungen. Alles ist seriell linksbündig heruntergeschrieben. Das tut dem Auge weh und macht bei der Analyse der Funktion reichlich Mühe. Besser kommt man wohl mit einem Layout zurecht, wie in Listing 3, welches weitgehend die gleiche Prozedur enthält. Was ist hier anders?

Public Function fuCodeLayoutBad(x As Long, y As Long) As Boolean

     Dim i As Long

     Dim dbs As DAO.Database

     Dim sText As String

     Dim j As Long

     Dim lRet As Long

     Dim sTmp As String

     Dim rs As DAO.Recordset

     Set dbs = CurrentDb

     Set rs = dbs.OpenRecordset("SELECT * FROM MSysObjects WHERE [Type]=1 AND Flags>0")

     Do While Not rs.EOF

         Debug.Print rs.Fields("Name"), rs.Fields("Id"), _

              rs.Fields("DateCreate"), rs.Fields("DateUpdate")

         i = i + 1

         rs.MoveNext

     Loop

     rs.Close

     fuCodeLayoutBad = True

     Debug.Print i & " Datensätze"

End Function

Listing 3: Code-Strukturierung durch Leerzeilen und Einrückungen

Zunächst ist der gesamte Code um vier Leerzeichen eingerückt. Damit setzt er sich klar von der Prozedurdeklarationszeile ab. Innerhalb des Moduls werden so die Prozeduren insgesamt deutlicher. Keiner Tipp am Rande: Die Einrückung erfolgt über die Tab-Taste zu Beginn einer Zeile. Wie viele Leerzeichen dies hervorruft, können Sie in den VBA-Optionen festlegen (Extras|Optionen|Editor|Tab-Schrittweite). Der Standard ist 4. Übrigens rückt die Tab-Taste auch gleich mehrere Zeilen ein, wenn Sie diese zuvor markiert haben.

Weiter ist allen Variablendeklarationen oben eine eigene Zeile spendiert worden, statt sie kommagetrennt aneinanderzufügen. Das macht es dem Auge leichter, den Datentyp jeder Variablen zu erkennen, auch wenn das auf Kosten der vertikalen Ausdehnung geht.

Auf die Variablendeklarationen folgt eine Leerzeile, um den Beginn des eigentlichen Codes zu markieren. Die Zuweisung der Objektvariablen im nächsten Schritt ist in einem Zweizeilenblock zusammengefasst. Auch die folgende Do...While-Schleife stellt einen eigenen Block dar, weil sie eine Funktionseinheit bildet. Es ist grundsätzlich gut, solche Funktionseinheiten durch Leerzeilen abzutrennen. Auch der Rückgabewert der Funktion (fuCodeLayoutBad) stellt Imgrunde eine eigene Einheit dar.

Wo Einrückungen anzubringen sind, ist nicht nur Geschmackssache. Schleifen sind dafür ein besonderer Kandidat. Sie erkennen über die Einrückung, wo die Schleife beginnt und endet. Ohne sie hätte das Auge mehr mit dem Auffinden der Grenzen zu tun. Auch With-Anweisungen fallen unter diese Kategorie. Rücken Sie alles innerhalb dieses With...End With-Blocks ein, wie in Listing 4.

Public Function fuCodeLayout(Optional ByVal x As Long, _

                              Optional ByVal y As Long) _

                              As Boolean

     Dim dbs As DAO.Database

     Dim rs As DAO.Recordset

     On Error GoTo Fehler

     Set dbs = CurrentDb

     Set rs = dbs.OpenRecordset("SELECT [Name],[Id],DateCreate,DateUpdate " & _

                                "FROM MSysObjects WHERE [Type]=1 AND Flags>0", _

                                dbOpenDynaset)

     i = 0: j = 0

     With rs

         Do While Not .EOF

             For j = 0 To 3

                 Debug.Print .Fields(j).Value,

             Next j

             Debug.Print

             i = i + 1

             .MoveNext

         Loop

         .Close

     End With

     fuCodeLayout = True

     Debug.Print i & " Datensätze"

Ende:

     On Error Resume Next

     rs.Close

     Set dbs = Nothing

     Exit Function

Fehler:

     MsgBox "Fehler in Zeile " & Erl & vbCrLf & Err.description, _

            vbCritical

     Resume Ende

End Function

Listing 4: Weitere Code-Strukturierung durch Leerzeilen und Einrückungen

Noch ein Wort zu den Zeilenumbrüchen über den Unterstrich am Ende einer Zeile. Lange Zeilen, wie die zum Öffnen des Recordsets im Listing, lassen sich schlecht überblicken. Vor allem dann, wenn sie länger sind, als das Code-Fenster und dann horizontales Scrollen erforderten. Solche Zeilen lassen sich über einen Unterstrich mit vorstehendem Leerzeichen in mehrere auftrennen. Damit deutlich wird, dass es sich um eine Zeile handelt, rücken Sie die zweiten und weiteren Trennzeilen dann ebenfalls ein. Im Beispiel wurde dabei nicht die Schrittweite 4 gewählt, sondern die umgebrochenen Folgezeilen bündig mit dem Beginn des SQL-Statements fortgesetzt. Ähnlich wurde mit der Prozedurdeklarationszeile verfahren, wo die Parameter bündig aneinander ausgerichtet sind.

Hilfsmittel

Module aus fremder Hand oder solche, die Sie noch keiner Gestaltung unterzogen, müssen nicht manuell nachbearbeitet werden. Viel Arbeit nimmt Ihnen ein freies VBA-Add-In ab, das auf den Namen Smart Indenter hört (Link). Einmal installiert können über das Kontextmenü von VBA-Code-Fenstern wahlweise das gesamte VBA-Projekt, einzelne Module oder auch nur einzelne Prozeduren eingerückt und mit Leerzeilen versehen werden. Dabei hält sich das Tool an die Vorgaben, die Sie in seinen Optionen einstellen (siehe Bild 1) – es ist vielseitig konfigurierbar.

Das Add-In SmartIndenter übernimmt das automatische Einrücken des Code

Bild 1: Das Add-In SmartIndenter übernimmt das automatische Einrücken des Code

Auch ein Teil der Features der MZ-Tools deckt die Gestaltung von Code ab. Über die Sortierfunktion des ebenfalls freien VBA-Add-Ins etwa können die Prozeduren eines Moduls in eine neue Anordnung gebracht werden. Dazu besitzt es ein Listenfenster, über das die Prozedurnamen einfach mit der Maus per Drag And Drop verschoben werden können. Nach dem Bestätigen sind die Prozedurblöcke im Modul neu angeordnet. Das ist wesentlich komfortabler, als das Ausschneiden und Einfügen selektierter Abschnitt per Copy And Paste.

Auch das automatische Umbrechen zu langer Zeilen kann das Tool bewerkstelligen. Und noch vieles mehr. Schauen Sie sich die Feature-Liste auf der Website des Autors an. Dieses Tool gehört zur Standardausrüstung jedes VBA-Programmierers.

Schließlich existiert mit einem weiteren VBA-Add-In, dem mossSOFT ProcBrowser ein Werkzeug, das die Navigation durch die Prozeduren eines Moduls erleichtert. Sie können sein Fenster (siehe Bild 2) in die VBA-Umgebung einklinken – etwa rechts neben den Projekt-Explorer –, und haben dann alle Prozeduren des aktuellen Moduls im Überblick. Durch Klick auf einen Eintrag springt der Cursor dann zur gewünschten Stelle. Ohne dieses Tool sind Sie entweder auf Scrollen angewiesen, oder müssen die Prozeduren über die beiden Comboboxen am oberen Rand der Code-Fenster auswählen.

Das Add-In ProcBrowser

Bild 2: Das Add-In ProcBrowser

Schlusswort

Hier sollten lediglich Anregungen gegeben werden, wo und aus welchem Grund die Gestaltung von VBA-Code eine Rolle spielt. Zwar gibt es Regeln, die sich landläufig eingebürgert haben, an die man sich jedoch nicht akribisch halten muss. Arbeiten Sie ausschließlich mit Ihren eigenen Elaboraten, so ist die Gestaltung auch Sache des Geschmacks und der Gewohnheit. Sobald Sie jedoch mit anderen Programmierern zusammenarbeiten, bekommen Gestaltungsregeln einen stärkeren Sinn.

Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.

Bitte geben Sie die Zeichenfolge in das nachfolgende Textfeld ein

Die mit einem * markierten Felder sind Pflichtfelder.

Ich habe die Datenschutzbestimmungen zur Kenntnis genommen.