In den späten 90ern habe ich ziemlich viel mit einer Codebasis gearbeitet, die Ausnahmen als Flusskontrolle verwendete. Es implementierte eine Finite-State-Maschine zur Steuerung von Telefonieanwendungen. In letzter Zeit werde ich an diese Tage erinnert, weil ich MVC-Web-Apps gemacht habe.
Sie haben beide die Controller
Entscheidung, wohin sie als nächstes gehen und die Daten an die Ziellogik liefern. Benutzeraktionen aus der Domäne eines Telefons der alten Schule, wie DTMF-Töne, wurden zu Parametern für Aktionsmethoden, aber anstatt etwas wie a zurückzugeben ViewResult
, warfen sie a StateTransitionException
.
Ich denke, der Hauptunterschied war, dass Aktionsmethoden void
Funktionen waren. Ich erinnere mich nicht an all die Dinge, die ich mit dieser Tatsache gemacht habe, aber ich habe gezögert, mich überhaupt an vieles zu erinnern, da ich dies seit diesem Job, wie vor 15 Jahren, bei keinem anderen Job im Produktionscode gesehen habe . Ich nahm an, dass dies ein Zeichen dafür war, dass es sich um ein sogenanntes Anti-Pattern handelte.
Ist dies der Fall und wenn ja, warum?
Update: Als ich die Frage stellte, hatte ich bereits die Antwort von @ MasonWheeler im Hinterkopf, also ging ich mit der Antwort um, die mein Wissen am meisten erweitert hat. Ich denke, das ist auch eine gute Antwort.
quelle
Antworten:
Es gibt eine ausführliche Diskussion dazu in Wards Wiki . Im Allgemeinen ist die Verwendung von Ausnahmen für die Ablaufsteuerung ein Anti-Muster, mit vielen bemerkenswerten Situation - und sprachspezifische (siehe zum Beispiel Python ) Husten Ausnahmen Husten .
Als kurze Zusammenfassung, warum es sich im Allgemeinen um ein Anti-Pattern handelt:
Lesen Sie die Diskussion im Wiki von Ward, um weitere Informationen zu erhalten.
Siehe auch ein Duplikat dieser Frage hier
quelle
if
undforeach
sind auch anspruchsvolleGOTO
s. Ehrlich gesagt finde ich den Vergleich mit goto nicht hilfreich; Es ist fast wie ein abfälliger Begriff. Die Verwendung von GOTO ist an sich nicht böse - es gibt echte Probleme, und Ausnahmen können diese Probleme teilen, aber sie können es nicht. Es wäre hilfreicher, diese Probleme zu hören.Der Anwendungsfall, für den Ausnahmen entwickelt wurden, ist "Ich bin gerade auf eine Situation gestoßen, mit der ich zu diesem Zeitpunkt nicht richtig umgehen kann, weil ich nicht genug Kontext habe, um damit umzugehen, sondern auf die Routine, die mich angerufen hat (oder auf etwas, das den Aufruf weiterführte) Stack) sollte wissen, wie man damit umgeht. "
Der sekundäre Anwendungsfall ist "Ich habe gerade einen schwerwiegenden Fehler festgestellt, und es ist jetzt wichtiger, aus diesem Kontrollfluss auszusteigen, um Datenkorruption oder andere Schäden zu verhindern, als weiterzumachen."
Wenn Sie aus einem dieser beiden Gründe keine Ausnahmen verwenden, gibt es wahrscheinlich einen besseren Weg, dies zu tun.
quelle
const myvar = theFunction
weil myvar aus try-catch erstellt werden muss, somit ist es nicht mehr konstant. Das bedeutet nicht, dass ich es nicht in C # verwende, da es aus irgendeinem Grund ein Mainstream ist, aber ich versuche es trotzdem zu reduzieren.Ausnahmen sind so mächtig wie Fortsetzungen und
GOTO
. Sie sind ein universelles Kontrollflusskonstrukt.In einigen Sprachen sind sie das einzige universelle Kontrollflusskonstrukt. JavaScript hat zum Beispiel weder Fortsetzungen noch
GOTO
richtige Schwanzaufrufe. Also, wenn Sie anspruchsvolle Steuerungsablauf in JavaScript implementieren möchten, Sie haben zu Ausnahmen verwenden.Das Microsoft Volta-Projekt war ein (inzwischen eingestelltes) Forschungsprojekt zum Kompilieren von beliebigem .NET-Code in JavaScript. .NET hat Ausnahmen, deren Semantik nicht genau JavaScript entspricht, aber was noch wichtiger ist, es hat Threads und Sie müssen diese irgendwie JavaScript zuordnen. Volta hat dazu Volta Continuations mithilfe von JavaScript-Ausnahmen implementiert und anschließend alle .NET-Kontrollflusskonstrukte in Bezug auf Volta Continuations implementiert. Sie mussten Ausnahmen als Kontrollfluss verwenden, da es kein anderes Kontrollflusskonstrukt gibt, das mächtig genug ist.
Sie erwähnten State Machines. SMs sind mit Proper Tail Calls trivial zu implementieren: Jeder Zustand ist eine Subroutine, jeder Zustandsübergang ist ein Subroutinenaufruf. SMs können auch problemlos mit
GOTO
oder Coroutines oder Continuations implementiert werden . Java hat jedoch keine dieser vier, aber es hat Ausnahmen. Es ist also durchaus akzeptabel, diese als Kontrollfluss zu verwenden. (Nun, eigentlich wäre es wahrscheinlich die richtige Wahl, eine Sprache mit dem richtigen Kontrollflusskonstrukt zu verwenden, aber manchmal kann es vorkommen, dass Sie mit Java nicht weiterkommen.)quelle
Wie bereits mehrfach erwähnt ( z. B. in dieser Frage zum Stapelüberlauf ), verbietet das Prinzip des geringsten Erstaunens , dass Sie Ausnahmen nur zu Kontrollzwecken übermäßig verwenden. Andererseits ist keine Regel zu 100% korrekt, und es gibt immer wieder Fälle, in denen eine Ausnahme "genau das richtige Werkzeug" ist - im Übrigen ähnlich wie bei sich
goto
selbst, das in der Form vonbreak
undcontinue
in Sprachen wie Java geliefert wird, welche sind oft der perfekte Weg, um aus stark verschachtelten Schleifen herauszuspringen, was nicht immer zu vermeiden ist.Der folgende Blog-Beitrag erklärt einen recht komplexen, aber auch interessanten Anwendungsfall für einen Nicht-Lokalen
ControlFlowException
:Es wird erklärt, wie in jOOQ (einer SQL-Abstraktionsbibliothek für Java) (Haftungsausschluss: Ich arbeite für den Hersteller) solche Ausnahmen gelegentlich verwendet werden, um den SQL-Rendering-Prozess vorzeitig abzubrechen, wenn eine "seltene" Bedingung erfüllt ist.
Beispiele für solche Bedingungen sind:
Es sind zu viele Bindungswerte aufgetreten. Einige Datenbanken unterstützen keine beliebige Anzahl von Bindungswerten in ihren SQL-Anweisungen (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). In diesen Fällen bricht jOOQ die SQL-Rendering-Phase ab und rendert die SQL-Anweisung mit eingebetteten Bindungswerten erneut. Beispiel:
Wenn wir die Bindungswerte explizit aus der Abfrage AST extrahieren, um sie jedes Mal zu zählen, würden wir wertvolle CPU-Zyklen für die 99,9% der Abfragen verschwenden, die nicht unter diesem Problem leiden.
Einige Logik ist nur indirekt über eine API verfügbar, die wir nur "teilweise" ausführen möchten. Die
UpdatableRecord.store()
Methode generiert eineINSERT
oderUPDATE
-Anweisung, abhängig von denRecord
internen Flags der Methode . Von außen wissen wir nicht, welche Art von Logik enthalten iststore()
(z. B. optimistisches Sperren, Behandlung von Ereignis-Listenern usw.), daher möchten wir diese Logik nicht wiederholen, wenn wir mehrere Datensätze in einer Batch-Anweisung speichern. Wobei wirstore()
nur die SQL-Anweisung erzeugen wollen, nicht sie tatsächlich ausführen. Beispiel:Wenn wir die
store()
Logik in eine "wiederverwendbare" API ausgelagert hätten, die so angepasst werden kann, dass die SQL optional nicht ausgeführt wird, würden wir versuchen, eine ziemlich schwer zu wartende, kaum wiederverwendbare API zu erstellen.Fazit
Im Wesentlichen entspricht unsere Verwendung dieser nicht-lokalen
goto
s genau dem, was Mason Wheeler in seiner Antwort gesagt hat:Beide Verwendungen von
ControlFlowExceptions
waren im Vergleich zu ihren Alternativen ziemlich einfach zu implementieren, sodass wir eine breite Palette von Logik wieder verwenden konnten, ohne sie aus den relevanten Interna heraus umzugestalten.Das Gefühl, dass dies für zukünftige Betreuer eine kleine Überraschung ist, bleibt jedoch bestehen. Der Code fühlt sich ziemlich heikel an, und obwohl es in diesem Fall die richtige Wahl war, würden wir es immer vorziehen, keine Ausnahmen für den Ablauf der lokalen Steuerung zu verwenden, bei denen es einfach ist, gewöhnliche Verzweigungen zu vermeiden
if - else
.quelle
Die Verwendung von Ausnahmen für den Kontrollfluss wird im Allgemeinen als Antimuster angesehen, es gibt jedoch Ausnahmen (kein Wortspiel beabsichtigt).
Es wurde tausendmal gesagt, dass Ausnahmen für außergewöhnliche Bedingungen gedacht sind. Eine unterbrochene Datenbankverbindung ist eine Ausnahmebedingung. Ein Benutzer, der Buchstaben in ein Eingabefeld eingibt, in das nur Zahlen eingegeben werden sollen, hat keine Berechtigung .
Ein Fehler in Ihrer Software, der dazu führt, dass eine Funktion mit unzulässigen Argumenten aufgerufen wird, z. B.
null
wenn dies nicht zulässig ist, ist eine Ausnahmebedingung.Indem Sie Ausnahmen für etwas verwenden, das nicht außergewöhnlich ist, verwenden Sie unangemessene Abstraktionen für das Problem, das Sie lösen möchten.
Es kann aber auch eine Leistungsminderung geben. Einige Sprachen verfügen über eine mehr oder weniger effiziente Implementierung zur Ausnahmebehandlung. Wenn Ihre Sprache also keine effiziente Ausnahmebehandlung bietet, kann dies in Bezug auf die Leistung * sehr kostspielig sein.
Andere Sprachen, z. B. Ruby, verfügen jedoch über eine Ausnahme-ähnliche Syntax für den Kontrollfluss. Ausnahmesituationen werden von den Operatoren
raise
/ behandeltrescue
. Sie können jedochthrow
/catch
für ausnahmeartige Kontrollflusskonstrukte ** verwenden.Obwohl Ausnahmen im Allgemeinen nicht für den Kontrollfluss verwendet werden, kann Ihre bevorzugte Sprache andere Redewendungen haben.
* Ein Beispiel für eine leistungsintensive Verwendung von Ausnahmen: Ich war einmal eingestellt, um eine ASP.NET Web Form-Anwendung mit schlechter Leistung zu optimieren. Es stellte sich heraus, dass das Rendern eines großen Tisches
int.Parse()
ca. Auf einer durchschnittlichen Seite befinden sich 1000 leere Zeichenfolgen. Tausend Ausnahmen werden behandelt. Durch Ersetzen des Codes durch habeint.TryParse()
ich mich eine Sekunde rasiert! Für jede einzelne Seitenanfrage!** Dies kann für einen Programmierer sehr verwirrend sein aus anderen Sprachen Ruby kommen, da beide
throw
undcatch
sind Schlüsselwörter mit Ausnahmen in vielen anderen Sprachen verbunden.quelle
Es ist völlig möglich, Fehlerzustände ohne die Verwendung von Ausnahmen zu behandeln. Einige Sprachen, insbesondere C, haben nicht einmal Ausnahmen, und die Leute schaffen es immer noch, damit recht komplexe Anwendungen zu erstellen. Der Grund, warum Ausnahmen nützlich sind, besteht darin, dass Sie zwei im Wesentlichen unabhängige Kontrollflüsse im selben Code genau angeben können: einen, wenn ein Fehler auftritt, und einen, wenn dies nicht der Fall ist. Ohne sie erhalten Sie überall Code, der so aussieht:
Oder eine Entsprechung in Ihrer Sprache, z. B. das Zurückgeben eines Tupels mit einem Wert als Fehlerstatus usw. Häufig vernachlässigen Personen, die darauf hinweisen, wie "teuer" die Ausnahmebehandlung ist, alle zusätzlichen
if
Anweisungen wie die oben genannten, die Sie hinzufügen müssen, wenn Sie keine ' Verwenden Sie keine Ausnahmen.Während dieses Muster am häufigsten bei der Behandlung von Fehlern oder anderen "Ausnahmebedingungen" auftritt, haben Sie meiner Meinung nach ein ziemlich gutes Argument für die Verwendung von Ausnahmen, wenn Sie unter anderen Umständen einen solchen Boilerplate-Code sehen. Abhängig von der Situation und der Implementierung kann ich sehen, dass Ausnahmen in einer Zustandsmaschine gültig verwendet werden, da Sie zwei orthogonale Kontrollflüsse haben: einen, der den Zustand ändert, und einen für die Ereignisse, die innerhalb der Zustände auftreten.
Diese Situationen sind jedoch selten, und wenn Sie eine Ausnahme (Wortspiel beabsichtigt) von der Regel machen möchten, sollten Sie besser darauf vorbereitet sein, ihre Überlegenheit gegenüber anderen Lösungen zu demonstrieren. Eine Abweichung ohne eine solche Begründung wird zu Recht als Antimuster bezeichnet.
quelle
In Python werden Ausnahmen für die Beendigung des Generators und der Iteration verwendet. Python hat sehr effiziente try / except-Blöcke, aber das Auslösen einer Ausnahme ist mit einem gewissen Overhead verbunden.
Aufgrund des Fehlens von mehrstufigen Unterbrechungen oder einer goto-Anweisung in Python habe ich gelegentlich Ausnahmen verwendet:
quelle
return
anstelle vongoto
.try:
Blöcke bitte auf 1 oder 2 Zeilen, niemalstry: # Do lots of stuff
.Skizzieren wir eine solche Ausnahmeverwendung:
Der Algorithmus sucht rekursiv, bis etwas gefunden wurde. Wenn man also von der Rekursion zurückkehrt, muss man das Ergebnis überprüfen, um es zu finden, und dann zurückkehren, andernfalls fortfahren. Und das immer wieder aus einer gewissen Rekursionstiefe.
Außerdem wird ein zusätzlicher Boolescher Wert benötigt
found
(um in eine Klasse gepackt zu werden, in der sonst möglicherweise nur ein int zurückgegeben worden wäre), und für die Rekursionstiefe geschieht dasselbe Postlude.Ein solches Abwickeln eines Aufrufstapels ist genau das, wofür eine Ausnahme besteht. Es scheint mir also ein nicht-goto-artiges, unmittelbareres und angemessenes Mittel zur Codierung zu sein. Nicht nötig, seltener Gebrauch, vielleicht schlechter Stil, aber auf den Punkt gebracht. Vergleichbar mit dem Prologschnitt.
quelle
Beim Programmieren geht es um Arbeit
Ich denke, der einfachste Weg, dies zu beantworten, besteht darin, die Fortschritte zu verstehen, die OOP im Laufe der Jahre gemacht hat. Alles, was in OOP (und in den meisten Programmierparadigmen) gemacht wird, basiert darauf, dass die Arbeit erledigt werden muss .
Jedes Mal, wenn eine Methode aufgerufen wird, sagt der Aufrufer: "Ich weiß nicht, wie diese Arbeit zu erledigen ist, aber Sie wissen, wie, also tun Sie es für mich."
Dies stellte eine Schwierigkeit dar: Was passiert, wenn die aufgerufene Methode im Allgemeinen weiß, wie die Arbeit zu erledigen ist, aber nicht immer? Wir brauchten einen Weg zu kommunizieren "Ich wollte dir helfen, das habe ich wirklich getan, aber das kann ich einfach nicht."
Eine frühe Methode, dies zu kommunizieren, bestand darin, einfach einen "Müll" -Wert zurückzugeben. Möglicherweise erwarten Sie eine positive Ganzzahl, sodass die aufgerufene Methode eine negative Zahl zurückgibt. Eine andere Möglichkeit, dies zu erreichen, bestand darin, irgendwo einen Fehlerwert festzulegen. Unglücklicherweise führten beide Wege zu einem Kesselschild , auf dem ich nachschauen musste , um sicherzustellen, dass alles koscher war . Wenn die Dinge komplizierter werden, fällt dieses System auseinander.
Eine außergewöhnliche Analogie
Angenommen, Sie haben einen Tischler, einen Klempner und einen Elektriker. Sie möchten Ihr Waschbecken reparieren, damit er es sich ansieht. Es ist nicht sehr nützlich, wenn er nur dir sagt: "Entschuldigung, ich kann es nicht reparieren. Es ist kaputt." Verdammt, es ist noch schlimmer, wenn er einen Blick darauf wirft, geht und dir einen Brief schickt, in dem er sagt, er könne ihn nicht reparieren. Jetzt müssen Sie Ihre E-Mails überprüfen, bevor Sie überhaupt wissen, dass er nicht das getan hat, was Sie wollten.
Was Sie bevorzugen, ist, dass er Ihnen sagt: "Sehen Sie, ich konnte es nicht reparieren, weil es so aussieht, als ob Ihre Pumpe nicht funktioniert."
Mit diesen Informationen können Sie den Schluss ziehen, dass der Elektriker einen Blick auf das Problem werfen soll. Vielleicht findet der Elektriker etwas, das mit dem Schreiner zu tun hat, und Sie müssen es vom Schreiner reparieren lassen.
Vielleicht wissen Sie gar nicht, dass Sie einen Elektriker brauchen, vielleicht wissen Sie auch nicht, wen Sie brauchen. Sie sind nur das mittlere Management eines Reparaturunternehmens, und Ihr Fokus liegt auf dem Sanitärbereich. Also sagen Sie , Sie sind Chef über das Problem, und dann er sagt dem Elektriker , es zu beheben.
Dies sind die Ausnahmen, die modelliert werden: Komplexe Fehlermodi werden entkoppelt. Der Klempner muss nichts über Elektriker wissen - er muss nicht einmal wissen, dass jemand in der Kette das Problem beheben kann. Er berichtet nur über das Problem, auf das er gestoßen ist.
Also ... ein Anti-Muster?
Ok, also ist es der erste Schritt , den Punkt der Ausnahmen zu verstehen . Das nächste ist zu verstehen, was ein Anti-Pattern ist.
Um sich als Anti-Pattern zu qualifizieren, muss es so sein
Der erste Punkt ist leicht zu erfüllen - das System hat funktioniert, oder?
Der zweite Punkt ist klebriger. Der Hauptgrund für die Verwendung von Ausnahmen als normalen Kontrollfluss ist, dass dies nicht deren Zweck ist. Jede Funktion in einem Programm sollte einen relativ klaren Zweck haben, und die Auswahl dieses Zwecks führt zu unnötiger Verwirrung.
Aber das ist kein definitiver Schaden. Es ist eine schlechte Art und Weise, Dinge zu tun, und seltsam, aber ein Anti-Muster? Nein, nur ... seltsam.
quelle