Es gibt eine Zeile, die in fast jedem gewachsenen Projekt steht und die mehr Zeit gekostet hat als jede andere:
try
{
VerarbeiteAuftrag(auftrag);
}
catch (Exception)
{
// ignorieren
}Was hier passiert: Das Programm lĂ€uft weiter, als wĂ€re alles gut gegangen. Der Auftrag wurde nicht verarbeitet, aber niemand erfĂ€hrt es â kein Log, kein Alarm, kein RĂŒckgabewert. Der Fehler taucht Wochen spĂ€ter als «die Zahlen stimmen nicht» wieder auf, und dann sucht ihn jemand an der Stelle, wo die Zahlen falsch sind. Nicht hier.
Der eigentliche Schaden ist nicht der verlorene Auftrag. Es ist die verlorene Information: Der Zeitpunkt, an dem das Problem noch bekannt war, ist vorbei.
Die einzige vertretbare Form
Ein leerer catch-Block braucht eine BegrĂŒndung im Code â und zwar eine, die erklĂ€rt, warum das Weiterlaufen hier richtig ist. Etwa: «AufrĂ€umen einer temporĂ€ren Datei; schlĂ€gt es fehl, rĂ€umt der nĂ€chtliche Lauf auf.» Ohne diese BegrĂŒndung ist es kein Entwurf, sondern eine unterdrĂŒckte Frage.
catch (Exception) ist fast immer zu breit
Auch ein catch, das protokolliert, fÀngt zu viel, wenn es Exception fÀngt:
try
{
var kurs = await _api.HoleWechselkursAsync("CHF", "EUR");
return betrag * kurs;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Kurs nicht verfĂŒgbar, nehme letzten bekannten");
return betrag * _letzterKurs;
}Gedacht war: Wenn der Kursdienst nicht erreichbar ist, nimm den letzten Wert. TatsÀchlich gefangen wird auch:
- eine
NullReferenceExceptionaus einem Programmierfehler inHoleWechselkursAsync, - eine
OutOfMemoryException, - eine
TaskCanceledException, weil der Benutzer abgebrochen hat.
In allen drei FĂ€llen rechnet die Anwendung fröhlich mit einem veralteten Kurs weiter â und ein echter Bug ist zu einer Warnung im Log geworden, die niemand liest.
Fang, was du erwartest:
catch (HttpRequestException ex)
{
_logger.LogWarning(ex, "Kursdienst nicht erreichbar, nehme letzten bekannten");
return betrag * _letzterKurs;
}
catch (TaskCanceledException)
{
throw; // Abbruch ist kein Fehler des Kursdienstes
}throw; und throw ex; sind nicht dasselbe
Das ist der subtilste Fehler in diesem Modul, und er kostet regelmÀssig eine Debugging-Sitzung:
Der Stacktrace
`throw ex;`
catch (SqlException ex)
{
_logger.LogError(ex, "DB-Fehler");
throw ex; // â setzt den
// Stacktrace zurĂŒck
}
// Im Log steht als Ursprung
// diese catch-Zeile â nicht die
// Stelle, an der es knallte.`throw;`
catch (SqlException ex)
{
_logger.LogError(ex, "DB-Fehler");
throw; // â behĂ€lt den
// ursprĂŒnglichen Trace
}
// Im Log steht die Methode,
// in der der Fehler entstand.throw ex; wirft dieselbe Instanz, ĂŒberschreibt aber ihren Stacktrace ab dieser Zeile. Die gesamte Vorgeschichte ist weg â genau die Information, die du beim Lesen des Logs brauchst.
Wenn du gar nichts damit tust: fang es nicht
Ein try/catch, das nur protokolliert und dann throw; macht, ist meistens ĂŒberflĂŒssig â ein globaler Handler protokolliert ohnehin, und zwar an einer Stelle statt an vierzig. Fang eine Ausnahme, wenn du an dieser Stelle etwas entscheiden kannst.
Ausnahmefilter
C# hat fĂŒr den Fall «fangen, aber nur unter einer Bedingung» eine eigene Syntax â und sie ist besser als ein if im catch-Block:
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
{
await WarteUndVersucheErneutAsync();
}Warum `when` besser ist als ein `if` im catch
Trifft die Bedingung nicht zu, wird der Block gar nicht erst betreten â der Stack bleibt unangetastet. Bei einem if im catch mit anschliessendem throw; ist der Stack bereits abgewickelt, was das Debuggen erschwert und die AusfĂŒhrung verlangsamt.