No Exceptions, no cry.
Louiza Houache | shutterstock.com
Exception Handling kommt in Programmiersprachen seit Dekaden zum Einsatz, um Laufzeitfehler in Applikationen zu behandeln. Allerdings sind Exceptions (Ausnahmen) der Performance abträglich, weswegen sie vermieden werden sollten. Dieser Artikel gibt Ihnen einige Strategien an die Hand, um das zu bewerkstelligen.
Um die Code-Beispiele in diesem Beitrag zu nutzen, sollten Sie Visual Studio 2022 auf ihrem System installiert haben. Ist das nicht der Fall, können Sie die Software direkt bei Microsoft herunterladen.
Im ersten Schritt erstellen wir nun ein .NET Core Console-Application-Projekt in Visual Studio. Dazu genügen folgende Schritte:
Starten Sie die Visual Studio IDE.
Erstellen Sie ein neues Projekt.
Wählen Sie in der Template-Liste „Console App (.NET Core)“.
Konfigurieren Sie Ihr neues Projekt, indem Sie einen Namen und den Speicherort festlegen.
Wählen Sie nun noch die Framework-Version aus, die Sie verwenden möchten – in unserem Fall „.NET 8.0“.
Klicken Sie auf Erstellen.
Anhand dieses nun erstellten .NET-Core-Application-Projekts werden wir im Folgenden verschiedene Möglichkeiten diskutieren, um Exceptions zu vermeiden. Zunächst jedoch ein Blick auf die wesentlichen Gründe, warum Exceptions im Code zu vermeiden sind:
Hoher Overhead. Wenn innerhalb einer Applikation immer wieder Exceptions geworfen werden, wirkt sich das negativ auf die App-Performance aus.
Komplexerer Code. Wenn sich das Abfangen von Exceptions im Quellcode häuft, wird dieser schwieriger zu lesen und zu warten.
Ein Beispiel, um zu verdeutlichen, wie das in der Praxis aussieht: Wenn in .NET eine Exception ausgelöst wird, wird der normale Ausführungsprozess Ihrer Anwendung durch einen dreistufigen Prozess unterbrochen. Den nutzt die Laufzeitumgebung für das Exception Handling. Der Ablauf sieht dabei wie folgt aus:
Die Laufzeitumgebung erstellt ein Exception-Objekt, das die Details der Ausnahme enthält, beispielsweise den Stack Trace, die Error Message oder den Typ der ausgelösten Ausnahme (ArithmeticException, DivideByZeroException, IndexOutOfRangeException, StackOverflowException, etc.).
In der nächsten Phase sucht die Laufzeitumgebung nach einem passenden try-catch-Block, der die aufgetretene Exception behandeln kann. Dieser Prozess beginnt an dem Punkt, an dem die Ausnahme ausgelöst wurde und wandert den Call Stack nach oben, bis die Runtime den richtigen Exception Handling Code ermittelt hat.
Ist das erledigt, wird der Quellcode innerhalb des try-catch-Blocks ausgeführt. Enthält letztgenannter einen finally-Block, wird dieser nach dem try-catch-Block ausgeführt. Ist der richtige Code zur Behandlung der Ausnahme nicht verfügbar, wird sie als unbehandelt („unhandled“) betrachtet. In diesem Fall wird die Kontrolle über den Call Stack weitergegeben, bis sie den Einstiegspunkt der Applikation erreicht. An diesem Punkt wird die Runtime beendet und eine entsprechende Fehlermeldung ausgegeben.
Nachdem wir uns den eingangs beschriebenen Overhead nun en Detail angesehen haben, werfen wir einen Blick auf einige praktische Strategien, um Exceptions in C# zu vermeiden.
Eine gute allgemeine Strategie um Exception Handling Code zu vermeiden, bietet das Result Pattern. Dieses können Sie in Ihren C#-Code über eine generische Klasse implementieren, die das Ergebnis einer spezifischen Operation kapselt.
Wenn ein Fehler in Ihrer App auftritt, können Sie dieses Pattern nutzen, um ein Ergebnisobjekt zurückzugeben, statt eine Exception zu werfen. Das gewährleistet simpleren, saubereren und leichter zu pflegenden Code.
Die nachfolgende Klasse namens Result stellt das Ergebnis einer Operation dar.
public class Result
{
public bool IsSuccess { get; }
public T Value { get; }
public string ErrorMessage { get; }
private Result(T value, string errorMessage, bool isSuccess)
{
Value = value;
ErrorMessage = errorMessage;
IsSuccess = isSuccess;
}
public static Result Success(T value) =>
new Result(value, null, true);
public static Result Failure(string errorMessage) =>
new Result(default(T), errorMessage, false);
}
Die Klasse enthält zwei Methoden, nämlich Success und Failure. Während erstere einen Wert zurückgibt, gibt zweitere eine Fehlermeldung zurück.
Die folgende Methode zeigt, wie Sie die Result-Klasse verwenden können, um Ausnahmen in Ihrem Code zu vermeiden.
public Result DivideNumbers(int x, int y)
{
if (x == 0)
{
return Result.Failure(“Division by zero is not allowed.”);
}
int result = x / y;
return Result.Success(result);
}
Der folgende Codeschnipsel zeigt, wie Sie das Ergebnismuster mit der DivideNumbers-Methode verwenden können.
var result = Test.DivideNumbers(15, 5);
if (result.IsSuccess)
Console.WriteLine($”The result is: {result.Value}”);
else
Console.WriteLine($”Error occurred: {result.ErrorMessage}”);
Das Try-Parse-Pattern bietet ebenfalls eine gute Möglichkeit, um Exceptions in Ihrer Anwendung zu vermeiden. In C# wird dieses Pattern durch die TryParse-Methode repräsentiert, die Datentypen transformiert und einen booleschen Wert zurückgibt.
Wenn der Parsing-Prozess erfolgreich verläuft, ist der Output true, anderenfalls false. Dieses Pattern können Sie folgendermaßen nutzen, um Ausnahmen in Ihrem Code zu vermeiden.
String str = “1000”;
Boolean result = Int32.TryParse(str, out int n);
if (result == true)
Console.WriteLine($”{n}”);
else
Console.WriteLine(“Error in conversion”);
Wenn Sie Datentypen konvertieren, sollten Sie das eben demonstrierte Try-Parse-Pattern nutzen. Ansonsten gibt es noch weitere Try-Methoden – beispielsweise TryGetValue.
Diese Methoden geben false zurück, wenn sie nicht erfolgreich waren. Der Output einer erfolgreichen Operation wird über einen Out-Parameter zurückgegeben. Das nachfolgende Code Snippet zeigt, wie Sie das erreichen.
int myValue;
if (dictionary.TryGetValue(“MyKey”, out myValue))
{
//TryGetValue is successful so you can proceed as usual
}
else
{
//TryGetValue is unsuccessful so display
//or return an appropriate error message
}
Sie können Ausnahmen auch vermeiden, indem Sie Bedingungen behandeln, die zur Runtime eine solche auslösen können. Bevor Sie die Verbindung zu einer Datenbank kappen, sollten Sie beispielsweise prüfen, ob ein Connection Object ein Nullwert ist oder bereits geschlossen ist. Das nachfolgende Snippet beschreibt diese Technik.
if (connection!= null && connection.State != ConnectionState.Closed)
{
connection.Close();
}
Wenn Sie Ihre Datenbank-Verbindungsinstanzen nicht auf Niullwerte prüfen oder eine bereits geschlossene Verbindung explizit mit einem Call der Close-Methode schließen, kann eine InvalidOperationException auftreten. Wie Sie diese in Ihrem Code behandeln sollten, zeigt folgender Code.
try
{
connection.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
}
Sich häufende Exceptions können sich wie bereits erwähnt negativ auf die Applikations-Leistung auswirken. Sie sollten deshalb Maßnahmen ergreifen, um Exception Handling innerhalb Ihres Codes zu vermeiden, wenn dieses leicht durch Logik ersetzt werden kann. Eine Faustregel ist dabei, Ihren Code auf gängige Error Conditions zu überprüfen. In allen anderen Fällen können Sie vom Result Pattern profitieren, das eine strukturierte Error-Handling-Methode bereitstellt.
Ein finaler Tipp: Exceptions sollten nur in Ausnahmefällen verwendet werden – also beispielsweise, wenn Sie bereits wissen, dass ein Fehler auftreten könnte. Und bitte: Verwenden Sie Ausnahmen nie, um den Control Flow einer Applikation zu managen. (fm)
Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!
No Exceptions, no cry.
Louiza Houache | shutterstock.com
Exception Handling kommt in Programmiersprachen seit Dekaden zum Einsatz, um Laufzeitfehler in Applikationen zu behandeln. Allerdings sind Exceptions (Ausnahmen) der Performance abträglich, weswegen sie vermieden werden sollten. Dieser Artikel gibt Ihnen einige Strategien an die Hand, um das zu bewerkstelligen.
Um die Code-Beispiele in diesem Beitrag zu nutzen, sollten Sie Visual Studio 2022 auf ihrem System installiert haben. Ist das nicht der Fall, können Sie die Software direkt bei Microsoft herunterladen.
.NET-Core-Projekt in Visual Studio erstellen
Im ersten Schritt erstellen wir nun ein .NET Core Console-Application-Projekt in Visual Studio. Dazu genügen folgende Schritte:
Starten Sie die Visual Studio IDE.
Erstellen Sie ein neues Projekt.
Wählen Sie in der Template-Liste „Console App (.NET Core)“.
Konfigurieren Sie Ihr neues Projekt, indem Sie einen Namen und den Speicherort festlegen.
Wählen Sie nun noch die Framework-Version aus, die Sie verwenden möchten – in unserem Fall „.NET 8.0“.
Klicken Sie auf Erstellen.
Anhand dieses nun erstellten .NET-Core-Application-Projekts werden wir im Folgenden verschiedene Möglichkeiten diskutieren, um Exceptions zu vermeiden. Zunächst jedoch ein Blick auf die wesentlichen Gründe, warum Exceptions im Code zu vermeiden sind:
Hoher Overhead. Wenn innerhalb einer Applikation immer wieder Exceptions geworfen werden, wirkt sich das negativ auf die App-Performance aus.
Komplexerer Code. Wenn sich das Abfangen von Exceptions im Quellcode häuft, wird dieser schwieriger zu lesen und zu warten.
Ein Beispiel, um zu verdeutlichen, wie das in der Praxis aussieht: Wenn in .NET eine Exception ausgelöst wird, wird der normale Ausführungsprozess Ihrer Anwendung durch einen dreistufigen Prozess unterbrochen. Den nutzt die Laufzeitumgebung für das Exception Handling. Der Ablauf sieht dabei wie folgt aus:
Die Laufzeitumgebung erstellt ein Exception-Objekt, das die Details der Ausnahme enthält, beispielsweise den Stack Trace, die Error Message oder den Typ der ausgelösten Ausnahme (ArithmeticException, DivideByZeroException, IndexOutOfRangeException, StackOverflowException, etc.).
In der nächsten Phase sucht die Laufzeitumgebung nach einem passenden try-catch-Block, der die aufgetretene Exception behandeln kann. Dieser Prozess beginnt an dem Punkt, an dem die Ausnahme ausgelöst wurde und wandert den Call Stack nach oben, bis die Runtime den richtigen Exception Handling Code ermittelt hat.
Ist das erledigt, wird der Quellcode innerhalb des try-catch-Blocks ausgeführt. Enthält letztgenannter einen finally-Block, wird dieser nach dem try-catch-Block ausgeführt. Ist der richtige Code zur Behandlung der Ausnahme nicht verfügbar, wird sie als unbehandelt („unhandled“) betrachtet. In diesem Fall wird die Kontrolle über den Call Stack weitergegeben, bis sie den Einstiegspunkt der Applikation erreicht. An diesem Punkt wird die Runtime beendet und eine entsprechende Fehlermeldung ausgegeben.
Nachdem wir uns den eingangs beschriebenen Overhead nun en Detail angesehen haben, werfen wir einen Blick auf einige praktische Strategien, um Exceptions in C# zu vermeiden.
Exceptions mit dem Result Pattern vermeiden
Eine gute allgemeine Strategie um Exception Handling Code zu vermeiden, bietet das Result Pattern. Dieses können Sie in Ihren C#-Code über eine generische Klasse implementieren, die das Ergebnis einer spezifischen Operation kapselt.
Wenn ein Fehler in Ihrer App auftritt, können Sie dieses Pattern nutzen, um ein Ergebnisobjekt zurückzugeben, statt eine Exception zu werfen. Das gewährleistet simpleren, saubereren und leichter zu pflegenden Code.
Die nachfolgende Klasse namens Result stellt das Ergebnis einer Operation dar.
public class Result
{
public bool IsSuccess { get; }
public T Value { get; }
public string ErrorMessage { get; }
private Result(T value, string errorMessage, bool isSuccess)
{
Value = value;
ErrorMessage = errorMessage;
IsSuccess = isSuccess;
}
public static Result Success(T value) =>
new Result(value, null, true);
public static Result Failure(string errorMessage) =>
new Result(default(T), errorMessage, false);
}
Die Klasse enthält zwei Methoden, nämlich Success und Failure. Während erstere einen Wert zurückgibt, gibt zweitere eine Fehlermeldung zurück.
Die folgende Methode zeigt, wie Sie die Result-Klasse verwenden können, um Ausnahmen in Ihrem Code zu vermeiden.
public Result DivideNumbers(int x, int y)
{
if (x == 0)
{
return Result.Failure(“Division by zero is not allowed.”);
}
int result = x / y;
return Result.Success(result);
}
Der folgende Codeschnipsel zeigt, wie Sie das Ergebnismuster mit der DivideNumbers-Methode verwenden können.
var result = Test.DivideNumbers(15, 5);
if (result.IsSuccess)
Console.WriteLine($”The result is: {result.Value}”);
else
Console.WriteLine($”Error occurred: {result.ErrorMessage}”);
Exceptions mit dem Try-Parse-Pattern vermeiden
Das Try-Parse-Pattern bietet ebenfalls eine gute Möglichkeit, um Exceptions in Ihrer Anwendung zu vermeiden. In C# wird dieses Pattern durch die TryParse-Methode repräsentiert, die Datentypen transformiert und einen booleschen Wert zurückgibt.
Wenn der Parsing-Prozess erfolgreich verläuft, ist der Output true, anderenfalls false. Dieses Pattern können Sie folgendermaßen nutzen, um Ausnahmen in Ihrem Code zu vermeiden.
String str = “1000”;
Boolean result = Int32.TryParse(str, out int n);
if (result == true)
Console.WriteLine($”{n}”);
else
Console.WriteLine(“Error in conversion”);
Exceptions mit Try*-Methoden vermeiden
Wenn Sie Datentypen konvertieren, sollten Sie das eben demonstrierte Try-Parse-Pattern nutzen. Ansonsten gibt es noch weitere Try-Methoden – beispielsweise TryGetValue.
Diese Methoden geben false zurück, wenn sie nicht erfolgreich waren. Der Output einer erfolgreichen Operation wird über einen Out-Parameter zurückgegeben. Das nachfolgende Code Snippet zeigt, wie Sie das erreichen.
int myValue;
if (dictionary.TryGetValue(“MyKey”, out myValue))
{
//TryGetValue is successful so you can proceed as usual
}
else
{
//TryGetValue is unsuccessful so display
//or return an appropriate error message
}
Exceptions mit Common Condition Handling vermeiden
Sie können Ausnahmen auch vermeiden, indem Sie Bedingungen behandeln, die zur Runtime eine solche auslösen können. Bevor Sie die Verbindung zu einer Datenbank kappen, sollten Sie beispielsweise prüfen, ob ein Connection Object ein Nullwert ist oder bereits geschlossen ist. Das nachfolgende Snippet beschreibt diese Technik.
if (connection!= null && connection.State != ConnectionState.Closed)
{
connection.Close();
}
Wenn Sie Ihre Datenbank-Verbindungsinstanzen nicht auf Niullwerte prüfen oder eine bereits geschlossene Verbindung explizit mit einem Call der Close-Methode schließen, kann eine InvalidOperationException auftreten. Wie Sie diese in Ihrem Code behandeln sollten, zeigt folgender Code.
try
{
connection.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
}
Sich häufende Exceptions können sich wie bereits erwähnt negativ auf die Applikations-Leistung auswirken. Sie sollten deshalb Maßnahmen ergreifen, um Exception Handling innerhalb Ihres Codes zu vermeiden, wenn dieses leicht durch Logik ersetzt werden kann. Eine Faustregel ist dabei, Ihren Code auf gängige Error Conditions zu überprüfen. In allen anderen Fällen können Sie vom Result Pattern profitieren, das eine strukturierte Error-Handling-Methode bereitstellt.
Ein finaler Tipp: Exceptions sollten nur in Ausnahmefällen verwendet werden – also beispielsweise, wenn Sie bereits wissen, dass ein Fehler auftreten könnte. Und bitte: Verwenden Sie Ausnahmen nie, um den Control Flow einer Applikation zu managen. (fm)
Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!