Das Testen eines Programms ist unumgänglich. Dieses Kapitel stellt Protokollierung und Debugging vor. Am Ende werden noch Klassen grafisch visualisiert, ohne den Kontakt zum Code zu verlieren.
9 Code erstellen und debuggen
9.1 Ausnahmen 

Bereits in Abschnitt 2.8, »Fehlerbehandlung«, habe ich Ihnen die Wirkungsweise der strukturierten Fehlerbehandlung mit Try/Catch/Finally gezeigt. In diesem Abschnitt beschränke ich mich auf einige fehlende Aspekte.
Hinweis |
Leider kann im Programmcode weder getestet werden, ob eine Methode eine Ausnahme auslösen könnte, noch muss deklariert werden, ob bzw. wie mit einer möglichen Ausnahme umzugehen ist. Der Compiler hilft Ihnen in diesem Punkt nicht weiter. |
9.1.1 Methodenaufrufe 

In »älteren« Programmiersprachen gibt es keine gesonderte Fehlerbehandlung. Meist wird ein Fehler durch einen speziellen Rückgabewert einer Funktion signalisiert. Das hat den enormen Nachteil, dass der Aufrufer nicht gezwungen ist, sich mit dem Fehler auseinanderzusetzen – er darf ihn ignorieren. In .NET dagegen werden Fehler durch das Auslösen einer Ausnahme signalisiert, die schlicht zum Abbruch eines Programms führt, wenn sie nicht behandelt wird. Damit dieser Zwang durchgesetzt werden kann, sind Ausnahmen unabhängig von dem, was Sie programmieren. Insbesondere funktionieren sie auch bei beliebig tief verschachtelten Methodenaufrufen. Nicht Sie, sondern die Laufzeitumgebung kümmert sich um den geordneten Ausstieg, bis eine Stelle erreicht ist, an der ein Fehler in einem Catch-Zweig behandelt wird. Damit geht einher, dass Sie Fehler an einer beliebigen, geeigneten Stelle in der Aufrufhierarchie der Methoden behandeln können: direkt beim Aufruf, in derselben Methode, in der übergeordneten Methode oder bei einem Aufrufer der Methode.
Bezüglich dieser Transparenz gegenüber dem Aufrufstack gibt es eine Besonderheit zu beachten. In Abschnitt 2.8.4, »Try/Catch/Finally«, haben wir uns mit dem Finally-Zweig beschäftigt. Er wird sowohl bei Fehlerfreiheit als auch im Fehlerfall ausgeführt. Wie weit das geht, zeigt das folgende Beispiel. Der erste Aufruf von Kehrwert läuft fehlerfrei, im zweiten wird im Catch-Zweig ein Return ausgeführt.
'...\Lauf\Ausnahmen\ReturnFinally.vb |
Option Strict On Namespace Lauf Module ReturnFinally Function Kehrwert(ByVal nenner As Integer) As Double Try Return 1 / nenner Catch ex As Exception Return Double.NaN Finally Console.WriteLine("Arbeit beendet.") End Try End Function Sub Test() Console.WriteLine("1/5={0}", Kehrwert(5)) Console.WriteLine("1/0={0}", Kehrwert(0)) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt, dass mit und ohne Return der Finally-Zweig immer ausgeführt wird.
Arbeit beendet. 1/5=0,2 Arbeit beendet. 1/0=+unendlich
Damit ist der Finally-Zweig der richtige Ort für Anweisungen, die immer – unabhängig vom Erfolg – ausgeführt werden müssen.
9.1.2 Hierarchie der Ausnahmen 

Bei der großen Anzahl an Ausnahmeklassen im .NET Framework – ich habe 636 gezählt – ist eine Organisation unumgänglich. Daher sind auch diese Klassen in einer Vererbungshierarchie organisiert, von der ich hier exemplarisch einen Ausschnitt mit den wichtigsten aus dem Namensraum System und allen aus dem Namensraum System.IO zeige.
Object +Exception +ApplicationException +SystemException +AccessViolationException +ArgumentException | +ArgumentNullException | +ArgumentOutOfRangeException | +DuplicateWaitObjectException +ArithmeticException | +DivideByZeroException | +NotFiniteNumberException | +OverflowException +FormatException | +UriFormatException | +IO.FileFormatException +IndexOutOfRangeException +InvalidCastException +InvalidOperationException | +ObjectDisposedException +NotImplementedException +NotSupportedException | +PlatformNotSupportedException +NullReferenceException +OutOfMemoryException | +InsufficientMemoryException +RankException +StackOverflowException +TypeInitializationException +IO.InternalBufferOverflowException +IO.InvalidDataException +IO.IOException +IO.DirectoryNotFoundException +IO.DriveNotFoundException +IO.EndOfStreamException +IO.FileLoadException +IO.FileNotFoundException +IO.PathTooLongException +IO.PipeException
Die Hierarchie wirkt sich an zwei Stellen besonders aus. Zum Ersten wird eine Referenz einer Ausnahme auf eine ihrer Basisklassen als allgemeiner betrachtet als die Ausnahme selbst. Da die Catch-Zweige immer in der Reihenfolge von speziell zu allgemein sortiert werden müssen, tauchen solche Referenzen weiter unten auf als die Ausnahme selbst. Außerdem kann durch die Ist-eine-Beziehung das Auffangen einer Basisklassen-Ausnahme alle Kindklassen-Ausnahmen mit erfassen, sodass keine vergessen werden kann. Im folgenden Beispiel wird die spezielle Ausnahme DivideByZeroException vor der allgemeineren ArithmeticException behandelt. In der Methode Test werden Werte übergeben, die jede der drei Ausnahmen in den Catch-Zweigen auslösen.
'...\Lauf\Ausnahmen\Hierarchie.vb |
Option Strict On Namespace Lauf Module Hierarchie Class Zahl : Public Wert As Integer : End Class Function Rechnung(ByVal z As Zahl) As Integer Try Return z.Wert * z.Wert \ z.Wert Catch ex As DivideByZeroException Console.Write(ex.Message & ": ") : Return Integer.MaxValue Catch ex As NullReferenceException Console.Write(ex.Message & ": ") : Return Integer.MinValue Catch ex As ArithmeticException Console.Write(ex.Message & ": ") : Return 0 End Try End Function Sub Test() Dim z As New Zahl() z.Wert = 0 : Console.WriteLine(Rechnung(Nothing)) z.Wert = 0 : Console.WriteLine(Rechnung(z)) z.Wert = Integer.MinValue : Console.WriteLine(Rechnung(z)) Console.ReadLine() End Sub End Module End Namespace
Die dritte Zeile zeigt, wie die »vergessene« Ausnahme OverflowException von dem Catch-Zweig mit deren Basisklasse ArithmeticException erfasst wurde.
Object reference not set to an instance of an object.: –2147483648 Attempted to divide by zero.: 2147483647 Arithmetic operation resulted in an overflow.: 0
Der zweite Effekt der Vererbungshierarchie der Ausnahmen ist, dass ausnahmslos alle Ausnahmen von der gemeinsamen Basisklasse Exception abgeleitet sind und deren Funktionalität nutzen können. Tabelle 9.1 zeigt deren Eigenschaften.
Eigenschaft | Beschreibung | |
Data |
Zusätzliche benutzerdefinierte Informationen (IDictionary) |
R |
HelpLink |
Verweist auf eine Hilfedatei, die diese Ausnahme beschreibt |
|
HResult |
Fehlercode zur Interoperabilität mit COM-Klassen (Protected) |
|
InnerException |
Referenz auf die tatsächliche Ausnahme. Diese Information dient dazu, auf geeignetere Weise auf die Ausnahme zu reagieren. |
R |
Message |
Gibt einen String mit der Beschreibung des aktuellen Fehlers zurück. |
R |
Source |
Beschreibung der fehlerauslösenden Anwendung oder des Objekts |
|
StackTrace |
String mit der aktuellen Aufrufreihenfolge aller Methoden |
R |
TargetSite |
Methode, in der die Ausnahme ausgelöst worden ist |
R |
Besonders hinweisen möchte ich auf InnerException, die oft benutzt wird, wenn eine Ausnahme aufgefangen wird und eine andere auslöst. Wenn vor dem Auslösen die innere Ausnahme gesetzt wird, kann der Aufrufer den »wahren« Grund der Ausnahme ermitteln.
9.1.3 Eigene Ausnahmen 

Sie sollten Fehler, die spezifisch für Ihre Anwendung sind, durch eigene Ausnahmen kennzeichnen. Diese müssen sich direkt oder indirekt von Exception ableiten. Damit Anwender nicht denken, dass eine Ihrer Ausnahmen Teil von .NET ist, empfiehlt sich die Ableitung von ApplicationException. Beide genannten Ausnahmeklassen haben die gleichen Konstruktoren, von denen Sie einen – implizit oder explizit – in dem Konstruktor Ihrer Ausnahmeklasse aufrufen müssen.
Public Sub New() Public Sub New(message As String) Protected Sub New(info As SerializationInfo, context As StreamingContext) Public Sub New(message As String, innerException As Exception) |
Von den 636 Ausnahmen, die ich in .NET gezählt habe, enden nur 30 nicht auf Exception. Sie sollten sich bei eigenen Ausnahmeklassen auch an diese Konvention halten, sie macht Ihren Code für andere leichter lesbar. Das folgende Beispiel definiert eine kleine Ausnahmeklassenhierarchie: ApplicationException->FinanzamtException->BuchungsException. In der Me-thode Summe wird ein Fehler mit einer BuchungsException quittiert. Der Aufrufer von Summe, die Methode Abschreibung, verpackt den Fehler in die allgemeinere Ausnahme FinanzamtException. In der Methode Test wird Abschreibung mit Werten aufgerufen, die einen Fehler auslösen. Der Catch-Zweig ist spezifisch für diese Anwendung und behandelt nur hier definierte Ausnahmen. Im Falle eines Fehlers wird nicht nur die Fehlermeldung ausgegeben, sondern auch der dem Fehler zugrunde liegende Fehler.
'...\Lauf\Ausnahmen\Eigene.vb |
Option Strict On Namespace Lauf Module Eigene Class FinanzamtException : Inherits ApplicationException Sub New(nachricht As String, grund As Exception) MyBase.New(nachricht, grund) End Sub Sub New(nachricht As String) MyBase.New(nachricht) End Sub End Class Class BuchungsException : Inherits FinanzamtException Sub New(nachricht As String) MyBase.New(nachricht) End Sub End Class Function Summe(wert As Short, jahre As Short) As Short Try Return wert \ jahre Catch ex As Exception Throw New BuchungsException("Null Jahre.") End Try End Function Function Abschreibung(wert As Short, jahre As Short) As Short Try Return Summe(wert, jahre) Catch ex As BuchungsException Throw New FinanzamtException("Buchungsfehler.", ex) End Try End Function Sub Test() Try Abschreibung(1000, 0) Catch ex As FinanzamtException Console.WriteLine("Fehler beim Finanzamt: {0}", ex.Message) Console.WriteLine("Wahrer Grund: {0}", ex.InnerException) End Try Console.ReadLine() End Sub End Module End Namespace
Die Formatierung der inneren Ausnahme als String beinhaltet unter anderem den Inhalt der Eigenschaft StackTrace und gibt so weitere Hinweise zur Fehlerbeseitigung.
Fehler beim Finanzamt: Buchungsfehler. Wahrer Grund: Ausnahmen.Lauf.Eigene+BuchungsException: Null Jahre. at Ausnahmen.Lauf.Eigene.Summe(Int16 wert, Int16 jahre) in M:\VisualStudioWS\Lauf\Ausnahmen\Eigene.vb:line 21 at Ausnahmen.Lauf.Eigene.Abschreibung(Int16 wert, Int16 jahre) in M:\VisualStudioWS\Lauf\Ausnahmen\Eigene.vb:line 26
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.