Fehlerbehandlung: Unterschied zwischen den Versionen
Pwania (Diskussion | Beiträge) |
Pwania (Diskussion | Beiträge) |
||
Zeile 165: | Zeile 165: | ||
End If | End If | ||
On Error GoTo 0 | On Error GoTo 0 | ||
'''Bitte beachten:''' Bei vielen Arbeitsschritten kann eine vorangehende Prüfung mit einfachen Mitteln stattfinden. Im Falle eines Datums kann zum Beispiel mit der 'IsDate'-Funktion geprüft werden, ob der Wert als Datum interpretiert werden kann, wodurch die Fehlerbhandlung entfällt: | |||
If IsDate(strDate) = True Then | |||
dteDate = CDate(strDate) | |||
Else | |||
MsgBox "Das Datum ist ungültig. Bitte geben Sie das korrekte Datum ein." | |||
Exit Sub | |||
End If | |||
'''Merke:''' Das Verhindern von Fehlern ist immer besser als Fehler zuunterdrücken. Diese Startegie sollte nur dann eingesetzt werden, wenn eine Verhinderung nicht oder nur sehr umständlich möglich ist. | |||
== Meldungen unterdrücken mit Application.DisplayAlerts == | == Meldungen unterdrücken mit Application.DisplayAlerts == |
Version vom 15. Februar 2023, 16:06 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.
Wir können uns das als eine Art Rechtschreib- und Grammatikprüfung des Programmcodes vorstellen.
Laufzeitfehler
Ein Laufzeitfehler tritt erst beim Ausführen der Befehle auf. Sie bedeuten, dass die Befehle zwar korrekt formuliert sind, aber zum Beispiel mit den tatsächlichen Werten nicht funktionieren können.
Ein klassisches Beispiel hierfür ist das Teilen durch Null. Wir gebildete Menschen wissen, dass diese Teilung nicht erlaubt ist und würden sie selbstverständlich immer vermeiden.
Beim Kompilieren des Programmcodes würde eine Angabe
x = 1 / 0
jedoch keinen Kompilierungsfehler hervorrufen, denn an dieser Programmzeile ist rein syntaktisch nichts auszusetzen: Der Variablen 'x' wird lediglich das Ergebnis einer Division zugewiesen.
Wenn aber dieser Code ausgeführt wird (also zur 'Laufzeit'), stellt das VBA fest, dass eine Teilung durch Null nicht erlaubt ist und zeigt dann die entsprechende Fehlermeldung an.
Die weitere Ausführung des Programmcodes wird hierbei unterbrochen.
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
Bitte beachten: Bei vielen Arbeitsschritten kann eine vorangehende Prüfung mit einfachen Mitteln stattfinden. Im Falle eines Datums kann zum Beispiel mit der 'IsDate'-Funktion geprüft werden, ob der Wert als Datum interpretiert werden kann, wodurch die Fehlerbhandlung entfällt:
If IsDate(strDate) = True Then dteDate = CDate(strDate) Else MsgBox "Das Datum ist ungültig. Bitte geben Sie das korrekte Datum ein." Exit Sub End If
Merke: Das Verhindern von Fehlern ist immer besser als Fehler zuunterdrücken. Diese Startegie sollte nur dann eingesetzt werden, wenn eine Verhinderung nicht oder nur sehr umständlich möglich ist.
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