2.8 Fehlerbehandlung 

Bei jedem Programm können während der Laufzeit Fehler auftreten. Zum Beispiel wollen Sie eine Datei schreiben, aber die Festplatte ist voll. In einem solchen Fall kann die Schreibroutine nicht weitermachen und informiert Ihr Programm über das Scheitern. Dies kann auf zwei Arten geschehen:
- Rückgabewert: Der Rückgabewert der aufgerufenen Funktion signalisiert Erfolg oder Misserfolg.
- Ausnahme: Das Programm löst eine sogenannte Ausnahme aus und verzweigt in einen Programmfluss, der parallel zum normalen Fluss ist.
Nur wenige Methoden des .NET Frameworks machen von der ersten Möglichkeit Gebrauch. Die Mehrzahl macht den Rückgabewert unabhängig vom Erfolg der Funktion und hat damit größere Flexibilität bei der Wahl des Typs des Rückgabewertes. Außerdem ist damit der normale Programmfluss sauber von der Fehlerbehandlung getrennt.
Hinweis |
Die hier dargestellte Fehlerbehandlung ist zwar komfortabel, sollte aber nicht für den normalen Programmfluss verwendet werden, um den normalen Ablauf sauber von den Ausnahmen zu trennen. Außerdem ist die Fehlerbehandlung deutlich langsamer als »normaler« Programmcode. |
2.8.1 Ohne Fehlerbehandlung 

Was passiert nun im Fehlerfall? Nehmen wir dazu den Fall einer Division durch null. Das folgende Programmfragment hat noch keine Ausnahmebehandlung. Es simuliert eine einfache Lotterie: Ein Einsatz wird gleichmäßig auf die Gewinner verteilt. Sowohl der Einsatz als auch die Zahl der Gewinner sind zufällig, und beide können unabhängig voneinander null betragen.
' ...\Sprachsyntax\Fehlerbehandlung\Fehlerbehandlung.vb |
Option Strict On
Namespace Sprachsyntax
Module Fehlerbehandlung
...
Sub OhneFehlerbehandlung()
Randomize() 'Initialisierung Zufallszahlengenerator
Dim Einsatz As Integer = CType(Rnd() * 100, Integer)
Dim Gewinner As Integer = CType(Rnd() * 3, Integer)
Dim Gewinn, Jackpot As Integer
Console.WriteLine("Verteile {0} auf {1}", _
Einsatz, Gewinner)
Gewinn = Einsatz \ Gewinner 'hier kann es klemmen!
Console.WriteLine("Jeder bekommt {0}", Gewinn)
Jackpot = Einsatz – Gewinn * Gewinner
Console.WriteLine("Jackpot {0}", Jackpot)
Console.ReadLine()
End Sub
End Module
End Namespace
Wenn zufällig kein Gewinner dabei ist, findet bei der Gewinnermittlung eine Division durch null statt. Das Ergebnis ist eine Unterbrechung des Programms durch den Debugger (siehe Abbildung 2.39).
Abbildung 2.39 Unbehandelte Ausnahme
An dieser Stelle bleibt Ihnen nichts anderes übrig, als das Programm über den Menüpunkt Debuggen • Debuggen beenden zu beenden. Ohne Debugger wäre das Programm durch die nicht behandelte Ausnahme sofort beendet worden.
2.8.2 Funktionsweise von Try/Catch/Finally 

Die Ausnahme des letzten Beispiels kann dadurch behandelt werden, dass die kritische Anweisung in ein Konstrukt verpackt wird, das sie auffängt. Seine Struktur wird hier gezeigt und in den folgenden Abschnitten an Beispielen näher erläutert. In der folgenden Syntax sind optionale Elemente in eckige Klammern gesetzt und kursive Teile müssen Sie Ihren Bedürfnissen anpassen. Dabei muss entweder mindestens ein Catch oder das Finally vorhanden sein. Beide auszulassen macht das Try bedeutungslos, und sie dürfen daher nicht gleichzeitig fehlen.
Try |
Hinweis |
Der Compiler gibt weder eine Fehler- noch eine Warnmeldung, wenn Fehler unbehandelt bleiben. Die Fehlerbehandlung liegt in Ihrer Verantwortung (anders als in Java). Es gibt kommerzielle Software, die sich auf die Suche nach unbehandelten Ausnahmen macht (zum Beispiel Exception Hunter). |
Im Try-Zweig stehen die Anweisungen, die eine Ausnahme verursachen könnten. Wenn alles fehlerfrei läuft, werden sie ganz normal abgearbeitet, als wäre gar kein Try vorhanden. Kommt es aber zu einem Fehler, wird eine Ausnahme ausgelöst (engl. to throw an exception). Dann wird der Try-Zweig verlassen, und es werden sukzessive alle Catch-Zweige daraufhin geprüft, ob sie für die Ausnahme zuständig sind und die ausgelöste Ausnahme auffangen können. Wird ein passendes Catch gefunden, werden die Anweisungen in diesem Zweig ausgeführt. Unabhängig davon, ob eine Ausnahme aufgetreten ist oder nicht, werden dann die Anweisungen im Finally-Zweig ausgewertet. Läuft dieser fehlerfrei, wird mit der nächsten Anweisung nach dem End Try fortgesetzt. Kommt es zu einer Ausnahme im Finally-Zweig, wird er sofort verlassen (analog zu Try). Zusammenfassend ergibt sich folgender Ablauf:
- Anweisungen in Try
- Ausnahmefall (es wird eine Ausnahme ausgelöst)
- Suche nach passendem Catch
- wenn gefunden: Anweisungen in Catch
- Anweisungen in Finally
Hinweis |
Der Finally-Zweig wird auch dann ausgeführt, wenn eine Ausnahme in einem Catch-Zweig auftritt. |
Im Folgenden werden die Varianten des Try/Catch/Finally-Blocks anhand von Beispielen aufgezeigt.
2.8.3 Try/Catch 

Nun können Sie die Lotterieanwendung etwas robuster gestalten, indem Sie die kritische Zeile in einen Try-Block verpacken. Im Catch-Zweig wird dann über den Misserfolg der Gewinnermittlung berichtet, sodass Sie wissen, ob etwas schiefgelaufen ist. Alle Ausnahmen sind vom allgemeinen Typ Exception, der hier der Einfachheit halber verwendet wird. In Abschnitt 2.8.7, »Spezifische Ausnahmen und mehrere Catch-Zweige«, werden wir sehen, wie der Ausnahmetyp genauer festgelegt werden kann.
' ...\Sprachsyntax\Fehlerbehandlung\Fehlerbehandlung.vb |
Option Strict On
Namespace Sprachsyntax
Module Fehlerbehandlung
...
Sub TryCatch()
Randomize() 'Initialisierung Zufallszahlengenerator
Dim Einsatz As Integer = CType(Rnd() * 100, Integer)
Dim Gewinner As Integer = CType(Rnd() * 3, Integer)
Dim Gewinn, Jackpot As Integer
Try
Console.WriteLine("Verteile {0} auf {1}", Einsatz, Gewinner)
Gewinn = Einsatz \ Gewinner 'hier kann es klemmen!
Console.WriteLine("Jeder bekommt {0}", Gewinn)
Jackpot = Einsatz – Gewinn * Gewinner
Console.WriteLine("Jackpot {0}", Jackpot)
Catch ex As Exception
Console.WriteLine("Fehler: {0}", ex.Message)
Console.WriteLine("Es gibt keine Gewinner.")
End Try
Console.ReadLine()
End Sub
End Module
End Namespace
Wenn es Gewinner gibt, wird eine »normale« Ausgabe erzeugt.
Verteile 74 auf 3
Jeder bekommt 24
Jackpot 2
Im Fall, dass es keinen Gewinner gibt, sieht die Ausgabe etwas anders aus.
Verteile 28 auf 0
Fehler: Attempted to divide by zero.
Es gibt keine Gewinner.
2.8.4 Try/Catch/Finally 

Selbst wenn es keine Gewinner gibt, sollte das Geld nicht verloren gehen, sondern für das nächste Spiel im Jackpot landen. Für diesen Fall wird ein Finally-Zweig eingefügt. Da er immer ausgeführt wird, ist sichergestellt, dass mit und ohne Gewinner der Jackpot ermittelt wird.
' ...\Sprachsyntax\Fehlerbehandlung\Fehlerbehandlung.vb |
Option Strict On
Namespace Sprachsyntax
Module Fehlerbehandlung
...
Sub TryCatch()
Randomize() 'Initialisierung Zufallszahlengenerator
Dim Einsatz As Integer = CType(Rnd() * 100, Integer)
Dim Gewinner As Integer = CType(Rnd() * 3, Integer)
Dim Gewinn, Jackpot As Integer
Try
Console.WriteLine("Verteile {0} auf {1}", Einsatz, Gewinner)
Gewinn = Einsatz \ Gewinner 'hier kann es klemmen!
Console.WriteLine("Jeder bekommt {0}", Gewinn)
Catch ex As Exception
Console.WriteLine("Fehler: {0}", ex.Message)
Console.WriteLine("Es gibt keine Gewinner.")
Finally
Jackpot = Einsatz – Gewinn * Gewinner
Console.WriteLine("Jackpot {0}", Jackpot)
End Try
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe im Fall ohne Gewinner hat sich verbessert: Der Jackpot wird auch dann angegeben.
Verteile 90 auf 0
Fehler: Attempted to divide by zero.
Es gibt keine Gewinner.
Jackpot 90
2.8.5 Try/Finally 

Soll nicht der Organisator, sondern sollen die Nachrichten die Gewinner bekannt geben, kann die Fehlerbehandlung delegiert werden. Das folgende Codefragment zeigt, dass man trotzdem nicht auf die Ermittlung des Jackpots verzichten muss, da der Finally-Zweig immer ausgeführt wird. Dazu wird der Jackpot in einer Modulvariablen gespeichert. Es hätte keinen Sinn, TryFinally() als Funktion mit dem Jackpot als Rückgabewert zu schreiben und diesen einer Variablen zuzuweisen, da im Ausnahmefall die Zuweisung gar nicht mehr ausgeführt würde. Im folgenden Codefragment sind die Quellen einer Ausgabe durch »N:« und »O:« identifizierbar.
' ...\Sprachsyntax\Fehlerbehandlung\Fehlerbehandlung.vb |
Option Strict On
Namespace Sprachsyntax
Module Fehlerbehandlung
...
Dim Jackpot As Integer() 'Modulebene!
Sub Weiterleitung()
Try
TryFinally()
Catch ex As Exception
Console.WriteLine("N: Fehler: {0}", ex.Message)
Console.WriteLine("N: Es gibt keine Gewinner.")
End Try
Console.WriteLine("N: Jackpot {0}", Jackpot)
Console.ReadLine()
End Sub
Sub TryFinally()
Randomize() 'Initialisierung Zufallszahlengenerator
Dim Einsatz As Integer = CType(Rnd() * 100, Integer)
Dim Gewinner As Integer = CType(Rnd() * 3, Integer)
Dim Gewinn As Integer
Try
Console.WriteLine("O: Verteile {0} auf {1}", Einsatz, Gewinner)
Gewinn = Einsatz \ Gewinner 'hier kann es klemmen!
Console.WriteLine("O: Jeder bekommt {0}", Gewinn)
Finally
Jackpot = Einsatz – Gewinn * Gewinner
End Try
End Sub
End Module
End Namespace
Wie gewünscht, wird eine Ausnahme in der aufrufenden Funktion, kenntlich durch »N:«, behandelt, und es ergibt sich folgende Ausgabe:
O: Verteile 85 auf 0
N: Fehler: Attempted to divide by zero.
N: Es gibt keine Gewinner.
N: Jackpot 85
2.8.6 Rethrow 

Je dichter Sie am Ort des Geschehens über Probleme berichten, desto besser. Das vorige Beispiel kann so abgeändert werden, dass über das Scheitern der Gewinnermittlung bereits in der Methode berichtet wird, die den Gewinn ermittelt. Damit auch die aufrufende Methode, die Nachrichten »N:«, über die Ausnahme Bescheid wissen, wird im Catch-Zweig ein Throw eingefügt. Es bewirkt, dass die behandelte Ausnahme erneut ausgelöst wird und so de facto nicht behandelt ist und in der aufrufenden Methode mit einem Catch aufgefangen werden muss.
' ...\Sprachsyntax\Fehlerbehandlung\Fehlerbehandlung.vb |
Option Strict On
Namespace Sprachsyntax
Module Fehlerbehandlung
...
Dim Jackpot As Integer() 'Modulebene!
Sub WeiterleitungOhneBericht()
Try
Rethrow()
Catch ex As Exception
Console.WriteLine("N: Es gibt keine Gewinner.")
End Try
Console.WriteLine("N: Jackpot {0}", Jackpot)
Console.ReadLine()
End Sub
Sub Rethrow()
Randomize() 'Initialisierung Zufallszahlengenerator
Dim Einsatz As Integer = CType(Rnd() * 100, Integer)
Dim Gewinner As Integer = CType(Rnd() * 3, Integer)
Dim Gewinn As Integer
Try
Console.WriteLine("O: Verteile {0} auf {1}", Einsatz, Gewinner)
Gewinn = Einsatz \ Gewinner 'hier kann es klemmen!
Console.WriteLine("O: Jeder bekommt {0}", Gewinn)
Catch ex As Exception
Console.WriteLine("O: Fehler: {0}", ex.Message)
Throw
Finally
Jackpot = Einsatz – Gewinn * Gewinner
End Try
End Sub
End Module
End Namespace
In der Ausgabe ist zu sehen, dass nun über die Ausnahme in der aufrufenden Methode berichtet wird, die durch »O:« kenntlich gemacht ist:
O: Verteile 87 auf 0
O: Fehler: Attempted to divide by zero.
N: Es gibt keine Gewinner.
N: Jackpot 87
2.8.7 Spezifische Ausnahmen und mehrere Catch-Zweige 

In einer etwas komplexeren Lotterie können außer dem Fall, dass es keine Gewinner gibt, weitere Ausnahmen auftreten. Damit nicht alle Ausnahmen in einem einzelnen Catch-Zweig behandelt werden, können Sie mehrere Catch-Zweige definieren, die eine Ausnahme spezifisch behandeln. Im Fall einer Ausnahme werden die Zweige so lange durchlaufen, bis ein passender gefunden wird oder keiner mehr übrig ist. Der erste passende Catch-Zweig wird dann ausgeführt.
Im folgenden Codefragment kümmert sich der erste Catch-Zweig um den Fall, dass keine Gewinner existieren und eine Division durch null stattfindet. Der Zweite behandelt den Fall, dass der Einsatz zu groß ist und den Datentyp Byte der Variablen Einsatz sprengt, aber nur, wenn mehr als 50 Spieler teilgenommen haben. Alle anderen Möglichkeiten, z. B. dass Einsatz zu groß ist, fängt der dritte Catch-Zweig ab. Die Zahlenkommentare geben jeweils die Stelle an, an der eine Ausnahme ausgelöst werden kann, und die Stelle, an der sie behandelt wird.
' ...\Sprachsyntax\Fehlerbehandlung\Fehlerbehandlung.vb |
Option Strict On
Namespace Sprachsyntax
Module Fehlerbehandlung
...
Sub SpezifischeCatch()
Randomize() 'Initialisierung Zufallszahlengenerator
Dim Einsatz As Byte = CType(Rnd() * 10, Byte)
Dim Spieler As Byte = CType(Rnd() * 100, Byte)
Dim Gewinner As Byte = CType(Rnd() * 3, Byte)
Try
Dim Topf As Byte = Spieler * Einsatz '1
Console.WriteLine("Verteile {0} auf {1}", Topf, Gewinner)
Dim Gewinn As Byte = Topf \ Gewinner '2
Console.WriteLine("Jeder bekommt {0}", Gewinn)
Catch ex As DivideByZeroException '2
Console.WriteLine("Fehler: {0}", ex.Message)
Console.WriteLine("Keine Gewinner.")
Catch ex As OverflowException When Spieler > 50 '1
Console.WriteLine("Fehler: {0}", ex.Message)
Console.WriteLine("{0} Spieler.", Spieler)
Catch ex As OverflowException '1
Console.WriteLine("Fehler: {0}", ex.Message)
Console.WriteLine("{1} Spieler mit Einsatz {0}.", Einsatz, Spieler)
End Try
Console.ReadLine()
End Sub
End Module
End Namespace
Der Fall, dass keine Gewinner existieren, generierte die folgende Ausgabe:
Verteile 224 auf 0
Fehler: Attempted to divide by zero.
Keine Gewinner.
Zu viele Spieler können den erlaubten Einsatz sprengen. Die Tatsache, dass nicht außerdem eine Ausgabe des Einsatzes im dritten Catch-Zweig erfolgt, beweist, dass der erste passende Catch-Zweig der einzige ist, der ausgeführt wird, selbst wenn noch weitere passende folgen.
Fehler: Arithmetic operation resulted in an overflow.
74 Spieler.
Ist der Einsatz zu groß und die Spielerzahl kleiner als 50, wird der dritte Catch-Zweig ausgeführt:
Fehler: Arithmetic operation resulted in an overflow.
42 Spieler mit Einsatz 7.
Hinweis |
Ein Catch ohne Angabe des Ausnahmetyps fängt alle Ausnahmen ab. |
Hinweis |
Die Bedingung in When hat Zugriff auf die Ausnahme. |
2.8.8 Unstrukturierte Fehlerbehandlung 

Der Vollständigkeit halber soll noch eine andere Art der Fehlerbehandlung erwähnt werden, die zwecks Kompatibilität zu älteren Visual-Basic-Versionen erlaubt ist. Sie sollte möglichst vermieden werden, da sie das Verständnis des Programmflusses erschwert und keine strukturierte Fehlerbehandlung erlaubt. Dieser Abschnitt sollte daher als Hilfe zum Verständnis älterer Quelltexte verstanden werden und nicht als Anregung für eigene Programme.
Die unstrukturierte Fehlerbehandlung besteht aus drei Teilen (kursive Namen müssen Sie Ihren Bedürfnissen anpassen):
- Vereinbarung des Sprungziels für den Fehlerfall:
On Error Goto Ziel
- Auslösen des Fehlers:
Error Nummer
- Sprungziel mit Fortsetzung des Programms durch (ohne Next wird wieder zum Anfang der fehlerauslösenden Zeile gesprungen):
Resume Next
Das nächste Codefragment zeigt die Verwendung. Erst wird ein Sprungziel vereinbart. Nach dem Auslösen des Fehlers wird zum vereinbarten Ziel gesprungen. Die dem Label Ziel: folgenden Anweisungen werden ausgeführt, wobei Resume Next zur Zeile nach der fehlerauslösenden Zeile springt.
' ...\Sprachsyntax\Fehlerbehandlung\Fehlerbehandlung.vb |
Option Strict On
Namespace Sprachsyntax
Module Fehlerbehandlung
...
Sub Unstrukturiert()
On Error GoTo Ziel
Error 7
Console.WriteLine("Nach Fehler.")
Console.ReadLine()
Return
Ziel: Console.WriteLine("Nach Ziel.")
Resume Next
End Sub
End Module
End Namespace
Die Ausgabe zeigt diese Reihenfolge der Auswertung:
Nach Ziel.
Nach Fehler.
Hinweis |
In einer Methode kann nur ein Sprungziel für den Fehlerfall vereinbart werden, das in derselben Methode liegen muss. |
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.