Wie vermeide ich störende Ausnahmen?

21

Das Lesen des Artikels von Eric Lippert über Ausnahmen war definitiv ein Augenschmaus darüber, wie ich mit Ausnahmen umgehen sollte, sowohl als Produzent als auch als Verbraucher. Ich kämpfe jedoch immer noch darum, eine Richtlinie zu definieren, wie man lästige Ausnahmen vermeiden kann.

Speziell:

  • Angenommen, Sie haben eine Speichermethode, die fehlschlagen kann, weil a) jemand anderes den Datensatz vor Ihnen geändert hat oder b) der Wert, den Sie erstellen möchten, bereits vorhanden ist . Diese Bedingungen sind zu erwarten und nicht außergewöhnlich. Anstatt eine Ausnahme auszulösen, erstellen Sie eine Try-Version Ihrer Methode, TrySave, die einen Booleschen Wert zurückgibt, der angibt, ob das Speichern erfolgreich war. Aber wenn es fehlschlägt, wie wird der Verbraucher wissen, was das Problem war? Oder ist es am besten, eine Aufzählung mit dem Ergebnis "Ok / RecordAlreadyModified / ValueAlreadyExists" zurückzugeben? Mit integer.TryParse existiert dieses Problem nicht, da es nur einen Grund gibt, warum die Methode fehlschlagen kann.
  • Ist das vorige Beispiel wirklich eine ärgerliche Situation? Oder wäre es in diesem Fall die bevorzugte Methode, eine Ausnahme auszulösen? Ich weiß, dass dies in den meisten Bibliotheken und Frameworks, einschließlich des Entity-Frameworks, der Fall ist.
  • Wie können Sie entscheiden, wann Sie eine Try-Version Ihrer Methode erstellen möchten, anstatt im Voraus zu testen, ob die Methode funktioniert oder nicht? Ich folge derzeit diesen Richtlinien:
    • Wenn die Möglichkeit einer Racebedingung besteht, erstellen Sie eine Testversion. Dies verhindert, dass der Verbraucher eine exogene Ausnahme abfangen muss. Zum Beispiel in der zuvor beschriebenen Save-Methode.
    • Wenn die Methode zum Testen der Bedingung so gut wie alles tun würde, was die ursprüngliche Methode tut, erstellen Sie eine Try-Version. Zum Beispiel integer.TryParse ().
    • Erstellen Sie in jedem anderen Fall eine Methode zum Testen der Bedingung.
Mike
quelle
1
Ihr Beispiel für einen fehlgeschlagenen Speichervorgang ist keine wirklich ärgerliche Ausnahme. Es ist ganz normal und sollte wahrscheinlich nur eine Ausnahme sein.
S.Lott
@S.Lott: Was meinst du mit es ist ganz normal? Die Situation selbst oder eine Ausnahme in dieser Situation? Wie auch immer, ich stimme Ihnen zu, dass es nicht offensichtlich ist, ob dies tatsächlich eine ärgerliche Situation ist. Ich werde die Frage aktualisieren.
Mike
"Die Situation selbst oder das Auslösen einer Ausnahme in dieser Situation" Beide.
S.Lott

Antworten:

24

Angenommen, Sie haben eine Speichermethode, die fehlschlagen kann, weil a) jemand anderes den Datensatz vor Ihnen geändert hat oder b) der Wert, den Sie erstellen möchten, bereits vorhanden ist. Diese Bedingungen sind zu erwarten und nicht außergewöhnlich. Anstatt eine Ausnahme auszulösen, erstellen Sie eine Try-Version Ihrer Methode, TrySave, die einen Booleschen Wert zurückgibt, der angibt, ob das Speichern erfolgreich war. Aber wenn es fehlschlägt, wie wird der Verbraucher wissen, was das Problem war?

Gute Frage.

Die erste Frage, die mir in den Sinn kommt, lautet: Wenn die Daten bereits vorhanden sind, inwiefern ist das Speichern fehlgeschlagen ? Es hört sich sicher so an, als wäre es mir gelungen. Nehmen wir jedoch an, dass Sie tatsächlich viele verschiedene Gründe haben, warum eine Operation fehlschlagen kann.

Die zweite Frage, die mir in den Sinn kommt, lautet: Sind die Informationen, die Sie an den Benutzer zurückgeben möchten, umsetzbar ? Das heißt, werden sie auf der Grundlage dieser Informationen eine Entscheidung treffen?

Wenn die "Check Engine" -Lampe aufleuchtet, öffne ich die Motorhaube, stelle sicher, dass sich in meinem Auto ein Motor befindet, der nicht in Flammen steht, und bringe ihn in die Garage. Natürlich haben sie in der Garage alle Arten von speziellen Diagnosegeräten, die ihnen sagen, warum das Licht des Prüfmotors an ist, aber aus meiner Sicht ist das Warnsystem gut konstruiert. Es ist mir egal, ob das Problem darin besteht, dass der Sauerstoffsensor einen abnormalen Sauerstoffpegel im Brennraum aufzeichnet oder der Leerlaufdrehzahldetektor nicht angeschlossen ist oder was auch immer. Ich werde das Gleiche tun, nämlich jemand anderem das herausfinden lassen .

Interessiert es den Anrufer, warum das Speichern fehlgeschlagen ist? Werden sie etwas dagegen unternehmen, außer entweder aufzugeben oder es erneut zu versuchen?

Nehmen wir aus Gründen des Arguments an, dass der Aufrufer tatsächlich verschiedene Aktionen ausführen wird, je nachdem, warum die Operation fehlgeschlagen ist.

Die dritte Frage lautet: Ist der Fehlermodus außergewöhnlich ? Ich glaube , Sie könnten etwas verwirrend sein kann mit unexceptional . Ich würde mir vorstellen, dass zwei Benutzer versuchen, denselben Datensatz gleichzeitig zu ändern, und zwar in einer Ausnahmesituation, die jedoch möglich ist, und nicht in einer üblichen Situation.

Nehmen wir aus Gründen der Argumentation an, dass es nicht außergewöhnlich ist.

Die vierte Frage lautet: Gibt es eine Möglichkeit, die schlechte Situation rechtzeitig und zuverlässig zu erkennen?

Wenn die schlechte Situation in meinem "exogenen" Eimer ist, dann nein. Es gibt keine Möglichkeit, zuverlässig zu sagen, dass ein anderer Benutzer diesen Datensatz geändert hat. weil sie es möglicherweise ändern, nachdem Sie die Frage gestellt haben . Die Antwort ist abgestanden , sobald sie erstellt wurde.

Die fünfte Frage, die sich stellt, lautet: Gibt es eine Möglichkeit, die API so zu gestalten, dass die schlechte Situation verhindert werden kann?

Beispielsweise können Sie den Vorgang "Speichern" in zwei Schritten ausführen. Schritt eins: Erwerben Sie eine Sperre für den Datensatz, der geändert wird. Diese Operation ist entweder erfolgreich oder schlägt fehl und kann daher einen Booleschen Wert zurückgeben. Der Anrufer kann dann festlegen, wie mit Fehlern umgegangen werden soll: Warten Sie eine Weile und versuchen Sie es erneut. Schritt 2: Sobald die Sperre aktiviert ist, speichern Sie sie und geben Sie die Sperre frei. Nun ist das Speichern immer erfolgreich und Sie müssen sich keine Gedanken mehr über die Fehlerbehandlung machen. Wenn das Speichern fehlschlägt, ist das wirklich außergewöhnlich.

Eric Lippert
quelle
Alles sehr gute Punkte, danke. Hier ist eine rhetorische Frage, die wahrscheinlich meinen Beitrag zusammenfasst: Wenn Sie File.Open () neu entwerfen würden, würden Sie stattdessen File.TryOpen () erstellen? Wie würden Sie dem Verbraucher den Grund des Scheiterns mitteilen? Oder ist das Werfen einer exogenen Ausnahme hier wirklich der beste Kompromiss?
Mike
10
@ Mike: Dateisysteme sind ein gutes Beispiel für die Verwendung von exogenen Ausnahmen. Sie scheitern selten, daher ist ein Scheitern außergewöhnlich. Sie versagen unvorhersehbar und aus Gründen, die völlig außerhalb der Kontrolle des Anrufers liegen (es gibt kein "Schloss", das das Ethernet-Kabel eingesteckt hält), und die Fehler sind sowohl vielfältig als auch behebbar (das heißt, ein Fehler, weil eine Datei vorliegt) nicht gefunden vs Datei wurde gefunden, aber Sie haben keinen Schreibzugriff. Beide können auf unterschiedliche Art und Weise bearbeitet werden.) All dies sind Gründe, ausnahmsweise einen Fehler darzustellen.
Eric Lippert
Ich halte die Frage jetzt für beantwortet, aber ich bin nur neugierig;) Wenn die Methode aus zwei oder mehr Gründen fehlschlagen kann, sind die Fehler umsetzbar, die Fehler sind nicht außergewöhnlich und die Fehler können nicht vorzeitig erkannt oder verhindert werden. was würdest du tun?
Mike
@Mike Ich kann nicht für Eric sprechen, aber das klingt nach einem guten Ort für Fehlercodes. Vielleicht ein Mitglied einer Enumeration zurückgeben.
Matthew Read
1

Wenn in Ihrem Beispiel die ValueAlreadyExists-Situation einfach überprüft werden kann, sollte sie überprüft und eine Ausnahme ausgelöst werden, bevor das Speichern versucht wird. Ich denke nicht, dass in dieser Situation ein Versuch erforderlich sein sollte. Es ist schwieriger, die Rennbedingungen im Voraus zu überprüfen. In diesem Fall ist es wahrscheinlich eine sehr gute Idee, das Speichern in einen Versuch zu packen.

Im Allgemeinen, wenn es eine Bedingung gibt, die meiner Meinung nach sehr wahrscheinlich ist (wie NoDataReturned, DivideByZero usw.) ODER die sehr einfach zu überprüfen ist (wie eine leere Auflistung oder ein NULL-Wert), versuche ich zu überprüfen bevor ich zu dem Punkt komme, an dem ich eine Ausnahme erwischen müsste. Ich gebe zu, es ist nicht immer einfach, diese Bedingungen im Voraus zu kennen, manchmal erscheinen sie nur, wenn der Code strengen Tests unterzogen wird.

FrustratedWithFormsDesigner
quelle
0

Das save() Methode muss eine Ausnahme auslösen.

Die oberste Ebene sollte den Benutzer erfassen und informieren , ohne das Programm zu beenden, es sei denn, es handelt sich um ein Unix-ähnliches Befehlszeilenprogramm. In diesem Fall ist es in Ordnung, das Programm zu beenden.

Rückgabewerte sind keine gute Möglichkeit, Ausnahmen zu verwalten.

Tulains Córdova
quelle