Fehlerbehandlung

Aus VBA-wiki
Wechseln zu: Navigation, Suche

Eines der wichtigsten Ziele eines Programmierers sollte selbstverständlich darin bestehen, mögliche Fehler zu vermeiden. Und nur in Ausnahmefällen, wenn zum Beispiel eine Vermeidung unmöglich oder nur sehr schwer möglich ist, Fehler zu unterdrücken:

Fehlerarten

Kompilierungsfehler

Die erste Instanz, die für uns als VBA-Programmierer Fehler vermeiden hilft, ist die Kompilierung unserer Projekte. Diese wird automatisch vor dem Ausführen unseres Codes durchgeführt oder kann von uns durch den Befehl 'Debuggen', 'Kompilieren von Projekt' direkt ausgeführt werden.

Bei der Kompilierung wird vorrangig geprüft, ob die Befehle aufgelöst werden können, also eine Entsprechung gefunden werden kann. Fehlschreibweisen und das Fehlen von Referenzen werden somit schnell aufgefunden und können korrigiert werden.

Laufzeitfehler

Wenn ein sogenannter Laufzeitfehler auftritt, also bei der Ausführung der Programmierung festgestellt wird, dass ein Befehl nicht wie erwartet durchgeführt werden kann, wenn zum Beispiel ein übergebener Wert nicht den Anforderungen entspricht.

Laufzeitfehler lassen sich oft nur schwierig komplett vermeiden, insbesondere wenn die Werte aus Benutzereingaben stammen oder wenn die Ausgangssituation sich während der Ausführung des Codes ändert.

Fehler vermeiden

Wenn die Werte, die eine gewünschte Operation nicht zulassen, bekannt sind, kann der problematische Wert geprüft und die Operation nur dann zugelassen werden, wenn es der Wert zulässt.

Die Division durch 0 (Null) ist zum Beispiel nicht zulässig und erzwingt den Laufzeitfehler 11, 'Division durch Null'.

In diesem Beispiel wird durch die Abfrage, ob der Divisor nicht 0 ist, verhindert, dass eine Division durch 0 zu einem Fehler führt:

   If lngDivisor <> 0 Then
       dblQuotient = lngDividend / lngDivisor
   End If

Ebenfalls könnte die Benutzereingabe eines Datums geprüft werden, ob sie ein Datum enthält, bevor sie tatsächlich in eine Variable vom Datentyp 'Date' übernommen wird. Die direkte Übernahme eines Textes, der nicht ein Datum enthält, würde unweigerlich zum Laufzeitfehler 13, 'Typen unverträglich' führen:

   Dim dteDate As Date
   
   dteDate = InputBox("Bitte geben Sie ein Datum ein:")
   MsgBox "Sie haben den " & dteDate & " eingegeben."

Deshalb sollte die Eingabe erst einmal in eine Zeichenkette übernommen, geprüft und dann erst in eine Datumsvariable übernommen werden:

   Dim strInput As String
   Dim dteDate As Date
   
   strInput = InputBox("Bitte geben Sie ein Datum ein:")
   If IsDate(strInput) = True Then
       dteDate = strInput
       MsgBox "Sie haben den " & dteDate & " eingegeben."
   End If

Tipp: Neben der 'IsDate'-Funktion ist die 'IsNumeric'-Funktion hilfreich, um eingegebene Zahlenwerte zu prüfen.

Fehler unterdrücken

In seltenen Fällen ist jedoch unklar oder nur sehr schwer ermittelbar, mit welchen Werten oder in welcher Situation ein Fehler auftreten wird.

Bei einer Collection kann zum Beispiel mithilfe der 'Item'-Eigenschaft auf ein einzelnes Element der Auflistung zugegriffen werden. Hierbei kann entweder der numerische Index oder der beim Hinzufügen des Elementes verwendete Schlüssel eingesetzt werden:

strItem = colCollection.Item(2)
strItem = colCollection.Item("Schlüssel")

Ob die Collection wirklich ein Element mit dem numerischen Index enthält, lässt sich wie folgt mithilfe der 'Count'-Eigenschaft ermitteln:

   If lngIndex > 0 And lngIndex <= colCollection.Count Then
       strItem = colCollection.Item(lngIndex)
   End If

Für den Zugriff auf ein Element mithilfe des Schlüssels gibt es leider keine entsprechende Möglichkeit, vorher zu klären, ob der Schlüssel tatsächlich schon vergeben wurde:

   Dim strItem As String
   Dim colCollection As Collection
   
   Set colCollection = New Collection
   colCollection.Add "Wert", "Schlüssel"
   
   strItem = colCollection.Item("Falscher Schlüssel")      ' Erzeugt Fehler!

Hier kann also nicht verhindert werden, einen Laufzeitfehler zu erzeugen!

Fehlerbehandlung ausschalten

Damit der Anwender den Fehler nicht angezeigt bekommt und die Programmierung trotz dem Fehler weiter ausgeführt werden kann, besteht die Möglichkeit, die Fehlerbehandlung kurzfristig auszuschalten:

   On Error Resume Next
   strItem = colCollection.Item("Falscher Schlüssel")
   On Error GoTo 0

Der Befehl 'On Error Resume Next' bewirkt, dass beim Auftreten eines Fehlers dieser erst einmal ignoriert, also nicht angezeigt, und die Ausführung der weiteren Befehle nicht unterbrochen wird.

Der Befehl 'On Error GoTo 0' sorgt dafür, dass die Fehlerbehandlung anschließend wieder eingeschaltet wird.

Wichtig: Bitte behandeln Sie diese beiden Befehle immer wie ein untrennbares Paar. Wenn Sie vergessen, die Fehlerbehandlung mit 'On Error GoTo 0' wieder einzuschalten, kann dies sehr schwer nachvollziehbare Auswirkungen auf die weitere Ausführung Ihrer Programmierung haben. Die Fehlerbehandlung ist anwendungsübergreifend -- das bedeutet, dass sie nicht nur in dieser Prozedur, sondern von hier an ausgeschaltet ist.

Eine permanent ausgeschaltete Fehlerbehandlung kann Auswirkungen auf If-Abfragen und auf Folgefehler haben.

Fehlercode abfragen

Im oberen Beispiel haben wir dafür gesorgt, dass die Zeile 'strItem = colCollection.Item("Falscher Schlüssel")' keinen Fehler anzeigt. Das Ergebnis in 'strItem' wäre demnach ein Leerstring ("").

Wenn jedoch zum Beispiel zusätzlich dem Anwender mitgeteilt werden soll, dass das gesuchte Element nicht in der Auflistung enthalten ist, können Sie die Befehlsfolge wie folgt erweitern:

   On Error GoTo 0              ' eventuell vorhandene Fehler im Fehlerspeicher zurücksetzen
   On Error Resume Next
   strItem = colCollection.Item("Falscher Schlüssel")
   If Err.Number <> 0 Then
       MsgBox "Das Element 'Falscher Schlüssel' konnte nicht gefunden werden!"
   End If
   On Error GoTo 0

Bitte beachten

Fehlerbehandlung punktuell einsetzen

Wie schon erwähnt, sollte die Fehlerbehandlung immer nur punktuell und mit Bedacht eingesetzt werden. Es wäre zwar schön, wenn der Anwender niemals wieder von Fehlermeldungen belästigt würde, aber dies kann auch dazu führen, dass schwerwiegende Fehler ignoriert und eventuelle Folgefehler entstehen.

Fehlerbehandlung von If- und Select-Abfragen fernhalten

Wenn Sie innerhalb der Fehlerbehandlung eine If- bzw. eine Select-Abfrage einsetzen, kann das Ergebnis der Bedingung verfälscht werden, weil bei Eintreten eines Fehlers der Code trotzdem weiter ausgeführt wird oder weil das Ergebnis 'False' eventuell ignoriert wird und dann der falsche Zweig ausgeführt wird.

   Dim lngResult As Long
   
   lngResult = 5
   On Error Resume Next
   lngResult = CLng(1 / 0)
   If lngResult = 0 Then     ' lngResult ist nach der Division durch 0 immer noch 5!!!
       '...
   End If
   Debug.Print lngResult 

Die Division durch 0 hätte eigentlich einen Fehler verursacht, die weitere Ausführung wäre damit abgebrochen worden. Mithilfe des 'On Error Resume Next' wird der Fehler ignoriert und die Zeile 'lngResult = CLng(1 / 0)' hat keinen Effekt, der Wert in lngResult bleibt weiterhin 5 ...

Fehlerbehandlung immer zurücksetzen

Bitte beachten Sie immer, dass die Fehlerbehandlung nach dem Einsatz in jedem Fall zurückgesetzt werden sollte:

   On Error GoTo 0             
   On Error Resume Next
   dteDate = CDate(strDate)
   If Err.Number <> 0 Then
       MsgBox "Das Datum ist ungültig. Bitte geben Sie das korrekte Datum ein."
       Exit Sub
   End If
   On Error GoTo 0             

In diesem Beispiel wird zwar die Fehlerbehandlung mit 'On Error GoTo 0' am Ende der If-Abfrage zurückgesetzt, beim Eintreten eines Fehlers erfolgt jedoch durch das 'Exit Sub' ein vorzeitiges Verlassen der Prozedur.

Mit der folgenden Erweiterung um ein zusätzliches 'On Error GoTo 0' in der If-Abfrage wird auf jeden Fall die Fehlerbehandlung wieder zurückgesetzt:

   On Error GoTo 0             
   On Error Resume Next
   dteDate = CDate(strDate)
   If Err.Number <> 0 Then
       MsgBox "Das Datum ist ungültig. Bitte geben Sie das korrekte Datum ein."
       On Error GoTo 0  
       Exit Sub
   End If
   On Error GoTo 0

Meldungen unterdrücken mit Application.DisplayAlerts

Mit der Befehlszeile

   Application.DisplayAlerts = False

können Sie in Word, Excel und PowerPoint verhindern, dass Benutzerabfragen, welche im aktuellen Zusammenhang störend bzw. überflüssig sind, angezeigt werden.

Beispiel: Löschen eines Arbeitsblattes (Excel)

Wenn Sie zum Beispiel in Excel per Programmierung ein Arbeitsblatt mit dem Befehl

   ActiveSheet.Delete

löschen, wird der Anwender gefragt, ob er dies tatsächlich durchführen möchte. Wenn er dies verneint, entsteht ein Laufzeitfehler, weil somit der 'Delete'-Vorgang nicht abgeschlossen werden konnte.

Im folgenden Beispiel wird das aktuelle Arbeitsblatt ohne zusätzliche Abfrage gelöscht:

   Application.DisplayAlerts = False
   ActiveSheet.Delete
   Application.DisplayAlerts = True

Nun erscheint keine Warnung für den Anwender -- somit ist der Entwickler dafür verantwortlich, dass der Lösch-Vorgang nicht am falschen Arbeitsblatt ausgeführt wird.

Bitte beachten

Begründete Verwendung

  • Die Unterdrückung von Warnungen sollte nur dann eingesetzt werden, wenn eine von der Anwendung ausgegebene Warnung die Ausführung der Programmierung unterbricht oder in Frage stellt.
  • Setzen Sie sie nicht proaktiv, sondern ausschließlich als Reaktion auf eine Warnung, die von der Anwendung angezeigt wird, ein.

Punktueller Einsatz

Die Unterdrückung von Warnungen sollte, wie bei den 'On Error ...'-Anweisungen schon erwähnt, nur punktuell eingesetzt werden, sonst können hier ebenso ungewollte und unvorhergesehene Probleme auftreten:

   Application.DisplayAlerts = False
   ActiveSheet.Delete
   ' ...
   ActiveWorkbook.Save

Wenn in diesem Fall das Speichern der Datei nicht möglich ist, erscheint keine entsprechende Warnung, weil diese zum Löschen des Arbeitsblattes schon ausgeschaltet worden war.

Besser:

   Application.DisplayAlerts = False
   ActiveSheet.Delete
   Application.DisplayAlerts = True
   ' ...
   ActiveWorkbook.Save

Hinweise

  • In Word und PowerPoint werden statt 'True' und 'False' folgende Einstellungen verwendet:
   Application.DisplayAlerts = ppAlertsNone        ' Schaltet Warnungen aus
   Application.DisplayAlerts = ppAlertsAll         ' Schaltet Warnungen ein

Eigene Fehler erzeugen: CVErr und IsError

Beispiel

Die Funktion CVErr kann einen benutzerdefinierten Fehlercode erzeugen, welcher von IsError als solcher erkannt werden kann:

Public Function Quotient(ByVal lngNumerator As Long, ByVal lngDenominator As Long) As Variant
   
    If lngDenominator <> 0 Then
       Quotient = lngNumerator / lngDenominator
    Else
        Quotient = CVErr(9)
    End If
End Function


Private Sub TestQuotient()
    Dim varQuotient As Variant
    
    varQuotient = Quotient(1, 2)
    If IsError(varQuotient) = False Then
        MsgBox "1 : 2 = " & varQuotient
   End If
     varQuotient = Quotient(1, 0)
    If IsError(varQuotient) = False Then
        MsgBox "1 : 0 = " & varQuotient
    End If
End Sub

Allerdings funktioniert diese Vorgehensweise ausschließlich, wenn alle beteiligten Variablen (inklusive dem Rückgabewert der Funktion) als Variant deklariert sind. Leider kann später nur festgestellt werden, dass ein Fehler aufgetreten ist, aber nicht direkt, welcher.

Empfehlung

Wählen Sie stattdessen ein prägnantes aber sehr unwahrscheinliches bzw. gänzlich unmögliches Rückgabeergebnis, um auf Fehler hinzuweisen.

Wenn Sie zum Beispiel den Index einer Spalte anhand der Spaltenüberschrift suchen und Sie möchten signalisieren, dass die Spalte nicht gefunden werden konnte, geben Sie den Wert '-1' zurück. Eine Spalte mit diesem Index kann es nicht geben, also muss ein Problem aufgetreten sein.

Wenn Sie kein prägnantes Rückgabeergebnis für den Fehlerfall finden, können Sie einen Enumerator hierfür verwenden. Unser Beispiel könnte entsprechend folgendermaßen angepasst werden:

Public Enum enmErrorCode
    enmErrorCodeDivisionByZero = -898754648     ' Sehr unwahrscheinliches Rückgabeergebnis einer Division
End Enum


Public Function Quotient(ByVal lngNumerator As Long, ByVal lngDenominator As Long) As Double
   
    If lngDenominator <> 0 Then
       Quotient = lngNumerator / lngDenominator
    Else
        Quotient = enmErrorCodeDivisionByZero
    End If
End Function


Private Sub TestQuotient()
    Dim dblQuotient As Double
    
    dblQuotient = Quotient(1, 2)
    If dblQuotient <> enmErrorCodeDivisionByZero Then
        MsgBox "1 : 2 = " & dblQuotient
    Else
        MsgBox "Bei der Division ist ein Fehler aufgetreten!"
    End If
    dblQuotient = Quotient(1, 0)
    If dblQuotient <> enmErrorCodeDivisionByZero Then
        MsgBox "1 : 0 = " & dblQuotient
    Else
        MsgBox "Bei der Division ist ein Fehler aufgetreten!"
    End If
End Sub