Wer mit VBA arbeitet, wird früher oder später auf das Thema Fehlerbehandlung stoßen. Je komplexer Code wird und je mehr sein Ablauf von externen Faktoren beeinflusst wird, desto mehr sind sie auf eine Fehlerbehandlung angewiesen. Die Fehlerbehandlung sorgt dafür, dass der Benutzer nicht mit schwer interpretierbaren oder noch besser gar nicht mit Fehlermeldungen konfrontiert wird. Im Hintergrund hilft eine Fehlerbehandlung aber noch in vielen anderen Fällen, von denen der Benutzer gar nichts mitbekommt. Wozu eine Fehlerbehandlung? Eine perfekte Anwendung arbeitet ohne Fehler. Perfekte Anwendungen sind jedoch selten, also treten auch Fehler auf. Zumindest der Benutzer einer Anwendung sollte jedoch den Eindruck erhalten, dass er mit einer perfekten Anwendung arbeitet oder zumindest mit einer, die Fehler, wenn sie denn auftreten, entsprechend professionell verarbeitet. Sprich: Besser, eine Anwendung wird im schlimmsten Fell beim Auftreten eine Fehlers kontrolliert beendet, als wenn sie unkontrolliert abstürzt und am Ende sogar noch Daten verloren gehen. Wenn unter VBA ein Fehler auftritt, geht das in der Regel beispielsweise mit dem Leeren von Objektvariablen einher. Wenn Sie also gerade eine globale Objektvariable mit einem Verweis auf ein Objekt wie ein Formular gefüllt haben und dann ein Fehler auftritt, wird die Objektvariable geleert. Mit der Folge, dass ein Zugriff auf das mit dieser Variable referenzierte Objekt ebenfalls zu einem Fehler führt. Wenn Sie in einem solchen Fall eine Fehlerbehandlung verwenden, verhindern Sie zumindest den Folgefehler. Und Sie präsentieren dem Benutzer damit gegebenenfalls eine aussagekräftige Fehlermeldung statt einer von Access/VBA generierten Fehlermeldung, mit welcher der Benutzer oft nichts anfangen kann. ser eine entsprechende Meldung aus und zeigt die fehlerhafte Stelle im Code an. Dies gilt jedoch nur, wenn Sie die Vollversion von Access verwenden und die Datei im.mdb-, dem.accdb- oder einem anderen nicht geschützten Format vorliegt. Wenn die Datenbank als.mde- oder.accde-datenbank kompiliert wurde, also ihr Code nicht mehr im Entwurf eingesehen werden kann, erhalten Sie nur die Fehlermeldung mit Nummer und Text, können aber nicht mehr den fehlerhaften Code einsehen. Bei Verwendung der Runtime-Version von Access (mehr dazu in einem späteren Artikel) erhalten Sie nur noch eine allgemeine Fehlermeldung. Deshalb ist gerade in diesem Fall eine benutzerdefinierte Fehlerbehandlung sehr wichtig! Die Meldung sieht beispielsweise wie in Bild 1 aus, weitere Beispiele und Grundlagen zu den verschiedenen Fehlerarten erhalten Sie im Artikel Fehler unter VBA. Fehlerbehandlung von Laufzeitfehler Im Gegensatz zu Syntax- oder Kompilierfehlern können Sie auf Fehler zur Laufzeit mit entsprechenden Anweisungen reagieren. Die einfachste Möglichkeit ist es, den Fehler einfach zu ignorieren. Dazu tragen Sie die Anweisung innerhalb der Routine, aber vor der fehlerhaften Zeile ein. Ein Beispiel sieht so aus: Dieser Artikel zeigt, wie Sie die unter VBA auftretenden Fehler so behandeln, dass Ihre Anwendung stabil weiterläuft und der Benutzer gegebenenfalls erfährt, was warum schief gelaufen ist. Fehler ohne Fehlerbehandlung Tritt ein Fehler ohne Fehlerbehandlung auf, löst die- Bild 1: Fehlermeldung eines unbehandelten Fehlers Seite 11
Public Sub DivisionDurchNullFehlerbehandlung() Das Resultat dieser Vorgehensweise ist, dass der in der Zeile Debug.Print 1/0 auftretende Fehler einfach ignoriert wird. Dummerweise tut die Zeile aber auch nichts mehr. Im folgenden Beispiel tritt in der Zeile i = 32768 ein Überlauf auf (Fehler 6). Die fehlerhafte Zeile wird nicht ausgeführt, i wird also nicht mit dem angegebenen Wert gefüllt. Integer-Variablen haben den Standardwert 0, also gibt das Meldungsfenster den Wert 0 aus: Public Sub Ueberlauf() Dim i As Integer i = 32768 MsgBox i Das ist ein Problem, denn durch den übergangenen Fehler entsteht ein logischer Fehler: Obwohl i eigentlich den Wert 32768 enthalten sollte, liefert das Meldungsfenster den Wert 0. Solche logischen Fehler entstehen oft, wenn man teilweise aus Bequemlichkeit einen Fehler mit On Error Resume Next unterbindet. sollten Sie also nur dort einsetzen, wo es nicht anders geht oder dort, wo Sie vielleicht sogar einen Fehler provozieren möchten (hierzu später mehr). Datenbankdatei befindet (CurrentProject.Path liefert den Datenbankpfad): MkDir CurrentProject.Path & "\Export" Das Dumme ist nur: Wenn Sie die Prozedur, in der sich diese Anweisung befindet, ein zweites Mal ausführen, ist das Verzeichnis bereits vorhanden; der erneute Versuch, dieses anzulegen, führt zu einem Fehler (siehe Bild 2). Fehlerbehandlung deaktivieren Nun gibt es zwei Möglichkeiten: Entweder Sie prüfen zuvor, ob das Verzeichnis schon vorhanden ist, oder Sie ignorieren einfach den Fehler beim erneuten Anlegen, indem Sie zuvor die Anweisung On Error Resume Next ausführen. Letzteres ist kein Problem, wenn Sie sehr vorsichtig damit umgehen. Das zieht in diesem Fall vor allem nach sich, dass Sie die Fehlerbehandlung unmittelbar im Anschluss an die betroffene Anweisung wieder so einstellen, dass Fehlermeldungen angezeigt werden! Dies erledigen Sie mit der Anweisung On Error Goto 0. Wenn Sie die folgende Prozedur ausführen, löst das wiederholte Anlegen des Verzeichnisses keine Fehlermeldung aus, wohl aber die folgende Division durch 0: Public Sub VerzeichnisAnlegen() MkDir CurrentProject.Path & "\Export" On Error GoTo 0 Ein Beispiel für den sinnvollen Einsatz ist etwa das Anlegen eines Verzeichnisses im aktuellen Datenbankverzeichnis beispielsweise, um dort Daten etwa im Excel-Format zu exportieren. Die Anweisung MkDir legt ein mit dem einzigen Parameter angegebenes Verzeichnis an. Wenn Sie diesen Parameter wie folgt verwenden, entsteht dadurch etwa das Verzeichnis Export in dem Ordner, in dem sich auch die Bild 2: Ein bereits vorhandenes Verzeichnis lässt sich nicht nochmals anlegen. Seite 12
Was also erledigt On Error Goto 0 genau? Es deaktiviert eine eventuell zuvor aktivierte benutzerdefinierte Fehlerbehandlung, die im einfachsten Fall nur aus dem Ignorieren von Fehlern durch die Anweisung On Error Resume Next besteht. Es setzt aber auch das Fehlerobjekt von VBA zurück und dieses schauen wir uns im nächsten Abschnitt genauer an. Das Fehlerobjekt Err Auch wenn wir in Access [basics] noch nicht explizit über Objekte gesprochen haben: Bei der Fehlerbehandlung kommen wir nicht umhin, diesem Thema ein wenig vorzugreifen. Entscheidend für die Fehlerbehandlung ist nämlich, dass Sie zunächst einmal den Fehler erkennen können. Dazu schreibt VBA (fast) alle notwendigen Informationen in das Fehlerobjekt Err. Wenn Sie beispielsweise nach dem Auftreten eines Fehlers seine Fehlernummer ermitteln möchten, erledigen Sie das wie im folgenden Codeschnipsel: Debug.Print Err.Number Die erste Anweisung schaltet die VBA-interne Fehlerbehandlung ab, die zweite löst den Fehler aus und die dritte gibt die Nummer des durch die zweite Anweisung ausgelösten Fehlers aus. In diesem Fall gibt die dritte Anweisung den Wert 11 im Direktfenster aus. Vielleicht möchten Sie auch noch die Fehlerbeschreibung ausgeben? Kein Problem fügen Sie einfach noch die folgende Zeile hinzu: Debug.Print Err.Description Im Direktfenster erscheint nun zusätzlich der Text Division durch Null. Bei der Verwendung von eingebauten VBA-Objekten unterstützt der VBA-Editor, indem er die Eigenschaften und Methoden dieser Objekte direkt nach der Eingabe des Objektnamens und eines Punktes zur Auswahl anbietet (siehe Bild 3). Zur Begriffsklärung: Eine Methode eines Objekts ist nichts weiter als ein Befehl, den Sie im Kontext dieses Objekts aufrufen können. Die beiden Eigenschaften Number und Description sind im Rahmen einer einfachen Fehlerbehandlung auch schon die einzigen beiden interessanten Eigenschaften, wobei prinzipiell die Eigenschaft Number und die darauf gewonnene Fehlernummer für die Fehlerbehandlung völlig ausreicht. On Error Goto 0 und das Err-Objekt Nun haben wir weiter oben erwähnt, dass Sie mit On Error Resume Next die VBA-interne Fehlerbehandlung ausschalten und diese mit On Error Goto 0 wieder aktivieren. Bei der Anweisung On Error Goto 0 ist besonders die Wirkung auf das Err-Objekt zu beachten: Dieses wird dadurch nämlich wieder in seinen Ausgangszustand versetzt, wodurch etwa die Eigenschaft Number den Wert 0 und Description bei erneuter Abfrage eine leere Zeichenkette liefert. Sollten Sie also nach einem durch Einsatz von On Error Resume Next unterdrückten Fehler die Eigenschaften des Err-Objekts auswerten wollen, dürfen Sie die VBA-interne Fehlerbehandlung nicht zuvor wieder mit On Error Goto 0 wieder einschalten. Bild 3: Der VBA-Editor bietet alle Eigenschaften und Methoden des Err-Objekts per IntelliSense zur Auswahl an. Die richtige Vorgehensweise sähe dann so aus: Zuerst wird die Fehlerbehandlung mit On Error Resume Next deaktiviert, der Fehler wird ausgelöst, die Fehlerursache ausgegeben (hier per Meldungsfenster) und schließlich die Fehlerbehandlung von VBA wieder angeschaltet. Den Code zeigt folgendes Listing, das Ergebnis Bild 4: Seite 13
Public Sub VerzeichnisAnlegenMitMeldung() MkDir CurrentProject.Path & "\Export" MsgBox Err.Number & " " & Err.Description On Error GoTo 0 Ablauf einer Fehlerbehandlung Wenngleich Sie, wie weiter unten beschrieben, auch gezielt Fehler auslösen und diese behandeln können, wissen Sie doch meist eher nicht genau, wann und wo ein Fehler auftritt. Manchmal geschieht das erst, wenn der Benutzer Eingaben durchführt, die Sie schlicht und einfach nicht getestet haben. Also werden Sie für solche Fälle eine allgemeine Fehlerbehandlung einbauen und diese um Spezialfälle ergänzen. Ohne Spezialfälle sieht dies wie im folgenden Listing aus (zum Ablauf siehe auch Bild 5): Public Function Division(intDividend As Integer, _ intdivisor As Integer) As Single On Error GoTo Fehler Division = intdividend / intdivisor Ende: Exit Function Fehler: MsgBox "Fehler " & Err.Number & ": " _ & Err.Description GoTo Ende End Function Zu Beginn wird mit On Error Goto Fehler festgelegt, was im Falle eines Fehlers geschehen soll und zwar soll die Routine an einer anderen Stelle fortgeführt werden. Diese Stelle wird durch eine sogenannte Sprungmarke definiert. Sprungmarken können, wenn man sie öfter als nötig einsetzt, die Lesbarkeit des Codes erheblich erschweren. Einer der wenigen Fälle, in denen professionelle VBA-Programmierer Sprungmarken gutheißen, ist die Fehlerbehandlung. Diese enthält klassischerweise zwei Sprungmarken: Eine, zu der gesprungen wird, wenn ein Fehler geschieht, und eine, zu welcher der Programmfluss nach der Abarbeitung der Fehlerbehandlung umgeleitet wird. Bild 4: Deaktivieren der VBA-Fehlerbehandlung, Ausgabe einer benutzerdefinierten Fehlermeldung und Einschalten der VBA-Fehlerbehandlung Im obigen Codebeispiel heißen diese beiden Sprungmarken Fehler und Ende. Sprungmarken bestehen lediglich aus ei- Bild 5: Schematischer Ablauf einer einfachen Fehlerbehandlung Seite 14
nem Text oder einer Zahl und einem abschließenden Doppelpunkt. Sprungmarken landen grundsätzlich am linken Rand, egal, wie groß der Einzug beim Festlegen der Sprungmarke war. Im Beispiel oben löst die Zeile Division = intdividend / intdivisor einen Fehler aus, wenn der Parameter int- Divisor den Wert 0 enthält. Die Prozedur springt dann zur Sprungmarke Fehler. Dort wird die benutzerdefinierte Fehlermeldung ausgeben und ein weiterer Sprung eingeleitet der zur Sprungmarke Ende. Hier finden Sie nur noch die Anweisung Exit Sub. Warum dies? Nun: Wenn die Prozedur durchläuft, ohne das ein Fehler auftritt, muss diese vor dem Erreichen der hinter der Sprungmarke Fehler befindlichen Anweisungen enden. Sonst würde ja in jedem Fall die Fehlermeldung erscheinen, wenn auch mit der Fehlernummer 0 und ohne Fehlermeldung. Dies hat einen praktischen Vorteil: Es kann sein, dass Sie vor dem Beenden der Routine unbedingt noch bestimmte Schritte durchführen müssen etwa das Schließen einer zuvor zum Schreiben geöffneten Textdatei. Solche Anweisungen können Sie prima hinter der Sprungmarke Ende, aber vor der Anweisung Exit Function einfügen. Damit macht es nun auch Sinn, beim Auftreten eines Fehlers nicht einfach die Routine zu beenden, sondern noch zur Sprungmarke Ende zu springen und notwendige Restarbeiten zu erledigen. Fehlerbehandlung und Variablen Einer der wichtigsten Gründe für den Einsatz einer benutzerdefinierten Fehlerbehandlung sind die Auswirkungen unbehandelter Fehler auf Variablen. Schauen Sie sich das folgende Beispiel an hier deklarieren wir zunächst eine globale Variable namens i in einem Standardmodul: Dim i As Integer Weisen Sie dieser Variablen dann über das Direktfenster einen Wert zu, beispielsweise 10: i = 10 Probieren Sie aus, ob die Variable diesen Wert gespeichert hat ebenfalls mithilfe des Direktfensters: Debug.Print i Diese Anweisung gibt nun den zuvor eingegebenen Wert aus. Führen Sie dann die folgende Prozedur aus: Public Sub DivisionDurchNull() Fragen Sie dann nochmals mit Debug.Print i den Wert von i im Direktfenster ab die Variable enthält nun keinen Wert mehr! Wenn Sie andererseits eine benutzerdefinierte Fehlerbehandlung implementieren, dem Fehler also eine On Error... Anweisung voranstellen, behält die Variable ihren Inhalt sogar dann, wenn Sie den Fehler schlicht mit der Anweisung On Error Resume Next ignorieren. Allein aus diesem Grund sollten Ihre Routinen keine Zeile Code enthalten, die einen Fehler auslösen kann und nicht durch eine benutzerdefinierte Fehlermeldung abgefangen wird. Wie geht es nach dem Fehler weiter? Wenn ein Fehler aufgetreten ist, werden Sie wie oben beschrieben zur Fehlerbehandlung springen. Dort erledigen Sie die notwendigen Schritte, also beispielsweise die Ausgabe einer benutzerdefinierten Fehlermeldung oder auch die Dokumentation des Fehlers (mehr dazu in einem späteren Artikel). Wie aber geht es danach weiter? In der Regel beenden Sie die aktuelle Prozedur durch einen Sprung zur Ende-Marke. Sie können allerdings auch alternative Vorgehensweisen wählen: Zur fehlerhaften Zeile zurückspringen: Mit der Anweisung Resume springen Sie zu der Zeile, die den Fehler ausgelöst hat. Zur ersten Zeile hinter der fehlerhaften Zeile springen: Dies erreichen Sie mit der Anweisung Resume Next. Seite 15
Zu einer Sprungmarke springen: Dies gelingt wie oben mit Goto <Sprungmarke>, aber auch mit Resume <Sprungmarke>. Der große Unterschied zwischen Goto <Sprungmarke> und Resume <Sprungmarke> ist, dass die Resume-Anweisung das Err-Objekt leert, also beispielsweise die Eigenschaften Number auf 0 und Description auf eine leere Zeichenkette zurücksetzt. Einen Einsatzzweck für die Resume-Anweisung zum Zurückkehren der fehlerhaften Zeile liefert die folgende Beispielprozedur. Hier wird schlicht der Grund für den Fehler behoben, indem die Variable i auf einen anderen Wert eingestellt wird: Public Sub test() Dim i As Integer On Error GoTo Fehler i = 0 Debug.Print 1 / i Ende: 'Restarbeiten erledigen Exit Sub Fehler: Select Case Err.Number Case 11 i = i + 1 Resume End Select Fehlerbehandlung in Formularen In Formularen gibt es Fehler, die Sie über den herkömmlichen Weg nicht behandeln können: Wenn Sie beispielsweise einen Fehler verursachen, weil Sie einen Wert in ein Feld mit eindeutigem Index eintragen, der bereits vorhanden ist, erscheint zwar eine Fehlermeldung. Sie können diese aber nicht abfangen wie auch? Immerhin kommen Sie bei dieser Konstellation theoretisch komplett ohne VBA-Code aus daher können Sie auch keine VBA-Anweisung als Sündenbock ausfindig machen und diese dementsprechend mit einer Fehlerbehandlung versehen. Es gibt allerdings auch hier eine Möglichkeit, und zwar eine Ereignisprozedur, die Sie für die Ereigniseigenschaft Bei Fehler festlegen. Diese bekommt mit dem Parameter DataErr die Nummer des Fehlers übergeben, der für die Anzeige der Fehlermeldung verantwortlich war. Da die Behandlung von Fehlern innerhalb von Formularen, die nicht direkt durch VBA-Anweisungen ausgelöst werden, sich etwas von der hier beschriebenen Vorgehensweise unterscheidet, haben wir diese mit einigen Beispielen in einem eigenen Beitrag beschrieben siehe Fehlerbehandlung in Formularen. Zusammenfassung und Ausblick Dieser Artikel liefert die technischen Möglichkeiten für die Fehlerbehandlung. In der Praxis eröffnet das eine Reihe weiterer Möglichkeiten zum Beispiel können Sie geziehlt Fehler provozieren und darauf reagieren. Eine Fehlerbehandlung soll auch immer helfen, eine Anwendung zu verbessern: Dazu soll die Fehlerbehandlung Fehler, die nicht vorhergesehen wurden, dokumentiert und in einer Form aufbereitet werden, in welcher der Benutzer diese unkompliziert zum Entwickler übermitteln kann, damit dieser den Fehler beheben kann. In weiteren Artikel werden wir das Thema weiter vertiefen. Außerdem gibt es noch einen Themenkomplex, der mit dem Bereich Fehlerbehandlung eng verknüpft ist: das Debugging und somit das Auffinden von Laufzeitfehlern und logischen Fehlern. Auch hierzu erfahren Sie in den folgenden Ausgaben mehr. Seite 16