Häufig findet man TryCatch-Verwendungen wie diese:
Was soll der TryCatch hier bezwecken?
Wenn ein Fehler auftritt, denselben melden.
Hmm - niemand meldet Fehler besser als die IDE selbst, wenn man sie nicht (durch TryCatch) daran hindert:
Meldung, Codestop, Fehlerzeile gelb markieren, Werte aller Variablen im Lokal-Fenster einsehbar - was will man mehr? (siehe auch: Debuggen mit VisualStudio)
Wenns ums Melden geht: Selbst die Release-Version einer Anwendung meldet Fehler mit bestmöglicher Differenzierung (allerdings beim sog. "Absturz").
Aber das Programm soll doch nicht abstürzen!
Wie jetzt - soll es etwa weiterlaufen, wenn eine Daten-Anforderung (oder ein vergleichbarer Ressourcen-Zugriff) fehlschlägt? - Es muss crashen!
Es wird eh crashen, dann nämlich, wenn die nicht gelieferten Daten verarbeitet werden sollen.
Ja, und dann suchste den Fehler an der falschen Stelle.
Ich ernte enorm viel Widerspruch für diese doch unwiderlegliche Analyse eines so konkreten wie alltäglichen Beispiels, das zeigt, wie man sich durch unsachgemäße TryCatch-Verwendung selbst ins Knie schießt.
Dass das Beispiel gradezu schmerzhaft alltäglich ist, ist schnell belegt: Einfach mal "Catch" in die Forum-Suche eingeben - ich wette, 90% der Treffer enthalten Codebeispiele, und von diesen Codebeispielen wiederum 95% vergleichbare oder gar schlimmere "Code-Katastrophen" wie die hier vorgeführte.
Der Widerspruch hingegen kommt üblicherweise von absolut kompetenter Seite, von Leuten, die TryCatch vernünftig einzusetzen wissen, und er wird sehr massiv vorgetragen, mit leider der polemischen Wirkung, dass ein Anfänger leicht den Eindruck gewinnt, seine Code-Katastrophen seien ja doch ganz in Ordnung.
Meine Empfehlung "vermeide TryCatch" wird offenbar missverstanden als "TryCatch ist dirty" (wie etwa Option Strict Off, oder Goto).
Das ist aber nicht meine Aussage.
Im Grunde ist meine Aussage nichts weiter, als die Trivial-Empfehlung "Fange nur die Exceptions, die du auch behandeln kannst"
Über diese Richtlinie besteht glaub ein uneingeschränkter Konsenz - nur ich empfehle, sie auch wirklich ernst zu nehmen.
Hinzu fügt "Avoid TryCatch" noch die ebenfalls triviale Empfehlung, sich vorrangig darauf zu konzentrieren, halt sicher zu programmieren, damit unzulässige Zustände gar nicht erst entstehen. Hierbei sind Exceptions eine entscheidende Hilfe - solange man sie sich nicht wegfängt.
ZB. kann ich mir derzeit keinen Grund vorstellen, jemals eine NullReferenceException zu fangen. Eine NullReferenceException reklamiert die Verwendung einer Objekt-Variable, die garnicht initialisiert ist - also es wird auf ein Objekt zugegriffen, dasses nicht gibt.
Hier liegt nach meiner Erfahrung zu 100% ein Programmier-Fehler vor, der behoben werden muß (zur Entwicklungs-Zeit), nicht behandelt (zur Laufzeit).
Vorrangig wäre hier logisch zu gewährleisten, dass die Variable eben sicher initialisiert ist, wenn sie verwendet wird - nachrangig muß man halt auf Is Nothing testen, wenn die Initialisierung nicht in jedem Fall gewährleistet werden kann.
Sowas meint "Avoid TryCatch" - eben Catchen vermeiden, und das ist oft einfacher als man denkt.
Etwa die Behandlung einer FileNotFoundException baut meist gleich die nächste Fehlerquelle ein: Nämlich dann kann immer noch die DirectoryNotFoundException auftreten - was bei einem einfachen Test auf FileInfo.Exists() nicht passiert wäre :P.
Auch ist in meinen Augen die Rede von "Ausnahmen" eine fragwürdige Beschönigung, denn es sind Fehler, die auftreten.
Von "Ausnahmen" sollte man IMO erst sprechen, wenn eine erfolgreiche, sichere Behandlung implementiert ist, und das ist meist ziemlich schwierig, gelegentlich auch unmöglich.
Nochmal obiges Beispiel: Was soll eine Datenbank-Anwendung denn machen, wenn die DB nicht zugreifbar ist?
Eine andere Datenbank nehmen? leere Tabellen anzeigen?
Ob nun gecatcht wird oder nicht - die Anwendung wird beenden, denn mit leeren Tabellen kann der User auch nichts anfangen.
Empfehlenswert wäre hier allein der "Graceful Exit", d.h., der User bekommt eine leidlich informative Meldung, der Fehler wird in einem Log protokolliert, und dann wird runtergefahren.
Aber "Graceful Exit" programmiert man besser nicht mit TryCatch, sondern man kann an zentraler Stelle das Application_UnhandledException-Event behandeln.
Aber mal grundsätzlich:
Was ist eine Exception?
Eine Exception meldet einen Fehler, wenn eine Operation nicht durchgeführt werden kann. Letztendlich wird auch eine ungecatchte Exception behandelt, denn in jedem Fall erhält der User oder vorzugsweise bereits der Entwickler eine möglichst differenzierte Fehlermeldung, bevor die Anwendung beendet.
Dass die Anwendung beendet ist eine höchst sinnvolle Behandlung, die verhindert, dass FolgeFehler auftreten, die dem Entwickler das Debuggen enorm erschweren, oder dem User gar Funktionalität vorgaukeln, die nicht vorhanden ist (meist aber stürzt die Anwendung eben beim nächstbesten FolgeFehler ab).
Ein Absturz ist zwar unschön, aber doch eine absolut zuverlässige Schadensbegrenzung, und letzteres hat Vorrang vor einer Anwendung, die vlt. noch schön anzeigt, aber was ganz anneres treibt als der User denkt. Zunächstmal ist diese Schadensbegrenzung also immer eingebaut, und ich empfehle, sich strikt daran zu halten, sie nur dann zu deaktivieren, wenn eine bessere Schadensbegrenzung auch tatsächlich implementiert ist.
Exception-Throwing
In meiner beschränkten Vorstellungswelt dienen Exceptions in erster Linie der Fehlermeldung, als Vorraussetzung der Fehlerbehebung - sind also vorrangig dazu da, geworfen zu werden, nicht zum fangen.
Hier habe ich etwa eine Function, die Minimum und Maximum verschiedenster Auflistungen ermittelt
Die Exception bedarf sicher keiner Erläuterung
Exception-Throwing braucht man nicht soo oft (allerdings doch häufiger als Catching), weil wenn man nichts unternimmt, fliegt meist auch eine
Die Standard-Fehlerbehandlung
Im Grunde gibt es keine unbehandelte Exception, denn wenn kein TryCatch hingeschrieben, tritt die Standard-Fehlerbehandlung ein: Während der Entwicklungszeit stoppt der Code an der Zeile, wo der Fehler auftritt, und die IDE des VisualStudios markiert die Zeile, gibt die Fehlermeldung aus, und stellt mit LokalFenster, Aufrufeliste, Schnellüberwachung und Intellisense alle nur möglichen Mittel bereit, den aktuellen Zustand des Programmes genau zu untersuchen.
Bei einer Release gibts nur die Fehlermeldung, und das Programm wird anschließend beendet, ehe es instabil weiterläuft, und womöglich größeren Schaden anrichtet.
Was macht TryCatch?
TryCatch deaktiviert die Standard-Fehlerbehandlung. Es wird kein Fehler mehr angezeigt, und die Anwendung läuft weiter, als sei nichts gewesen.
Damit hat sich der Entwickler für den gesamten Zeitraum der weiteren Entwicklung die bestmögliche Debug-Unterstützung selbst weggenommen. Darüberhinaus hat er, solange er keine absolut zuverlässige Ausnahmebehandlung implementiert hat, einen Haufen Folgefehler quasi neu eingebaut, und Folgefehler sind deshalb besonders fies zu debuggen, weil sie eiglich gar keine Fehler sind - der wirkliche Fehler liegt ja ganz woanders.
Wann sind TryCatchens sinnvoll?
(na, wenn man sie nicht vermeiden kann ;)) Im Ernst: Es gibt Exceptions, deren Auftreten man nicht durch einfache Tests vorhersehen kann. Als Beispiel nenne ich die UnAuthorisizedException, wenn man auf ein Directory zugegreifen will, für das keine Rechte bestehen. Auch hier sehe man vorrangig nach, ob nicht ein Programmier-Fehler im weiteren Sinne vorliegt, etwa dass die Anwendung nicht mit erforderlichen Rechten gestartet wurde, oder dass DatenDateien in ungeeigneten Verzeichnissen abgelegt sind.
Aber etwa eine rekursive DateiSuche kann leicht zB. auf ein verstecktes SystemVerzeichnis stoßen. Hier die Exception einfach zu verschlucken "swallow-Catching" kann man rechtfertigen, wenn man sagt, dass diese Dateien nicht gefunden und angezeigt werden, weil sie eben nicht gefunden werden dürfen.
Kann vlt. aber auch ein Problem sein: wenn etwa der belegte Plattenplatz ermittelt werden soll, wird die Methode schlicht falsche Ergebnisse liefern.
Eine andere unproblematische Schluck-Behandlung kann man bei Verwendung expliziter Thread-Objekte implementieren: So einen Thread kann man einfach mit Thread.Abort() abschießen. Dann fliegt innerhalb des Threads die ThreadAbortedException, und wenn man die schluckt, beendet der Thread kommentarlos, und das ist ja in diesem Fall das beabsichtigte Verhalten.
Unvermeidbar ist Catching meist auch beim Zugriff aufs Internet: Ein Server, dem die Verbindung zu einem Client abreißt, muß die Exception behandeln, mindestens indem er den Client aus seiner Client-Auflistung entfernt oder als unerreichbar markiert. Einfach schlucken hilft hier aber garnix, sondern baut nur FolgeFehler ein. (Also Finger weg vom TryCatch, bis die Lösung in diesem Einzelfall eingerichtet ist!).
Das wird aber ein ausgewogenes Tutorial - ich widersprech mir andauernd selber ;): Hmm - ich sehe grade, dasses in meim VersuchsChat ohne TryCatch geregelt ist, wenn eine Verbindung abreißt. (Ein Client geht Offline, bzw. der Server schmeißt den Client aus seiner Auflistung, wenn man im TaskManager eine Seite einer Verbindung abschießt)
Vermeidungs- und Behandlungs-Strategien
beachte: Nicht jede Strategie ist auf jedes Problem anwendbar
ReThrowing
ist Exception-Throwing innerhalb eines Catch-Abschnitts. Es findet keine Fehlerbehandlung statt, es besteht aber auch keine Gefahr von Folgefehlern. Sinn von ReThrowing ist, eine Exception mit zusätzlichen Informationen anzureichern. Unsinniges Beispiel:
Die ArgumentException liefert nun einen "spezifischen" Text, und enthält als InnerException entweder eine IndexOutOfRangeException, falls der String zu kurz ist, oder eine der 3 Exceptions, die beim Parsen nach Double auftreten können. Braucht man nur sehr selten, ist aber wichtig zu wissen, denn im Framework werden doch recht häufig Exceptions geworfen, die InnerExceptions enthalten, und falls dem so ist, muß man dort natürlich als erstes nachgucken.
Was soll der TryCatch hier bezwecken?
Wenn ein Fehler auftritt, denselben melden.
Hmm - niemand meldet Fehler besser als die IDE selbst, wenn man sie nicht (durch TryCatch) daran hindert:
Meldung, Codestop, Fehlerzeile gelb markieren, Werte aller Variablen im Lokal-Fenster einsehbar - was will man mehr? (siehe auch: Debuggen mit VisualStudio)
Wenns ums Melden geht: Selbst die Release-Version einer Anwendung meldet Fehler mit bestmöglicher Differenzierung (allerdings beim sog. "Absturz").
Aber das Programm soll doch nicht abstürzen!
Wie jetzt - soll es etwa weiterlaufen, wenn eine Daten-Anforderung (oder ein vergleichbarer Ressourcen-Zugriff) fehlschlägt? - Es muss crashen!
Es wird eh crashen, dann nämlich, wenn die nicht gelieferten Daten verarbeitet werden sollen.
Ja, und dann suchste den Fehler an der falschen Stelle.
Ich ernte enorm viel Widerspruch für diese doch unwiderlegliche Analyse eines so konkreten wie alltäglichen Beispiels, das zeigt, wie man sich durch unsachgemäße TryCatch-Verwendung selbst ins Knie schießt.
Dass das Beispiel gradezu schmerzhaft alltäglich ist, ist schnell belegt: Einfach mal "Catch" in die Forum-Suche eingeben - ich wette, 90% der Treffer enthalten Codebeispiele, und von diesen Codebeispielen wiederum 95% vergleichbare oder gar schlimmere "Code-Katastrophen" wie die hier vorgeführte.
Der Widerspruch hingegen kommt üblicherweise von absolut kompetenter Seite, von Leuten, die TryCatch vernünftig einzusetzen wissen, und er wird sehr massiv vorgetragen, mit leider der polemischen Wirkung, dass ein Anfänger leicht den Eindruck gewinnt, seine Code-Katastrophen seien ja doch ganz in Ordnung.
Meine Empfehlung "vermeide TryCatch" wird offenbar missverstanden als "TryCatch ist dirty" (wie etwa Option Strict Off, oder Goto).
Das ist aber nicht meine Aussage.
Im Grunde ist meine Aussage nichts weiter, als die Trivial-Empfehlung "Fange nur die Exceptions, die du auch behandeln kannst"
Über diese Richtlinie besteht glaub ein uneingeschränkter Konsenz - nur ich empfehle, sie auch wirklich ernst zu nehmen.
Hinzu fügt "Avoid TryCatch" noch die ebenfalls triviale Empfehlung, sich vorrangig darauf zu konzentrieren, halt sicher zu programmieren, damit unzulässige Zustände gar nicht erst entstehen. Hierbei sind Exceptions eine entscheidende Hilfe - solange man sie sich nicht wegfängt.
ZB. kann ich mir derzeit keinen Grund vorstellen, jemals eine NullReferenceException zu fangen. Eine NullReferenceException reklamiert die Verwendung einer Objekt-Variable, die garnicht initialisiert ist - also es wird auf ein Objekt zugegriffen, dasses nicht gibt.
Hier liegt nach meiner Erfahrung zu 100% ein Programmier-Fehler vor, der behoben werden muß (zur Entwicklungs-Zeit), nicht behandelt (zur Laufzeit).
Vorrangig wäre hier logisch zu gewährleisten, dass die Variable eben sicher initialisiert ist, wenn sie verwendet wird - nachrangig muß man halt auf Is Nothing testen, wenn die Initialisierung nicht in jedem Fall gewährleistet werden kann.
Sowas meint "Avoid TryCatch" - eben Catchen vermeiden, und das ist oft einfacher als man denkt.
Etwa die Behandlung einer FileNotFoundException baut meist gleich die nächste Fehlerquelle ein: Nämlich dann kann immer noch die DirectoryNotFoundException auftreten - was bei einem einfachen Test auf FileInfo.Exists() nicht passiert wäre :P.
Auch ist in meinen Augen die Rede von "Ausnahmen" eine fragwürdige Beschönigung, denn es sind Fehler, die auftreten.
Von "Ausnahmen" sollte man IMO erst sprechen, wenn eine erfolgreiche, sichere Behandlung implementiert ist, und das ist meist ziemlich schwierig, gelegentlich auch unmöglich.
Nochmal obiges Beispiel: Was soll eine Datenbank-Anwendung denn machen, wenn die DB nicht zugreifbar ist?
Eine andere Datenbank nehmen? leere Tabellen anzeigen?
Ob nun gecatcht wird oder nicht - die Anwendung wird beenden, denn mit leeren Tabellen kann der User auch nichts anfangen.
Empfehlenswert wäre hier allein der "Graceful Exit", d.h., der User bekommt eine leidlich informative Meldung, der Fehler wird in einem Log protokolliert, und dann wird runtergefahren.
Aber "Graceful Exit" programmiert man besser nicht mit TryCatch, sondern man kann an zentraler Stelle das Application_UnhandledException-Event behandeln.
Aber mal grundsätzlich:
Was ist eine Exception?
Eine Exception meldet einen Fehler, wenn eine Operation nicht durchgeführt werden kann. Letztendlich wird auch eine ungecatchte Exception behandelt, denn in jedem Fall erhält der User oder vorzugsweise bereits der Entwickler eine möglichst differenzierte Fehlermeldung, bevor die Anwendung beendet.
Dass die Anwendung beendet ist eine höchst sinnvolle Behandlung, die verhindert, dass FolgeFehler auftreten, die dem Entwickler das Debuggen enorm erschweren, oder dem User gar Funktionalität vorgaukeln, die nicht vorhanden ist (meist aber stürzt die Anwendung eben beim nächstbesten FolgeFehler ab).
Ein Absturz ist zwar unschön, aber doch eine absolut zuverlässige Schadensbegrenzung, und letzteres hat Vorrang vor einer Anwendung, die vlt. noch schön anzeigt, aber was ganz anneres treibt als der User denkt. Zunächstmal ist diese Schadensbegrenzung also immer eingebaut, und ich empfehle, sich strikt daran zu halten, sie nur dann zu deaktivieren, wenn eine bessere Schadensbegrenzung auch tatsächlich implementiert ist.
Exception-Throwing
In meiner beschränkten Vorstellungswelt dienen Exceptions in erster Linie der Fehlermeldung, als Vorraussetzung der Fehlerbehebung - sind also vorrangig dazu da, geworfen zu werden, nicht zum fangen.
Hier habe ich etwa eine Function, die Minimum und Maximum verschiedenster Auflistungen ermittelt
VB.NET-Quellcode
- <Extension()> _
- Public Function Extremum(Of T, T2 As IComparable)(ByVal Subj As IEnumerable(Of T), ByVal Converter As Func(Of T, T2)) As MinMax(Of T)
- With Subj.GetEnumerator
- If Not .MoveNext Then Throw New ArgumentException( _
- "In leerer Auflistung kann kein Extremum gefunden werden")
- Dim MinMax = New T() {.Current, .Current}
- Dim Val = Converter(.Current)
- Dim Values = New T2() {Val, Val}
- While .MoveNext
- Val = Converter(.Current)
- If Val.CompareTo(Values(0)) < 0 Then
- MinMax(0) = .Current
- Values(0) = Val
- ElseIf Val.CompareTo(Values(1)) > 0 Then
- MinMax(1) = .Current
- Values(1) = Val
- End If
- End While
- Return New MinMax(Of T)(MinMax(0), MinMax(1))
- .Dispose()
- End With
- End Function
Exception-Throwing braucht man nicht soo oft (allerdings doch häufiger als Catching), weil wenn man nichts unternimmt, fliegt meist auch eine
Die Standard-Fehlerbehandlung
Im Grunde gibt es keine unbehandelte Exception, denn wenn kein TryCatch hingeschrieben, tritt die Standard-Fehlerbehandlung ein: Während der Entwicklungszeit stoppt der Code an der Zeile, wo der Fehler auftritt, und die IDE des VisualStudios markiert die Zeile, gibt die Fehlermeldung aus, und stellt mit LokalFenster, Aufrufeliste, Schnellüberwachung und Intellisense alle nur möglichen Mittel bereit, den aktuellen Zustand des Programmes genau zu untersuchen.
Bei einer Release gibts nur die Fehlermeldung, und das Programm wird anschließend beendet, ehe es instabil weiterläuft, und womöglich größeren Schaden anrichtet.
Was macht TryCatch?
TryCatch deaktiviert die Standard-Fehlerbehandlung. Es wird kein Fehler mehr angezeigt, und die Anwendung läuft weiter, als sei nichts gewesen.
Damit hat sich der Entwickler für den gesamten Zeitraum der weiteren Entwicklung die bestmögliche Debug-Unterstützung selbst weggenommen. Darüberhinaus hat er, solange er keine absolut zuverlässige Ausnahmebehandlung implementiert hat, einen Haufen Folgefehler quasi neu eingebaut, und Folgefehler sind deshalb besonders fies zu debuggen, weil sie eiglich gar keine Fehler sind - der wirkliche Fehler liegt ja ganz woanders.
Wann sind TryCatchens sinnvoll?
(na, wenn man sie nicht vermeiden kann ;)) Im Ernst: Es gibt Exceptions, deren Auftreten man nicht durch einfache Tests vorhersehen kann. Als Beispiel nenne ich die UnAuthorisizedException, wenn man auf ein Directory zugegreifen will, für das keine Rechte bestehen. Auch hier sehe man vorrangig nach, ob nicht ein Programmier-Fehler im weiteren Sinne vorliegt, etwa dass die Anwendung nicht mit erforderlichen Rechten gestartet wurde, oder dass DatenDateien in ungeeigneten Verzeichnissen abgelegt sind.
Aber etwa eine rekursive DateiSuche kann leicht zB. auf ein verstecktes SystemVerzeichnis stoßen. Hier die Exception einfach zu verschlucken "swallow-Catching" kann man rechtfertigen, wenn man sagt, dass diese Dateien nicht gefunden und angezeigt werden, weil sie eben nicht gefunden werden dürfen.
Kann vlt. aber auch ein Problem sein: wenn etwa der belegte Plattenplatz ermittelt werden soll, wird die Methode schlicht falsche Ergebnisse liefern.
Eine andere unproblematische Schluck-Behandlung kann man bei Verwendung expliziter Thread-Objekte implementieren: So einen Thread kann man einfach mit Thread.Abort() abschießen. Dann fliegt innerhalb des Threads die ThreadAbortedException, und wenn man die schluckt, beendet der Thread kommentarlos, und das ist ja in diesem Fall das beabsichtigte Verhalten.
Unvermeidbar ist Catching meist auch beim Zugriff aufs Internet: Ein Server, dem die Verbindung zu einem Client abreißt, muß die Exception behandeln, mindestens indem er den Client aus seiner Client-Auflistung entfernt oder als unerreichbar markiert. Einfach schlucken hilft hier aber garnix, sondern baut nur FolgeFehler ein. (Also Finger weg vom TryCatch, bis die Lösung in diesem Einzelfall eingerichtet ist!).
Das wird aber ein ausgewogenes Tutorial - ich widersprech mir andauernd selber ;): Hmm - ich sehe grade, dasses in meim VersuchsChat ohne TryCatch geregelt ist, wenn eine Verbindung abreißt. (Ein Client geht Offline, bzw. der Server schmeißt den Client aus seiner Auflistung, wenn man im TaskManager eine Seite einer Verbindung abschießt)
Vermeidungs- und Behandlungs-Strategien
beachte: Nicht jede Strategie ist auf jedes Problem anwendbar
- Fehler beheben - sollte man nicht denken, aber die meisten Exceptions treten einfach deshalb nicht auf, weil ordentlich programmiert wurde . Bereits gesagt: Eine NullReferenceException ist glaub immer zu beheben oder zu vermeiden - ein Fangen ist nie erforderlich.
- Vermeiden - zweitbeste Möglichkeit ist, fragliche Parameter ggfs. zunächst zu testen, bevor man sie ins Rennen schickt.
- Swallow - Das einfachste Behandlung bei unvermeidbarem Catching besteht darin, den Fehler diskret zu übergehen und zu tun, als sei nichts gewesen. "Schlucken" nennen die Engländer sowas - naja, jedem das seine
- Substitute - Vielleicht kann man Ersatz-Daten / Default-Einstellungen bereitstellen zur alternativen Benutzung
- ReTry - ziemlich anspruchsvoll ist, die mißlungene Operation mit veränderten Parametern zu wiederholen. Diese Strategie muß immer mit einer der anderen kombiniert sein, denn irgendwann muß auch die schönste Rumprobiererei mal ein Ende haben.
- Graceful Exit - Wenn garnix geht - dann geht halt nix. Das muß man dem User schonend beibringen, nachdem die Umstände des Scheiterns möglichst ausführlich und genau protokolliert sind, für spätere Fehler-Analysen.
Graceful Exit kann man auch als "Nicht-Behandlung" auffassen, und wird, wie oben schon erwähnt, besser ohne TryCatch implementiert.
ReThrowing
ist Exception-Throwing innerhalb eines Catch-Abschnitts. Es findet keine Fehlerbehandlung statt, es besteht aber auch keine Gefahr von Folgefehlern. Sinn von ReThrowing ist, eine Exception mit zusätzlichen Informationen anzureichern. Unsinniges Beispiel:
Dieser Beitrag wurde bereits 12 mal editiert, zuletzt von „ErfinderDesRades“ ()