Sollte TryFoo jemals eine Ausnahme auslösen?

8

Ein gebräuchliches Muster in .NET Framework ist das TryXXX-Muster (ich weiß nicht, ob es das ist, was es wirklich heißt), in dem die aufgerufene Methode versucht, etwas zu tun, oder True zurückgibt, wenn es erfolgreich war False , wenn die Operation fehlgeschlagen ist. Ein gutes Beispiel ist die generische Methode Dictionary.TryGetValue .

Die Dokumentation für diese Methoden besagt, dass sie keine Ausnahmen auslösen werden: Dieser Fehler wird im Rückgabewert der Methode gemeldet. Alles gut so weit.

Ich habe kürzlich zwei verschiedene Umstände festgestellt, bei denen .NET Framework-Methoden, die das TryXXX-Muster implementieren, Ausnahmen ausgelöst haben. Siehe Fehler im System.Random-Konstruktor? und Uri.TryCreate wirft UriFormatException? für Details.

In beiden Fällen hat die Methode TryXXX andere Methoden aufgerufen, die unerwartete Ausnahmen ausgelöst haben und diese Ausnahmen maskiert wurden. Meine Frage: Durchbricht dies den impliziten Vertrag, keine Ausnahmen zu werfen?

Anders gesagt: Wenn Sie TryFoo schreiben würden, würden Sie garantieren, dass Ausnahmen nicht entkommen können, indem Sie es so schreiben?

%Vor%

Es ist verlockend, denn das garantiert, dass keine Ausnahmen ausbleiben und damit der implizierte Vertrag eingehalten wird. Aber es ist ein Bug-Hider.

Mein Bauchgefühl sagt meiner Erfahrung nach, dass das eine wirklich schlechte Idee ist. Aber wenn TryFoo einige Ausnahmen zulässt, dann sagt es eigentlich: "Ich werde keine Ausnahmen werfen, mit denen ich umgehen kann", und dann ist die ganze Idee des Vertrags "Ich werde keine Ausnahmen werfen" aus dem Fenster geworfen.

Also, was ist deine Meinung? Sollte TryFoo alle Ausnahmen behandeln, oder nur diejenigen, die erwartet werden? Was ist deine Argumentation?

    
Jim Mischel 22.07.2009, 17:24
quelle

12 Antworten

5

Es kommt darauf an. TryParseFoo sollte alle Parsing-Exceptions für Dinge wie null oder falsch formatierte Zeichenfolgen abfangen. Aber es gibt auch nicht verwandte Ausnahmen ( ThreadAbortException , sagen wir), die nicht automatisch gelöscht werden sollten.

In Python (ertragen Sie mit mir), können Sie eine Menge Probleme verursachen, indem Sie KeyboardInterrupt exceptions abfangen, was im Grunde bedeutet, dass Ihr Programm läuft, auch wenn Sie Ctrl-C ausschalten.

Meine Faustregel ist, dass Sie TryFoo verwenden sollten, wenn Sie Foo verwenden möchten, ohne Ihre Eingaben zu bereinigen. Wenn TryFoo Fehler feststellt, die nicht mit Foo Ihrer Eingabe zusammenhängen, ist es zu aggressiv.

    
ojrac 22.07.2009, 17:32
quelle
5

Es ist keine Garantie, keine Ausnahmen auszulösen. Betrachten Sie dieses triviale Beispiel auf Dictionary ...

%Vor%

Ich habe einen Null-Schlüssel gesendet, ich bekomme eine NullArgumentException. Reflektor zeigt den Code so ...

%Vor%

In meinen Augen ist die Bedeutung des Vertrags: "Wenn das, was Sie versucht haben, gescheitert ist, werde ich keine Ausnahme werfen", aber das ist weit entfernt von "Ich werde keine Ausnahmen werfen." Da du wahrscheinlich kein Framework schreibst, könntest du mit so etwas Kompromisse machen ...

%Vor%

... und den TryXXX-Fehlerfall mit diesem catch-Block nicht behandeln (Sie haben also keine Menge trivialer Log-Ganzzahlen). In diesem Fall werden Sie zumindest die Art des Fehlers enthüllen und ihn während des Entwickelns identifizieren. Zeit sowie Notiz zur Laufzeit.

    
JP Alioto 22.07.2009 17:42
quelle
3

Ich glaube nicht, dass TryFoo keine Ausnahmen auslösen darf. Ich glaube, der Vertrag von TryFoo ist, dass ungültige Eingaben als normaler und nicht als außergewöhnlicher Fall behandelt werden. Das heißt, es wird keine Ausnahme ausgelöst, wenn es aufgrund ungültiger Eingaben seine Aufgabe nicht erfüllt. Wenn es aus einem anderen Grund fehlschlägt, sollte eine Ausnahme ausgelöst werden. Meine Erwartung nach TryFoo gibt zurück, dass entweder meine Eingabe verarbeitet wurde oder meine Eingabe ungültig war . Wenn keines dieser Dinge wahr ist, möchte ich eine Ausnahme, also weiß ich davon.

    
Nick Lewis 22.07.2009 17:40
quelle
2

Die beiden anderen StackOverflow-Fragen, die Sie verlinkt haben, scheinen echte Fehler zu sein. Derjenige, bei dem Uri.TryCreate eine UriFormatException zum Beispiel auslöst, ist definitiv ein Einbruch der Vertrag, da die MSDN-Dokumentation für diese Methode explizit angibt:

  

Es wird keine Ausnahme ausgelöst, wenn der URI nicht erstellt werden kann.

Der andere, TryDequeue, kann ich keine Dokumentation finden, aber es scheint auch ein Fehler zu sein. Wenn die Methoden TryXXX genannt wurden, aber die Dokumentation explizit angibt, dass sie Ausnahmen auslösen können, dann haben Sie möglicherweise einen Punkt.

Im Allgemeinen gibt es zwei Versionen dieser Art von Methode, eine, die Ausnahmen auslöst und eine, die dies nicht tut. Zum Beispiel Int32.Parse und Int32.TryParse . Der Punkt der TryXXX -Methode besteht nicht darin, Ausnahmen zu verstecken. Es ist für Situationen, in denen das Scheitern nicht wirklich eine außergewöhnliche Bedingung ist. Wenn Sie beispielsweise eine Zahl von der Konsole aus einlesen und solange versuchen möchten, bis eine korrekte Zahl eingegeben wurde, möchten Sie keine Ausnahmen mehr abfangen. Wenn Sie einen Wert aus einem Wörterbuch erhalten möchten, aber es ist ein ganz normaler Fall, in dem es möglicherweise nicht existiert, verwenden Sie TryGetValue .

Zusammenfassung : TryXXX sollte keine Ausnahmen auslösen und auch keine Ausnahmen ausblenden. Es ist für Fälle, in denen es normal und nicht außergewöhnlich ist, zu versagen, und Sie möchten diesen Fall ohne den Overhead und die Bemühung, eine Ausnahme zu fangen, leicht feststellen. Normalerweise wird auch eine Nicht-TryXXX-Methode bereitgestellt, die Ausnahmen bereitstellt, für Fälle, in denen es außergewöhnlich ist.

    
IRBMe 22.07.2009 17:36
quelle
1

Sie sollten fast nie alle Ausnahmen erfassen. Wenn Sie TryXXX mit einem try/catch -Paar um XXX implementieren müssen (anstatt das bevorzugte, XXX in Bezug auf TryXXX implementiert und ein falsches Ergebnis zu liefern), fangen Sie nur die bestimmten erwarteten Ausnahmen ab .

Es ist OK für TryXXX zu werfen, wenn ein semantisch anderes Problem auftritt, wie zum Beispiel ein Programmierfehler (vielleicht Null, wo es nicht erwartet wird).

    
Barry Kelly 22.07.2009 17:29
quelle
1

Das hängt davon ab, was die Methode versucht.

Wenn die Methode beispielsweise einen Zeichenfolgenwert aus einem Datenleseprogramm liest und versucht, ihn in eine Ganzzahl zu zerlegen, sollte sie keine Ausnahme auslösen, wenn das Parsing fehlschlägt. Sie sollte jedoch eine Ausnahme auslösen, wenn der Datenleser nicht lesen kann die Datenbank oder wenn der Wert, den Sie lesen, keine Zeichenfolge ist.

Die Methode sollte dem allgemeinen Prinzip zum Abfangen von Ausnahmen folgen, dass Sie nur das fangen sollten, mit dem Sie umgehen können. Wenn eine völlig andere Ausnahme auftritt, sollten Sie diese Blase mit anderem Code umgehen, der damit umgehen kann.

    
Guffa 22.07.2009 17:32
quelle
1

Die Idee hinter TryXXX ist, dass es keinen bestimmten Ausnahmetyp auslöst, der normalerweise auftritt, wenn Sie einfach den entsprechenden XXX Aufruf aufrufen - der Konsument der Methode weiß, dass er jede Ausnahme ignoriert, die normalerweise passieren würde auftreten.

  

Es ist verlockend, denn das garantiert, dass keine Ausnahmen entgehen und den impliziten Vertrag einhalten.

Dies ist nicht vollständig richtig - es gibt ein paar Ausnahmen, dass ein try/catch (z. B. OutOfMemoryException , StackOverflowException , usw.) entkommen kann.

    
Andrew Hare 22.07.2009 17:27
quelle
1
  

Aber wenn TryFoo einige Ausnahmen entkommen lässt, dann sagt es wirklich: "Ich werde keine Ausnahmen werfen, mit denen ich umgehen kann" und dann die ganze Idee des Vertrags "Ich werde keine Ausnahmen werfen" wird aus dem Fenster geworfen.

Ich denke, du hast es selbst gesagt:)

TryXXX sollte nur garantieren, dass es keine Ausnahmen auslöst, mit denen es umgehen kann. Wenn es nicht mit einer bestimmten Ausnahme umgehen kann, warum sollte es es fangen? Das gleiche gilt für Sie - Wenn Sie nicht wissen, was zu tun ist, warum sollten Sie es fangen? BadAllocExceptions zum Beispiel, naja, (normalerweise) nicht viel zu tun, außer um sie in Ihrer Haupt-try / catch-Block zu fangen zeigen einige Fehlermeldung und versuchen, die App anmutig zu schließen ..

    
cwap 22.07.2009 17:35
quelle
1

Das Abfangen und Schlucken aller Ausnahmen, wie in Ihrem Beispiel, ist im Allgemeinen eine schlechte Idee. Einige Ausnahmen, vor allem ThreadAbortException und OutOfMemoryException sollten nicht verschluckt werden. Eigentlich kann der erste nicht geschluckt werden und wird am Ende des Catch-Blocks automatisch wieder aufgetaut, aber trotzdem.

Phil Haack hat einen Blogeintrag zu genau diesem Thema.

    
Martin Liversage 22.07.2009 17:37
quelle
1
  

Sagen Sie es anders, wenn Sie TryFoo schreiben, würden Sie garantieren   dass Ausnahmen nicht entkommen können, durch   schreib es so?

Nein, ich würde keine Ausnahmen in meiner TryFoo-Methode verschlucken. Foo hängt von TryFoo ab, nicht umgekehrt. Da Sie das generische Dictionary erwähnt haben, das eine GetValue- und eine TryGetValue-Methode hat, würde ich meine Dictionary-Methoden wie folgt schreiben:

%Vor%

Also hängt GetValue von TryGetValue ab und löst eine Ausnahme aus, wenn TryGetValue false zurückgibt. Dies ist viel besser als GetValue von TryGetValue aufrufen und die resultierende Ausnahme verschlucken.

    
Juliet 22.07.2009 17:35
quelle
1

Denken Sie zuerst daran, was eine Ausnahme an erster Stelle bedeutet:

Eine Ausnahme bedeutet, dass Ihre Methode nicht so funktioniert wie der Name sagt.

Der Grund, warum wir das TryFoo -Muster in erster Linie implementieren, ist, dass das Auslösen von Exceptions teuer ist, und zusätzlich, wenn Sie den Debugger beim ersten Auslösen einer Exception bitten zu brechen, kann es sehr sein ärgerlich . Wenn Sie also eine Methode mit einem besonders häufigen Fehlermodus haben, z. B. Int32.Parse , die ArgumentException auslöst, wenn ungültige Eingaben eingegeben wurden, ist dies problematisch.

Die Idee hinter dem TryFoo -Muster ist, dass Ihre Methode in der Lage sein sollte, einen besonders häufigen Fehlermodus anzugeben, ohne eine Ausnahme auszulösen. Folglich sollten Sie niemals dies mit folgenden Worten umsetzen:

%Vor%

, da dies den gesamten Zweck des TryFoo -Musters an erster Stelle unterläuft.

Was das Auslösen von Ausnahmen betrifft, lautet die Antwort: Ja, es kann vorkommen, dass Ihre TryFoo -Methode eine Ausnahme auslösen muss. Insbesondere: Wenn die Methode aus anderen Gründen als dem besonders häufig vorkommenden fehlschlägt . Beispiele hierfür sind möglicherweise ein OutOfMemoryException oder ein Stapelüberlauf oder eine erwartete Assembly, die nicht bereitgestellt wurde oder bei der ein externer Webdienst nicht verfügbar ist. Diese Fehlermodi zeigen an, dass etwas Aufmerksamkeit erfordert und nicht ignoriert werden sollte.

    
jammycakes 29.08.2012 14:30
quelle
0

Ich habe einmal an einem großartigen Vortrag von jemandem teilgenommen, der sagte, er sei an der Ausarbeitung der Ausnahmebehandlungsstrategie für die BCL beteiligt. Leider habe ich seinen Namen vergessen und kann meine Notizen nicht finden.

Er beschrieb die Strategie so:

  1. Methodennamen müssen Verben sein, die die von der Methode durchgeführte Aktion beschreiben.
  2. Wenn die durch den Namen beschriebene Aktion aus irgendeinem Grund nicht stattfindet und eine Ausnahme ausgelöst werden muss.
  3. Wo möglich, sollte eine Möglichkeit zum Testen und Vermeiden einer bevorstehenden Ausnahme bereitgestellt werden. Z.B. Das Aufrufen von File.Open (Dateiname) löst eine Ausnahme aus, wenn die Datei nicht existiert, aber wenn Sie File.Exists (Dateiname) zuerst aufrufen, können Sie dies (die meiste Zeit) vermeiden.
  4. Wenn nachweisbare Gründe vorliegen (z. B. Leistung in einem häufigen Fall), kann eine zusätzliche Methode hinzugefügt werden, rufen Sie TryXXX auf, wobei XXX der Name der ursprünglichen Methode ist. Diese Methode sollte in der Lage sein, einen einzelnen gemeinsamen Fehlermodus zu handhaben und muss einen Booleschen Wert zurückgeben, um Erfolg oder Misserfolg anzuzeigen.

Der interessante Punkt hier war # 4. Ich erinnere mich klar daran, dass er den single failure mode Teil der Richtlinien angegeben hat. Andere Fehler sollten immer noch Ausnahmen auslösen.

Übrigens sagte er auch, dass das CLR-Team ihm gesagt hat, dass .Net-Ausnahmen langsam sind, weil sie auf SEH implementiert sind. Sie sagten auch, dass es keine besondere Notwendigkeit gebe, dass sie auf diese Weise implementiert würden (abgesehen von Zweckdienlichkeit), und wenn sie jemals in die Top 10 der realen Leistungsprobleme für echte Kunden eindringen würden, würden sie darüber nachdenken, sie wieder zu implementieren sei schneller!

    
Swythan 03.08.2009 09:01
quelle

Tags und Links