Aber es ist immer noch nicht gut, die Software Ihres Kunden zum Absturz zu bringen
Es ist mit Sicherheit eine gute Sache.
Sie möchten, dass alles, was das System in einem undefinierten Zustand belässt, das System stoppt, da ein undefiniertes System böse Dinge wie beschädigte Daten ausführen, die Festplatte formatieren und E-Mails an den Präsidenten senden kann. Wenn Sie das System nicht wiederherstellen und in einen definierten Zustand zurückversetzen können, ist ein Absturz die verantwortliche Maßnahme. Genau deshalb bauen wir Systeme, die abstürzen, anstatt sich leise auseinander zu reißen. Nun sicher, wir alle wollen ein stabiles System, das niemals abstürzt, aber das wollen wir wirklich nur, wenn das System in einem definierten vorhersagbaren sicheren Zustand bleibt.
Ich habe gehört, dass Ausnahmen als Kontrollfluss als schwerwiegende Antimuster gelten
Das ist absolut richtig, wird aber oft missverstanden. Als sie das Ausnahmesystem erfanden, befürchteten sie, die strukturierte Programmierung zu brechen. Strukturierte Programmierung Deshalb haben wir for
, while
, until
, break
, und , continue
wenn alles , was wir brauchen, all das zu tun, ist goto
.
Dijkstra lehrte uns, dass die informelle Verwendung von goto (das heißt herumspringen, wo immer Sie möchten) das Lesen von Code zu einem Albtraum macht. Als sie uns das Ausnahmesystem gaben, hatten sie Angst, dass sie goto neu erfinden würden. Also sagten sie uns, wir sollten es nicht zur Flusskontrolle verwenden, in der Hoffnung, dass wir es verstehen würden. Leider haben viele von uns dies nicht getan.
Seltsamerweise missbrauchen wir nicht oft Ausnahmen, um Spaghetti-Code zu erstellen, wie wir es früher mit goto getan haben. Der Rat selbst scheint mehr Ärger verursacht zu haben.
Grundsätzlich geht es bei Ausnahmen darum, eine Annahme abzulehnen. Wenn Sie nach dem Speichern einer Datei fragen, gehen Sie davon aus, dass die Datei gespeichert werden kann und wird. Die Ausnahme, die Sie bekommen, wenn es nicht möglich ist, kann sein, dass der Name illegal ist, die Festplatte voll ist oder dass eine Ratte durch Ihr Datenkabel genagt hat. Sie können alle diese Fehler unterschiedlich behandeln, Sie können sie alle auf dieselbe Weise behandeln, oder Sie können sie das System anhalten lassen. In Ihrem Code gibt es einen glücklichen Pfad, auf dem Ihre Annahmen zutreffen müssen. Die eine oder andere Ausnahme bringt Sie von diesem glücklichen Weg ab. Genau genommen ist das eine Art "Flusskontrolle", aber davor haben sie dich nicht gewarnt. Sie sprachen über Unsinn wie folgt :
"Ausnahmen sollten außergewöhnlich sein". Diese kleine Tautologie wurde ins Leben gerufen, weil die Entwickler von Ausnahmesystemen Zeit benötigen, um Stack-Traces zu erstellen. Im Vergleich zum Herumspringen ist dies langsam. Es frisst CPU-Zeit. Wenn Sie jedoch das System protokollieren und anhalten oder zumindest die aktuelle zeitintensive Verarbeitung anhalten möchten, bevor Sie die nächste starten, haben Sie etwas Zeit, um sie zu beenden. Wenn Leute anfangen, Ausnahmen "zur Flusskontrolle" zu verwenden, gehen diese Annahmen über die Zeit alle aus dem Fenster. "Ausnahmen sollten außergewöhnlich sein" wurde uns wirklich als Leistungsüberlegung gegeben.
Viel wichtiger als das verwirrt uns nicht. Wie lange haben Sie gebraucht, um die Endlosschleife im obigen Code zu erkennen?
KEINE Fehlercodes zurückgeben.
... ist ein guter Rat, wenn Sie sich in einer Codebasis befinden, in der normalerweise keine Fehlercodes verwendet werden. Warum? Denn niemand wird sich daran erinnern, den Rückgabewert zu speichern und Ihre Fehlercodes zu überprüfen. Es ist immer noch eine gute Konvention, wenn Sie in C. sind
OneOf
Sie verwenden noch eine andere Konvention. Das ist in Ordnung, solange Sie die Konvention festlegen und nicht einfach gegen eine andere kämpfen. Es ist verwirrend, zwei Fehlerkonventionen in derselben Codebasis zu haben. Wenn Sie den gesamten Code, der die andere Konvention verwendet, entfernt haben, fahren Sie fort.
Ich mag die Convention selbst. Eine der besten Erklärungen dafür fand ich hier * :
Aber so sehr es mir gefällt, werde ich es trotzdem nicht mit den anderen Konventionen mischen. Wähle einen aus und bleibe dabei. 1
1: Damit meine ich, dass ich nicht über mehrere Konventionen gleichzeitig nachdenke.
Nachfolgende Gedanken:
Aus dieser Diskussion nehme ich derzeit Folgendes mit:
- Wenn Sie erwarten, dass der unmittelbare Aufrufer die Ausnahme die meiste Zeit abfängt und behandelt und seine Arbeit fortsetzt, möglicherweise über einen anderen Pfad, sollte er wahrscheinlich Teil des Rückgabetyps sein. Optional oder OneOf kann hier hilfreich sein.
- Wenn Sie erwarten, dass der unmittelbare Aufrufer die Ausnahme die meiste Zeit nicht abfängt, lösen Sie eine Ausnahme aus, um die Dummheit zu vermeiden, sie manuell über den Stapel weiterzuleiten.
- Wenn Sie nicht sicher sind, was der unmittelbare Anrufer tun wird, geben Sie möglicherweise beides an, z. B. Parse und TryParse.
Es ist wirklich nicht so einfach. Eines der grundlegenden Dinge, die Sie verstehen müssen, ist, was eine Null ist.
Wie viele Tage sind noch im Mai? 0 (weil es nicht Mai ist. Es ist schon Juni).
Ausnahmen sind eine Möglichkeit, eine Annahme abzulehnen, aber nicht die einzige. Wenn Sie Ausnahmen verwenden, um die Annahme abzulehnen, verlassen Sie den glücklichen Pfad. Wenn Sie jedoch Werte ausgewählt haben, um den glücklichen Pfad abzusenden, der signalisiert, dass die Dinge nicht so einfach sind, wie angenommen wurde, können Sie auf diesem Pfad bleiben, solange er mit diesen Werten umgehen kann. Manchmal bedeutet 0 bereits etwas, sodass Sie einen anderen Wert finden müssen, um Ihre Annahme, die eine Idee ablehnt, darauf abzubilden. Sie erkennen diese Idee vielleicht an ihrer Verwendung in der guten alten Algebra . Monaden können dabei helfen, aber es muss nicht immer eine Monade sein.
Zum Beispiel 2 :
IList<int> ParseAllTheInts(String s) { ... }
Kannst du dir einen guten Grund vorstellen, warum dies so gestaltet sein muss, dass es absichtlich irgendetwas auslöst? Rate mal, was du bekommst, wenn kein Int geparst werden kann? Ich muss es dir nicht einmal sagen.
Das ist ein Zeichen für einen guten Namen. Sorry, aber TryParse ist nicht meine Vorstellung von einem guten Namen.
Wir vermeiden es oft, eine Ausnahme zu machen, wenn die Antwort mehr als eine Sache zur gleichen Zeit sein könnte, aber aus irgendeinem Grund, wenn die Antwort entweder eine Sache oder nichts ist, werden wir davon besessen, darauf zu bestehen, dass sie uns eine Sache gibt oder wirft:
IList<Point> Intersection(Line a, Line b) { ... }
Müssen parallele Linien hier wirklich eine Ausnahme verursachen? Ist es wirklich so schlimm, wenn diese Liste niemals mehr als einen Punkt enthält?
Vielleicht kann man das semantisch einfach nicht ertragen. Wenn ja, ist es schade. Aber vielleicht List
fühlen Sie sich bei Monaden, die keine willkürliche Größe haben , besser.
Maybe<Point> Intersection(Line a, Line b) { ... }
Die Monaden sind kleine Spezialsammlungen, die für bestimmte Zwecke verwendet werden sollen, ohne dass sie getestet werden müssen. Wir sollen Wege finden, mit ihnen umzugehen, unabhängig davon, was sie enthalten. Auf diese Weise bleibt der glückliche Weg einfach. Wenn Sie jede Monade, die Sie berühren, aufschlagen und testen, verwenden Sie sie falsch.
Ich weiß, es ist komisch. Aber für uns ist es ein neues Werkzeug. Also gib ihm etwas Zeit. Hämmer sind sinnvoller, wenn Sie sie nicht mehr für Schrauben verwenden.
Wenn Sie mich verwöhnen, möchte ich diesen Kommentar ansprechen:
Wie kommt es, dass keine der Antworten klarstellt, dass die Monade entweder kein Fehlercode ist und OneOf auch nicht? Sie unterscheiden sich grundlegend, und die Frage scheint folglich auf einem Missverständnis zu beruhen. (Obwohl in modifizierter Form, ist es immer noch eine gültige Frage.) - Konrad Rudolph 4. Juni 18 um 14.08 Uhr
Das ist absolut richtig. Monaden sind Sammlungen viel näher als Ausnahmen, Flags oder Fehlercodes. Sie machen feine Behälter für solche Dinge, wenn sie mit Bedacht eingesetzt werden.
Result
;-)