Stellen Sie sich eine Situation vor, in der ich drei (oder mehr) Möglichkeiten habe, eine Berechnung durchzuführen, von denen jede mit einer Ausnahme fehlschlagen kann. Um jede Berechnung zu versuchen, bis wir eine finden, die erfolgreich ist, habe ich Folgendes getan:
double val;
try { val = calc1(); }
catch (Calc1Exception e1)
{
try { val = calc2(); }
catch (Calc2Exception e2)
{
try { val = calc3(); }
catch (Calc3Exception e3)
{
throw new NoCalcsWorkedException();
}
}
}
Gibt es ein akzeptiertes Muster, das dies auf schönere Weise erreicht? Natürlich könnte ich jede Berechnung in eine Hilfsmethode einschließen, die bei einem Fehler null zurückgibt, und dann einfach den ??
Operator verwenden, aber es gibt eine Möglichkeit, dies allgemeiner zu tun (dh ohne für jede Methode, die ich verwenden möchte, eine Hilfsmethode schreiben zu müssen )? Ich habe darüber nachgedacht, eine statische Methode mit Generika zu schreiben, die eine bestimmte Methode in einen Versuch / Fang einschließt und bei einem Fehler null zurückgibt, bin mir aber nicht sicher, wie ich vorgehen soll. Irgendwelche Ideen?
quelle
String
zu einemDate
using analysierenSimpleDateFormat.parse
und mehrere verschiedene Formate der Reihe nach ausprobieren, um zum nächsten überzugehen, wenn eine Ausnahme ausgelöst wird.Antworten:
Verwenden Sie nach Möglichkeit keine Ausnahmen für den Kontrollfluss oder außergewöhnliche Umstände.
Aber um Ihre Frage direkt zu beantworten (vorausgesetzt, alle Ausnahmetypen sind gleich):
quelle
Calc1Exception
,Calc2Exception
undCalc3Exception
eine gemeinsame Basisklasse gemeinsam haben.continue
im catch-Block undbreak
nach dem catch-Block hinzugefügt , damit die Schleife endet, wenn eine Berechnung funktioniert (danke an Lirik für dieses Bit)break
Aussagecalc();
innerhalb dertry
(und Nrcontinue
Aussage) könnte etwas idiomatischer sein.Nur um eine "außerhalb der Box" -Alternative anzubieten, wie wäre es mit einer rekursiven Funktion ...
HINWEIS: Ich sage keineswegs, dass dies der beste Weg ist, um die Arbeit zu erledigen, nur ein anderer Weg
quelle
return DoCalc(c++)
entsprichtreturn DoCalc(c)
- nach dem Inkrementieren wird der Wert nicht tiefer übergeben. Damit es funktioniert (und mehr Dunkelheit einführt), könnte es eher so seinreturn DoCalc((c++,c))
.Sie können die Verschachtelung reduzieren, indem Sie sie in eine Methode wie die folgende einfügen:
Ich vermute jedoch, dass das eigentliche Designproblem darin besteht, dass drei verschiedene Methoden existieren, die im Wesentlichen dasselbe tun (aus Sicht des Anrufers), aber unterschiedliche, nicht verwandte Ausnahmen auslösen.
Dies setzt voraus, dass die drei Ausnahmen nichts miteinander zu tun haben. Wenn alle eine gemeinsame Basisklasse haben, ist es besser, eine Schleife mit einem einzelnen catch-Block zu verwenden, wie Ani vorgeschlagen hat.
quelle
Versuchen Sie, die Logik nicht anhand von Ausnahmen zu steuern. Beachten Sie auch, dass Ausnahmen nur in Ausnahmefällen ausgelöst werden sollten. Berechnungen sollten in den meisten Fällen keine Ausnahmen auslösen, es sei denn, sie greifen auf externe Ressourcen zu oder analysieren Zeichenfolgen oder ähnliches. Befolgen Sie im schlimmsten Fall den TryMethod-Stil (wie TryParse ()), um die Ausnahmelogik zu kapseln und Ihren Kontrollfluss wartbar und sauber zu machen:
Eine weitere Variante, die das Try-Muster verwendet und eine Liste von Methoden anstelle von verschachtelten kombiniert, wenn:
und wenn der foreach ein wenig kompliziert ist, können Sie es klar machen:
quelle
Erstellen Sie eine Liste der Delegaten für Ihre Berechnungsfunktionen und führen Sie anschließend eine while-Schleife durch:
Das sollte Ihnen eine allgemeine Vorstellung davon geben, wie es geht (dh es ist keine exakte Lösung).
quelle
Exception
. ;)Exception
.Das sieht nach einem Job für ... MONADS aus! Insbesondere die Vielleicht-Monade. Beginnen Sie mit der hier beschriebenen Vielleicht-Monade . Fügen Sie dann einige Erweiterungsmethoden hinzu. Ich habe diese Erweiterungsmethoden speziell für das von Ihnen beschriebene Problem geschrieben. Das Schöne an Monaden ist, dass Sie die genauen Erweiterungsmethoden schreiben können, die für Ihre Situation erforderlich sind.
Verwenden Sie es so:
Wenn Sie diese Art von Berechnungen häufig durchführen, sollte die Monade möglicherweise die Menge an Boilerplate-Code reduzieren, die Sie schreiben müssen, und gleichzeitig die Lesbarkeit Ihres Codes verbessern.
quelle
Maybe
wird, ist dies keine monadische Lösung. Es verwendet null der monadischen Eigenschaften vonMaybe
und kann daher auch nur verwendet werdennull
. Außerdem, "monadisch" verwendet, wäre dies die Umkehrung derMaybe
. Eine echte monadische Lösung müsste eine staatliche Monade verwenden, die den ersten nicht außergewöhnlichen Wert als Zustand beibehält, aber das wäre übertrieben, wenn die normale verkettete Bewertung funktioniert.Eine andere Version des Ansatzes der try- Methode. Dieser erlaubt typisierte Ausnahmen, da es für jede Berechnung einen Ausnahmetyp gibt:
quelle
&&
zwischen den einzelnen Bedingungen verwenden.In Perl können Sie tun
foo() or bar()
, was ausgeführt wird,bar()
wennfoo()
fehlschlägt. In C # sehen wir dieses Konstrukt "if fail, then" nicht, aber es gibt einen Operator, den wir für diesen Zweck verwenden können: den Null-Coalesce-Operator??
, der nur fortgesetzt wird, wenn der erste Teil null ist.Wenn Sie die Signatur Ihrer Berechnungen ändern können und wenn Sie entweder die Ausnahmen (wie in den vorherigen Beiträgen gezeigt) umbrechen oder sie neu schreiben, um sie zurückzugeben
null
, wird Ihre Codekette immer kürzer und dennoch leicht zu lesen:Ich habe die folgenden Ersetzungen für Ihre Funktionen verwendet, was zu dem Wert
40.40
in führtval
.Mir ist klar, dass diese Lösung nicht immer anwendbar ist, aber Sie haben eine sehr interessante Frage gestellt, und ich glaube, obwohl der Thread relativ alt ist, ist dies ein Muster, das es wert ist, in Betracht gezogen zu werden, wenn Sie die Änderungen vornehmen können.
quelle
Da die Berechnungsmethoden dieselbe parameterlose Signatur haben, können Sie sie in einer Liste registrieren, diese Liste durchlaufen und die Methoden ausführen. Wahrscheinlich ist es für Sie sogar noch besser, die
Func<double>
Bedeutung "eine Funktion, die ein Ergebnis vom Typ zurückgibtdouble
" zu verwenden.quelle
Sie können eine Aufgabe / ContinueWith verwenden und nach der Ausnahme suchen. Hier ist eine nette Erweiterungsmethode, um es hübsch zu machen:
quelle
Wenn der tatsächliche Typ der ausgelösten Ausnahme keine Rolle spielt, können Sie einfach einen typenlosen Catch-Block verwenden:
quelle
if(succeeded) { break; }
Post-Catch werfen .Und benutze es so:
quelle
Es scheint, dass die Absicht des OP darin bestand, ein gutes Muster für die Lösung seines Problems und die Lösung des aktuellen Problems zu finden, mit dem er in diesem Moment zu kämpfen hatte.
Ich habe viele gute Muster gesehen, die verschachtelte Try-Catch-Blöcke vermeiden , die in diesem Feed veröffentlicht wurden, aber keine Lösung für das oben genannte Problem gefunden. Hier ist also die Lösung:
Wie oben erwähnt, wollte er ein Wrapper-Objekt erstellen, das
null
bei einem Fehler zurückkehrt . Ich würde es einen Pod nennen ( ausnahmesicherer Pod ).Was aber, wenn Sie einen sicheren Pod für ein Referenztyp-Ergebnis erstellen möchten, das von CalcN () -Funktionen / -Methoden zurückgegeben wird?
Möglicherweise stellen Sie fest, dass "für jede Methode, die Sie verwenden möchten, keine Hilfsmethode geschrieben werden muss" .
Die zwei Arten von Pods (für
ValueTypeResult
s undReferenceTypeResult
s) sind ausreichend .Hier ist der Code von
SafePod
. Es ist jedoch kein Container. Stattdessen wird ein ausnahmesicherer Delegate-Wrapper fürValueTypeResult
s undReferenceTypeResult
s erstellt.So können Sie die Null-Koaleszenz nutzen Betreiber
??
mit der Kraft des kombinierten Bürger erster Klasse Einheiten (delegate
n).quelle
Sie haben Recht, jede Berechnung zu verpacken, aber Sie sollten nach dem Tell-Don't-Ask-Prinzip einbrechen.
Die Operationen sind einfach und können ihr Verhalten unabhängig ändern. Und es spielt keine Rolle, warum sie Standard sind. Als Beweis können Sie calc1DefaultingToCalc2 wie folgt implementieren:
quelle
Es hört sich so an, als hätten Ihre Berechnungen mehr gültige Informationen als nur die Berechnung selbst. Vielleicht wäre es für sie sinnvoller, ihre eigene Ausnahmebehandlung durchzuführen und eine "Ergebnis" -Klasse zurückzugeben, die Fehlerinformationen, Wertinformationen usw. enthält. Denken Sie daran, dass die AsyncResult-Klasse dem asynchronen Muster folgt. Sie können dann das tatsächliche Ergebnis der Berechnung auswerten. Sie können dies rationalisieren, indem Sie sich vorstellen, dass eine fehlgeschlagene Berechnung genauso informativ ist, als ob sie bestanden würde. Daher ist eine Ausnahme eine Information, kein "Fehler".
Natürlich frage ich mich mehr über die Gesamtarchitektur, mit der Sie diese Berechnungen durchführen.
quelle
while (!calcResult.HasValue) nextCalcResult()
, anstatt einer Liste von Calc1, Calc2, Calc3 usw.Was ist mit der Verfolgung der Aktionen, die Sie ausführen ...
quelle
track
in diesem Fall), und ich weiß genau, durch welches Segment in meinem Code der Block fehlgeschlagen ist. Vielleicht hätten Sie Mitglieder wie DeCaf darüber informieren sollen, dass dietrack
Nachricht an Ihre benutzerdefinierte Fehlerbehandlungsroutine gesendet wird, mit der Sie Ihren Code debuggen können. Klingt so, als hätte er deine Logik nicht verstanden.