Fehlerbehandlung

Aus VBA-wiki
Version vom 23. November 2017, 11:19 Uhr von Pwania (Diskussion | Beiträge) (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…“)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

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