Fehlerbehandlung: Unterschied zwischen den Versionen

Aus VBA-wiki
Zur Navigation springen Zur Suche springen
(Die Seite wurde neu angelegt: „Eines der wichtigsten Ziele eines Programmierers sollte selbstverständlich darin bestehen, mögliche Fehler zu vermeiden. Und nur in Ausnahmefällen, wenn zum…“)
 
Keine Bearbeitungszusammenfassung
Zeile 5: Zeile 5:
=== Kompilierungsfehler ===
=== 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.
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 ===
=== 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.
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 ==
== 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.
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 ==
== 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!
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 ===
=== 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.
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 ===
=== 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
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 ===
=== Bitte beachten ===
Zeile 35: Zeile 112:
==== Fehlerbehandlung von If- und Select-Abfragen fernhalten ====
==== 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 ...
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 ====
==== 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
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 ==
== 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.
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) ===
=== 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.
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 ===
=== Bitte beachten ===
Zeile 53: Zeile 181:
==== Begründete Verwendung ====
==== 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.
* 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.
* Setzen Sie sie nicht proaktiv, sondern ausschließlich als Reaktion auf eine Warnung, die von der Anwendung angezeigt wird, ein.


==== Punktueller Einsatz ====
==== 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
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 ===
=== 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
* 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 ==
== Eigene Fehler erzeugen: CVErr und IsError ==
Zeile 68: Zeile 214:
=== Beispiel ===
=== 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.
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 ===
=== 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


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
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

Version vom 10. Oktober 2018, 16:09 Uhr

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