Ausnahmen, Fehlercodes und diskriminierte Gewerkschaften


80

Ich habe vor kurzem einen C # -Programmierjob begonnen, habe aber einiges an Hintergrundwissen in Haskell.

Aber ich verstehe, dass C # eine objektorientierte Sprache ist, ich möchte keinen runden Stift in ein quadratisches Loch zwingen.

Ich habe den Artikel Exception Throwing von Microsoft gelesen, in dem es heißt:

KEINE Fehlercodes zurückgeben.

Aber da ich an Haskell gewöhnt bin, habe ich den C # -Datentyp verwendet OneOfund das Ergebnis als "richtigen" Wert oder den Fehler (meistens eine Aufzählung) als "linken" Wert zurückgegeben.

Dies ähnelt der Konvention von Eitherin Haskell.

Das erscheint mir sicherer als Ausnahmen. In C # führt das Ignorieren von Ausnahmen nicht zu einem Kompilierungsfehler. Wenn sie nicht abgefangen werden, sprudeln sie nur und stürzen Ihr Programm ab. Dies ist vielleicht besser, als einen Fehlercode zu ignorieren und undefiniertes Verhalten zu erzeugen, aber das Abstürzen der Client-Software ist immer noch keine gute Sache, insbesondere, wenn viele andere wichtige Geschäftsaufgaben im Hintergrund ausgeführt werden.

Bei OneOfmuss man ziemlich explizit sein, wie man es auspackt und mit dem Rückgabewert und den Fehlercodes umgeht. Und wenn man zu diesem Zeitpunkt im Aufrufstapel nicht weiß, wie man damit umgeht, muss es in den Rückgabewert der aktuellen Funktion eingefügt werden, damit die Aufrufer wissen, dass ein Fehler auftreten kann.

Dies scheint jedoch nicht der Ansatz zu sein, den Microsoft vorschlägt.

Ist die Verwendung von OneOfanstelle von Ausnahmen zum Behandeln von "normalen" Ausnahmen (wie "Datei nicht gefunden" usw.) ein vernünftiger Ansatz oder ist es eine schreckliche Praxis?


Es ist erwähnenswert, dass ich gehört habe, dass Ausnahmen als Kontrollfluss ein ernstes Antipattern darstellen . Wenn die "Ausnahme" also etwas ist, mit dem Sie normalerweise umgehen würden, ohne das Programm zu beenden, ist das nicht eine Art "Kontrollfluss"? Soweit ich weiß, gibt es hier eine Grauzone.

Hinweis Ich verwende nicht OneOffür Dinge wie "Out of Memory". Bedingungen, von denen ich keine Wiederherstellung erwarte, lösen weiterhin Ausnahmen aus. Ich halte es jedoch für vernünftig, wenn Benutzereingaben, die nicht analysiert werden, im Wesentlichen "Kontrollfluss" sind und wahrscheinlich keine Ausnahmen auslösen sollten.


Nachfolgende Gedanken:

Aus dieser Diskussion nehme ich derzeit Folgendes mit:

  1. Wenn Sie erwarten, dass der unmittelbare Aufrufer catchdie Ausnahme die meiste Zeit behandelt und seine Arbeit fortsetzt, möglicherweise über einen anderen Pfad, sollte er wahrscheinlich Teil des Rückgabetyps sein. Optionaloder OneOfkann hier nützlich sein.
  2. 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.
  3. Wenn Sie nicht sicher sind, was der unmittelbare Anrufer tun wird, geben Sie möglicherweise beides an, wie Parseund TryParse.

13
Verwenden Sie F # Result;-)
Astrinus

8
Dies ist ein wenig ungewöhnlich. Beachten Sie jedoch, dass der von Ihnen betrachtete Ansatz die allgemeine Sicherheitsanfälligkeit aufweist, dass der Entwickler den Fehler nicht ordnungsgemäß handhaben kann. Sicher, das Tippsystem kann als Erinnerung dienen, aber Sie verlieren die Standardeinstellung, das Programm zu sprengen, wenn ein Fehler nicht behoben werden kann, was es wahrscheinlicher macht, dass Sie in einen unerwarteten Zustand geraten. Ob die Mahnung den Verlust dieses Ausfalls wert ist, ist jedoch eine offene Debatte. Ich bin der Meinung, dass es einfach besser ist, Ihren gesamten Code zu schreiben, vorausgesetzt, eine Ausnahme beendet ihn irgendwann. dann hat die Erinnerung weniger Wert.
jpmc26

9
Passender Artikel: Eric Lippert über die vier "Eimer" der Ausnahmen . Das Beispiel, das er zu exogenen Ausnahmen gibt, zeigt, dass es Szenarien gibt, in denen man sich nur mit Ausnahmen befassen muss, d FileNotFoundException. H.
Søren D. Ptæus

7
Ich bin vielleicht alleine dabei, aber ich denke, dass Abstürzen gut ist . Es gibt keinen besseren Beweis für ein Problem mit Ihrer Software als ein Absturzprotokoll mit der richtigen Ablaufverfolgung. Wenn Sie ein Team haben, das in der Lage ist, einen Patch innerhalb von 30 Minuten oder weniger zu korrigieren und bereitzustellen, ist es möglicherweise keine schlechte Sache, diese hässlichen Freunde auftauchen zu lassen.
T. Sar

9
Wie kommt es, dass keine der Antworten klarstellt, dass die EitherMonade kein Fehlercode ist und auch nicht OneOf? 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

Antworten:


131

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 , continuewenn 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 :

Bildbeschreibung hier eingeben

"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 * :

Bildbeschreibung hier eingeben

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:

  1. 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.
  2. 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.
  3. 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 Listfü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.


9
Wäre es nicht angemessener, wenn sich die rote Spur unter den Kisten befände und somit nicht durch sie lief?
Flater

4
Logischerweise sind Sie richtig. Jedes Kästchen in diesem speziellen Diagramm repräsentiert jedoch eine monadische Bindeoperation. bind schließt (schafft vielleicht!) die rote Spur ein. Ich empfehle die vollständige Präsentation anzusehen. Es ist interessant und Scott ist ein großartiger Redner.
Gusdor

7
Ich denke an Ausnahmen, die eher einer COMEFROM- Anweisung ähneln als einer GOTO-Anweisung, da throwich eigentlich nicht weiß / nicht sage, wohin wir springen werden;)
Warbo,

3
Es ist nichts Falsches an Mischkonventionen, wenn Sie Grenzen festlegen können, worum es bei der Kapselung geht.
Robert Harvey

4
Der gesamte erste Abschnitt dieser Antwort liest sich stark, als hätten Sie aufgehört, die Frage nach dem zitierten Satz zu lesen. In der Frage wird anerkannt, dass ein Absturz des Programms dem Übergang zu undefiniertem Verhalten überlegen ist: Es handelt sich nicht um einen Gegensatz zwischen Laufzeitabstürzen und einer fortgesetzten undefinierten Ausführung, sondern um Fehler bei der Kompilierung, die Sie daran hindern, das Programm selbst zu erstellen, ohne den potenziellen Fehler zu berücksichtigen und damit umzugehen (was möglicherweise zum Absturz führt, wenn nichts anderes zu tun ist, aber es wird zum Absturz, weil Sie es wollen, nicht weil Sie es vergessen haben).
KRyan

35

C # ist nicht Haskell, und Sie sollten dem Experten-Konsens der C # -Community folgen. Wenn Sie stattdessen versuchen, die Haskell-Praktiken in einem C # -Projekt zu befolgen, entfremden Sie alle anderen Mitglieder Ihres Teams und entdecken möglicherweise die Gründe, warum die C # -Community die Dinge anders ausführt. Ein wichtiger Grund ist, dass C # diskriminierte Gewerkschaften nicht bequem unterstützt.

Ich habe gehört, dass Ausnahmen als Kontrollfluss als schwerwiegende Antimuster gelten,

Dies ist keine allgemein akzeptierte Wahrheit. In jeder Sprache, die Ausnahmen unterstützt, können Sie entweder eine Ausnahme auslösen (die der Aufrufer nicht behandeln kann) oder einen zusammengesetzten Wert zurückgeben (den der Aufrufer behandeln MUSS).

Die Weitergabe von Fehlerbedingungen über den Aufrufstapel nach oben erfordert eine Bedingung auf jeder Ebene, die die zyklomatische Komplexität dieser Methoden verdoppelt und folglich die Anzahl der Einzeltestfälle verdoppelt. In typischen Geschäftsanwendungen sind viele Ausnahmen nicht mehr wiederherstellbar und können auf die höchste Ebene übertragen werden (z. B. den Serviceeinstiegspunkt einer Webanwendung).


9
Die Ausbreitung von Monaden erfordert nichts.
Basilevs

23
@Basilevs Erfordert Unterstützung für die Weitergabe von Monaden, während der Code lesbar bleibt, über den C # nicht verfügt. Sie erhalten entweder wiederholte Anweisungen zur Rückgabe oder Lambda-Ketten.
Sebastian Redl

4
@SebastianRedl Lambdas sehen in C # OK aus.
Basilevs

@Basilevs Aus der Sicht der Syntax ja, aber ich vermute, dass sie aus der Sicht der Leistung weniger attraktiv sind.
Pharap

5
In modernem C # können Sie wahrscheinlich teilweise lokale Funktionen verwenden, um etwas Geschwindigkeit zurückzugewinnen. In jedem Fall wird die meiste Unternehmenssoftware durch IO wesentlich stärker verlangsamt als sonst.
Turksarama

26

Sie sind zu einem interessanten Zeitpunkt zu C # gekommen. Bis vor relativ kurzer Zeit saß die Sprache fest im imperativen Programmierraum. Die Verwendung von Ausnahmen zur Übermittlung von Fehlern war definitiv die Norm. Die Verwendung von Rückgabecodes litt unter mangelnder Sprachunterstützung (z. B. diskriminierte Gewerkschaften und Mustervergleich). Die offizielle Anweisung von Microsoft lautete vernünftigerweise, Rückkehrcodes zu vermeiden und Ausnahmen zu verwenden.

Es hat jedoch immer Ausnahmen in Form von TryXXXMethoden gegeben, die ein booleanErfolgsergebnis zurückgeben und ein zweites outWertergebnis über einen Parameter liefern . Diese sind den "try" -Mustern in der funktionalen Programmierung sehr ähnlich, außer dass das Ergebnis über einen out-Parameter und nicht über einen Maybe<T>Rückgabewert kommt.

Aber die Dinge ändern sich. Funktionale Programmierung wird immer beliebter und Sprachen wie C # reagieren. In C # 7.0 wurden einige grundlegende Mustervergleichsfunktionen für die Sprache eingeführt. C # 8 wird noch viel mehr einführen, einschließlich eines Schalterausdrucks, rekursiver Muster usw. Gleichzeitig hat die Anzahl der "funktionalen Bibliotheken" für C # zugenommen, wie z. B. meine eigene Succinc <T> -Bibliothek , die auch diskriminierte Gewerkschaften unterstützt .

Diese beiden Dinge zusammen bedeuten, dass Code wie der folgende langsam an Popularität gewinnt.

static Maybe<int> TryParseInt(string source) =>
    int.TryParse(source, out var result) ? new Some<int>(result) : none; 

public int GetNumberFromUser()
{
    Console.WriteLine("Please enter a number");
    while (true)
    {
        var userInput = Console.ReadLine();
        if (TryParseInt(userInput) is int value)
        {
            return value;
        }
        Console.WriteLine("That's not a valid number. Please try again");
    }
}

Es ist im Moment noch eine ziemlich kleine Nische, obwohl ich selbst in den letzten zwei Jahren einen deutlichen Wandel von allgemeiner Feindseligkeit unter C # -Entwicklern zu solchem ​​Code zu einem wachsenden Interesse an der Verwendung solcher Techniken bemerkt habe.

Wir sind noch nicht da, wenn wir sagen " KEINE Fehlercodes zurückgeben". ist antiquiert und muss in den Ruhestand. Aber der Marsch die Straße hinunter in Richtung dieses Ziels ist in vollem Gange. Treffen Sie also Ihre Wahl: Bleiben Sie bei den alten Methoden, Ausnahmen mit schwulem Hintergrund zu werfen, wenn Sie es so mögen. oder entdecken Sie eine neue Welt, in der Konventionen aus funktionalen Sprachen in C # immer beliebter werden.


22
Das ist der Grund, warum ich C # hasse :) Sie fügen immer wieder neue Wege hinzu, um eine Katze zu häuten, und natürlich verschwinden die alten Wege nie. Am Ende gibt es keine Konventionen für irgendetwas, ein perfektionistischer Programmierer kann stundenlang sitzen und entscheiden, welche Methode "schöner" ist, und ein neuer Programmierer muss alles lernen, bevor er den Code anderer lesen kann.
Aleksandr Dubinsky

24
@AleksandrDubinsky, Sie sind auf ein Kernproblem mit Programmiersprachen im Allgemeinen gestoßen, nicht nur mit C #. Neue Sprachen entstehen, schlank, frisch und voller moderner Ideen. Und nur sehr wenige Leute benutzen sie. Mit der Zeit gewinnen sie Benutzer und neue Funktionen, die auf alten Funktionen aufbauen, die nicht entfernt werden können, da sie den vorhandenen Code beschädigen würden. Die Blähung wächst. Die Leute erschaffen also neue Sprachen, die schlank, frisch und voller moderner Ideen sind ...
David Arno

7
@AleksandrDubinsky hasst du es nicht, wenn sie jetzt Funktionen aus den 1960er Jahren hinzufügen.
Strg-Alt-Delor

6
@AleksandrDubinsky Ja. Da Java versucht , etwas hinzuzufügen , einmal (Generika), haben sie es versäumt, müssen wir jetzt mit dem schrecklichen Chaos leben, geführt , so haben sie die Lektion gelernt , und beenden Sie etwas hinzuzufügen überhaupt:)
Agent_L

3
@Agent_L: Sie wissen, dass Java hinzugefügt wurde java.util.Stream(ein halbgesetzter IEnumerable-alike) und Lambdas jetzt, richtig? :)
cHao

5

Ich denke, es ist völlig vernünftig, eine DU zu verwenden, um Fehler zurückzugeben, aber dann würde ich das sagen, als ich OneOf schrieb :) Aber ich würde das trotzdem sagen, da es in F # usw. üblich ist (z. B. der von anderen erwähnte Ergebnistyp) ).

Ich denke nicht an die Fehler, die ich normalerweise als Ausnahmesituationen zurückgebe, sondern als normale, aber hoffentlich selten auftretende Situationen, die möglicherweise behandelt werden müssen oder nicht, aber modelliert werden sollten.

Hier ist das Beispiel von der Projektseite.

public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
    if (!IsValid(username)) return new InvalidName();
    var user = _repo.FindByUsername(username);
    if(user != null) return new NameTaken();
    var user = new User(username);
    _repo.Save(user);
    return user;
}

Das Auslösen von Ausnahmen für diese Fehler erscheint als Overkill - sie haben einen Performance-Overhead, abgesehen von den Nachteilen, dass sie nicht explizit in der Methodensignatur enthalten sind oder einen umfassenden Abgleich bieten.

Inwieweit OneOf in c # idiomatisch ist, ist eine andere Frage. Die Syntax ist gültig und ziemlich intuitiv. DUs und Rail-orientierte Programmierung sind bekannte Konzepte.

Ich würde Ausnahmen für Dinge speichern, die darauf hinweisen, dass etwas kaputt ist, wo dies möglich ist.


Was Sie sagen, ist, dass OneOfes darum geht, den Rückgabewert reicher zu machen, wie eine Nullable zurückzugeben. Es ersetzt Ausnahmen für Situationen, die anfangs nicht außergewöhnlich sind.
Aleksandr Dubinsky

Ja - und bietet eine einfache Möglichkeit, umfassende Übereinstimmungen im Aufrufer vorzunehmen, die mit einer vererbungsbasierten Ergebnishierarchie nicht möglich sind.
Mcintyre321

4

OneOfentspricht in etwa überprüften Ausnahmen in Java. Es gibt einige Unterschiede:

  • OneOffunktioniert nicht mit Methoden, die Methoden erzeugen, die für ihre Nebenwirkungen aufgerufen werden (da sie möglicherweise sinnvoll aufgerufen werden und das Ergebnis einfach ignoriert wird). Natürlich sollten wir alle versuchen, reine Funktionen zu verwenden, aber das ist nicht immer möglich.

  • OneOfgibt keine Auskunft darüber, wo das Problem aufgetreten ist. Dies ist gut, da es die Leistung spart (ausgelöste Ausnahmen in Java sind kostspielig, weil der Stack-Trace gefüllt wird, und in C # ist es dasselbe) und Sie dazu zwingt, im Fehlermitglied OneOfselbst ausreichende Informationen bereitzustellen . Es ist auch schlecht, da diese Informationen möglicherweise unzureichend sind und Sie Schwierigkeiten haben können, die Ursache des Problems zu finden.

OneOf und geprüfte Ausnahmen haben ein wichtiges "Merkmal" gemeinsam:

  • Beide zwingen Sie, sie überall im Call-Stack zu verarbeiten

Dies verhindert deine Angst

In C # führt das Ignorieren von Ausnahmen nicht zu einem Kompilierungsfehler. Wenn sie nicht abgefangen werden, sprudeln sie nur und stürzen Ihr Programm ab.

aber wie gesagt, diese angst ist irrational. Grundsätzlich gibt es normalerweise eine einzige Stelle in Ihrem Programm, an der Sie alles erfassen müssen (wahrscheinlich verwenden Sie dafür bereits ein Framework). Da Sie und Ihr Team normalerweise Wochen oder Jahre an dem Programm arbeiten, werden Sie es nicht vergessen, oder?

Der große Vorteil ignorierbarer Ausnahmen besteht darin, dass sie überall dort behandelt werden können, wo Sie sie behandeln möchten, und nicht überall im Stack-Trace. Dies eliminiert Tonnen von Boilerplate (sehen Sie sich nur einige Java-Codes an, die "Throws" deklarieren oder Ausnahmen einschließen) und macht Ihren Code weniger fehleranfällig: Da jede Methode Throws verursachen kann, müssen Sie sich dessen bewusst sein. Glücklicherweise bewirkt die richtige Aktion normalerweise nichts , dh, dass sie an einen Ort sprudelt, an dem sie vernünftig gehandhabt werden kann.


+1 aber warum funktioniert OneOf nicht mit Nebenwirkungen? (Ich denke , es ist , weil es einen Scheck übergeben wird , wenn Sie nicht den Rückgabewert zuweisen, aber ich weiß nicht OneOf, noch viel C # Ich bin nicht sicher , -. Klarstellung verbessern Sie Ihre Antwort)
dcorking

1
Sie können Fehler Info mit OneOf Rückkehr des zurückgegebene Fehlerobjekt enthält nützliche Informationen zum Beispiel zu machen , OneOf<SomeSuccess, SomeErrorInfo>wo die SomeErrorInfoEinzelheiten des Fehlers zur Verfügung stellt.
Mcintyre321

@dcorking Bearbeitet. Sicher, wenn Sie den Rückgabewert ignoriert haben, wissen Sie nichts über das Problem. Wenn Sie dies mit einer reinen Funktion tun, ist es in Ordnung (Sie haben nur Zeit verschwendet).
Maaartinus

2
@ mcintyre321 Klar, du kannst Informationen hinzufügen, aber du musst es manuell machen und es unterliegt der Faulheit und dem Vergessen. Ich denke, in komplizierteren Fällen ist ein Stack-Trace hilfreicher.
Maaartinus

4

Ist die Verwendung von OneOf anstelle von Ausnahmen zur Behandlung von "normalen" Ausnahmen (wie "Datei nicht gefunden" usw.) eine vernünftige Vorgehensweise oder ist es eine schreckliche Praxis?

Es ist erwähnenswert, dass ich gehört habe, dass Ausnahmen als Kontrollfluss ein ernstes Antipattern darstellen. Wenn die "Ausnahme" also etwas ist, mit dem Sie normalerweise umgehen würden, ohne das Programm zu beenden, ist das nicht eine Art "Kontrollfluss"?

Fehler haben eine Reihe von Dimensionen. Ich identifiziere drei Dimensionen: Können sie verhindert werden, wie oft sie auftreten und ob sie wiederhergestellt werden können? Unterdessen hat die Fehlersignalisierung hauptsächlich eine Dimension: zu entscheiden, inwieweit der Anrufer über den Kopf geschlagen werden soll, um ihn zur Behebung des Fehlers zu zwingen, oder den Fehler ausnahmsweise "leise" in die Luft sprudeln zu lassen.

Die nicht vermeidbaren, häufigen und behebbaren Fehler müssen vor Ort behoben werden. Sie rechtfertigen es am besten, den Anrufer zu zwingen, sich ihnen zu stellen. In Java mache ich sie zu geprüften Ausnahmen, und ein ähnlicher Effekt wird erzielt, wenn man sie machtTry* Methoden macht oder eine diskriminierte Union zurückgibt. Diskriminierte Gewerkschaften sind besonders nett, wenn eine Funktion einen Rückgabewert hat. Sie sind eine viel raffiniertere Version der Rückkehr null. Die Syntax von try/catchBlöcken ist nicht besonders gut (zu viele Klammern), sodass Alternativen besser aussehen. Ausnahmen sind außerdem etwas langsam, da sie Stack-Traces aufzeichnen.

Die vermeidbaren Fehler (die aufgrund von Programmierfehlern / Nachlässigkeiten nicht verhindert wurden) und die nicht behebbaren Fehler funktionieren wie gewöhnliche Ausnahmen. Seltene Fehler funktionieren auch besser als gewöhnliche Ausnahmen, da ein Programmierer es oftmals als nicht vorhersehbar ansieht (dies hängt vom Zweck des Programms ab).

Ein wichtiger Punkt ist, dass es häufig die Verwendungsstelle ist, die bestimmt, wie die Fehlermodi einer Methode in diese Dimensionen passen. Dies sollte beachtet werden, wenn Folgendes berücksichtigt wird.

Nach meiner Erfahrung ist es in Ordnung, den Anrufer zu zwingen, Fehlerzuständen zu begegnen, wenn Fehler nicht vermeidbar, häufig und behebbar sind, aber sehr ärgerlich, wenn der Anrufer gezwungen ist, durch Rahmen zu springen, wenn er sie nicht benötigt oder möchte . Dies kann daran liegen, dass der Aufrufer weiß, dass der Fehler nicht auftritt, oder dass er den Code (noch) nicht ausfallsicher machen möchte. In Java erklärt dies die Angst vor der häufigen Verwendung von geprüften Ausnahmen und der Rückgabe von Optional (ähnlich wie "Vielleicht" und wurde kürzlich in vielen Kontroversen eingeführt). Wenn Sie Zweifel haben, werfen Sie Ausnahmen und lassen Sie den Anrufer entscheiden, wie er mit ihnen umgehen möchte.

Denken Sie zum Schluss daran, dass das Wichtigste bei Fehlerzuständen ist, sie gründlich zu dokumentieren , und zwar weit über jede andere Überlegung, wie sie beispielsweise signalisiert werden .


2

Die anderen Antworten haben Ausnahmen und Fehlercodes ausführlich genug besprochen, daher möchte ich der Frage eine weitere spezifische Perspektive hinzufügen:

OneOf ist kein Fehlercode, sondern eher eine Monade

Bei der Verwendung handelt OneOf<Value, Error1, Error2>es sich um einen Container, der entweder das tatsächliche Ergebnis oder einen Fehlerstatus darstellt. Es ist wie einOptional , außer dass, wenn kein Wert vorhanden ist, es mehr Details liefern kann, warum das so ist.

Das Hauptproblem bei Fehlercodes besteht darin, dass Sie vergessen, sie zu überprüfen. Aber hier können Sie buchstäblich nicht auf das Ergebnis zugreifen, ohne nach Fehlern zu suchen.

Das einzige Problem ist, wenn Sie sich nicht um das Ergebnis kümmern. Das Beispiel aus der OneOf-Dokumentation enthält:

public OneOf<User, InvalidName, NameTaken> CreateUser(string username) { ... }

... und dann erstellen sie unterschiedliche HTTP-Antworten für jedes Ergebnis, was viel besser ist als die Verwendung von Ausnahmen. Aber wenn Sie die Methode so aufrufen.

public void CreateUserButtonClick(String username) {
    UserManager.CreateUser(string username)
}

dann hast du ein problem und ausnahmen wären die bessere wahl. Vergleichen Rail savevs save!.


1

Eine Sache, die Sie berücksichtigen müssen, ist, dass Code in C # im Allgemeinen nicht rein ist. In einer Codebasis voller Nebenwirkungen und mit einem Compiler, der sich nicht darum kümmert, was Sie mit einem Rückgabewert einer Funktion tun, kann Ihr Ansatz zu erheblichem Kummer führen. Wenn Sie beispielsweise über eine Methode verfügen, mit der eine Datei gelöscht wird, möchten Sie sicherstellen, dass die Anwendung einen Fehler bei der Ausführung der Methode feststellt, obwohl es keinen Grund gibt, den Fehlercode "auf dem fehlerfreien Pfad" zu überprüfen. Dies ist ein erheblicher Unterschied zu funktionalen Sprachen, bei denen Rückgabewerte, die Sie nicht verwenden, explizit ignoriert werden müssen. In C # werden Rückgabewerte immer implizit ignoriert (die einzige Ausnahme sind Property Getters IIRC).

Der Grund, warum MSDN Sie auffordert, keine Fehlercodes zurückzugeben, ist genau das. Niemand zwingt Sie, den Fehlercode überhaupt zu lesen . Der Compiler hilft Ihnen überhaupt nicht. Allerdings , wenn Ihre Funktion Nebeneffekt frei ist, Sie können sicher etwas verwenden , wie Either- der Punkt ist , dass selbst wenn Sie das Fehlerergebnis (falls vorhanden) zu ignorieren, können Sie nur das tun , wenn Sie das nicht „richtige Ergebnis“ verwenden entweder. Es gibt einige Möglichkeiten, wie Sie dies erreichen können. Beispielsweise können Sie das "Lesen" des Werts möglicherweise nur zulassen, indem Sie einen Delegierten für die Behandlung der Erfolgs - und Fehlerfälle übergeben, und Sie können dies problemlos mit Ansätzen wie der eisenbahnorientierten Programmierung kombinieren (dies ist der Fall) funktioniert gut in C #).

int.TryParseist irgendwo in der Mitte. Es ist rein. Hiermit wird festgelegt, wie die Werte der beiden Ergebnisse zu jedem Zeitpunkt lauten. Sie wissen also, dass falseder Ausgabeparameter bei einem Rückgabewert auf gesetzt wird 0. Es hindert Sie immer noch nicht daran, den Ausgabeparameter zu verwenden, ohne den Rückgabewert zu überprüfen, aber es ist zumindest gewährleistet, dass das Ergebnis auch dann angezeigt wird, wenn die Funktion fehlschlägt.

Aber eine absolut entscheidende Sache ist hier die Konsistenz . Der Compiler wird Sie nicht retten, wenn jemand diese Funktion ändert, um Nebenwirkungen zu haben. Oder um Ausnahmen zu werfen. Während dieser Ansatz in C # völlig in Ordnung ist (und ich verwende ihn), müssen Sie sicherstellen, dass jeder in Ihrem Team ihn versteht und anwendet . Viele der Invarianten, die für eine funktionierende Programmierung erforderlich sind, werden vom C # -Compiler nicht erzwungen, und Sie müssen sicherstellen, dass sie selbst befolgt werden. Sie müssen sich über die Probleme im Klaren sein, die auftreten können, wenn Sie die von Haskell standardmäßig festgelegten Regeln nicht einhalten. Dies sollten Sie bei allen Funktionsparadigmen berücksichtigen, die Sie in Ihren Code einführen.


0

Die richtige Anweisung wäre: Verwenden Sie keine Fehlercodes für Ausnahmen! Und verwenden Sie keine Ausnahmen für Nicht-Ausnahmen.

Wenn etwas passiert, von dem Ihr Code nicht wiederhergestellt werden kann (dh funktioniert), ist dies eine Ausnahme. Es gibt nichts, was Ihr sofortiger Code mit einem Fehlercode tun würde, außer ihn weiterzugeben, um ihn zu protokollieren oder dem Benutzer möglicherweise auf irgendeine Weise bereitzustellen. Genau dafür gibt es jedoch Ausnahmen - sie kümmern sich um das ganze Herumreichen und helfen bei der Analyse, was tatsächlich passiert ist.

Wenn jedoch etwas passiert, wie eine ungültige Eingabe, die Sie verarbeiten können, indem Sie sie entweder automatisch neu formatieren oder den Benutzer auffordern, die Daten zu korrigieren (und Sie haben an diesen Fall gedacht), ist dies - wie Sie - keine wirkliche Ausnahme erwarte es. Sie können diese Fälle jederzeit mit Fehlercodes oder einer anderen Architektur behandeln. Nur keine Ausnahmen.

(Beachten Sie, dass ich in C # nicht sehr erfahren bin, aber meine Antwort sollte im Allgemeinen auf der konzeptionellen Ebene der Ausnahmen basieren.)


7
Ich bin mit der Prämisse dieser Antwort grundsätzlich nicht einverstanden. Wenn Sprachentwickler nicht beabsichtigten, Ausnahmen für behebbare Fehler zu verwenden, wäre das catchSchlüsselwort nicht vorhanden. Und da wir in einem C # -Kontext sprechen, ist es erwähnenswert, dass die hier vertretene Philosophie nicht vom .NET-Framework befolgt wird. Vielleicht ist das einfachste vorstellbare Beispiel für "ungültige Eingaben, die Sie verarbeiten können", dass der Benutzer eine Nicht-Nummer in ein Feld eingibt, in dem eine Nummer erwartet wird ... und int.Parseeine Ausnahme auslöst.
Mark Amery

@MarkAmery Für int.Parse ist es nichts, von dem es sich erholen kann, und auch nichts, was es erwarten würde. Die catch-Klausel wird normalerweise verwendet, um einen vollständigen Ausfall von außen zu vermeiden. Sie fordert den Benutzer nicht zur Eingabe neuer Datenbankanmeldeinformationen oder dergleichen auf und verhindert, dass das Programm abstürzt. Es ist ein technischer Fehler, kein Ablauf der Anwendungssteuerung.
Frank Hopkins

3
Die Frage, ob es für eine Funktion besser ist, eine Ausnahme auszulösen, wenn eine Bedingung auftritt, hängt davon ab, ob der unmittelbare Aufrufer der Funktion diese Bedingung sinnvoll handhaben kann. In Fällen, in denen dies möglich ist, bedeutet das Auslösen einer Ausnahme, die der unmittelbare Aufrufer abfangen muss, zusätzliche Arbeit für den Autor des aufrufenden Codes und zusätzliche Arbeit für die Maschine, die ihn verarbeitet. Wenn ein Anrufer jedoch nicht auf die Behandlung einer Bedingung vorbereitet ist, können Ausnahmen die Notwendigkeit verringern, dass der Anrufer den Code explizit für diese Bedingung verwendet.
Supercat

@Darkwing "Es steht Ihnen frei, diese Fälle mit Fehlercodes oder einer anderen Architektur zu behandeln." Ja, das können Sie tun. Sie erhalten jedoch keine Unterstützung von .NET, da die .NET-CL selbst Ausnahmen anstelle von Fehlercodes verwendet. Sie sind also nicht nur in vielen Fällen gezwungen, die .NET-Ausnahmen abzufangen und den entsprechenden Fehlercode zurückzugeben, anstatt die Ausnahmeblase auszulösen, sondern Sie zwingen auch andere Mitglieder Ihres Teams zu einem anderen Fehlerbehandlungskonzept, das sie verwenden müssen parallel zu Ausnahmen das Standard-Fehlerbehandlungskonzept.
Aleksander

-2

Es ist eine "schreckliche Idee".

Es ist schrecklich, denn zumindest in .net gibt es keine vollständige Liste möglicher Ausnahmen. Jeder Code kann möglicherweise eine Ausnahme auslösen. Insbesondere, wenn Sie OOP ausführen und überschriebene Methoden für einen Untertyp anstelle des deklarierten Typs aufrufen könnten

Sie müssten also alle Methoden und Funktionen auf ändern. OneOf<Exception, ReturnType>Wenn Sie dann verschiedene Ausnahmetypen behandeln möchten, müssen Sie den Typ If(exception is FileNotFoundException)usw. Untersuchen

try catch ist das Äquivalent von OneOf<Exception, ReturnType>

Bearbeiten ----

Mir ist bewusst, dass Sie eine Rückkehr vorschlagen, OneOf<ErrorEnum, ReturnType>aber ich bin der Meinung, dass dies ein Unterschied ohne Unterschied ist.

Sie kombinieren Rückkehrcodes, erwartete Ausnahmen und Verzweigen nach Ausnahme. Der Gesamteffekt ist jedoch der gleiche.


2
"Normale" Ausnahmen sind ebenfalls eine schreckliche Idee. Der Hinweis ist im Namen
Ewan

4
Ich schlage nicht vor, Exceptions zurückzugeben.
Clinton

schlagen Sie vor, dass die Alternative zu einer der Kontrollfluss über Ausnahmen ist
Ewan
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.