Werden Ausnahmen als Kontrollfluss als ernstes Antimuster angesehen? Wenn ja warum?


129

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 ControllerEntscheidung, 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 voidFunktionen 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.



25
Nein. In Python wird die Verwendung von Ausnahmen als Kontrollfluss als "pythonisch" betrachtet.
user16764

4
Wenn ich so etwas mit Java machen würde, würde ich sicherlich keine Ausnahme machen. Ich würde von einer Nicht-Ausnahme-Nicht-Fehler-Throwable-Hierarchie ableiten.
Thomas Eding

2
Um die vorhandenen Antworten zu ergänzen, hier eine kurze Richtlinie, die mir gute Dienste geleistet hat: - Verwenden Sie niemals Ausnahmen für "den glücklichen Weg". Der Happy Path kann sowohl (für das Web) die gesamte Anfrage sein, oder einfach ein Objekt / eine Methode. Alle anderen vernünftigen Regeln gelten natürlich noch :)
Houen

8
Steuern Ausnahmen nicht immer den Ablauf einer Anwendung?

Antworten:


109

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:

  • Ausnahmen sind im Wesentlichen ausgefeilte GOTO-Anweisungen
  • Das Programmieren mit Ausnahmen führt daher dazu, dass Code schwieriger zu lesen und zu verstehen ist
  • In den meisten Sprachen gibt es Kontrollstrukturen, mit denen Sie Ihre Probleme ohne Ausnahmen lösen können
  • Argumente für die Effizienz sind für moderne Compiler eher umstritten. Diese tendieren dazu, mit der Annahme zu optimieren, dass Ausnahmen nicht für den Kontrollfluss verwendet werden.

Lesen Sie die Diskussion im Wiki von Ward, um weitere Informationen zu erhalten.


Siehe auch ein Duplikat dieser Frage hier


18
Bei Ihrer Antwort klingt es so, als wären Ausnahmen unter allen Umständen schlecht , während sich die Frage auf Ausnahmen als Ablaufsteuerung konzentriert.
Whatsisname

14
@MasonWheeler Der Unterschied besteht darin, dass for / while-Schleifen ihre Flusssteuerungsänderungen klar enthalten und den Code leicht lesbar machen. Wenn Sie eine for-Anweisung in Ihrem Code sehen, müssen Sie nicht herausfinden, welche Datei das Ende der Schleife enthält. Goto's sind nicht schlecht, weil einige Gott sagten, dass sie schlecht sind, nur weil sie schwerer zu befolgen sind als die Loop-Konstrukte. Ausnahmen sind ähnlich, nicht unmöglich, aber schwer genug, um die Dinge zu verwirren.
Bill K

24
@BillK, dann argumentieren Sie, und machen Sie keine simplen Aussagen darüber, wie Ausnahmen GOTOS sind.
Winston Ewert

6
Okay, aber im Ernst, was ist los mit serverseitigen und App-Entwicklern, die Fehler mit leeren catch-Anweisungen im JavaScript begraben? Es ist ein böses Phenol, das mich viel Zeit gekostet hat und ich weiß nicht, wie ich fragen soll, ohne mich zu beschimpfen. Fehler sind dein Freund.
Erik Reppen

12
@mattnz: ifund foreachsind auch anspruchsvolle GOTOs. 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.
Eamon Nerbonne,

110

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.


18
Dies beantwortet jedoch nicht die Frage. Wofür sie entwickelt wurden , ist irrelevant. Das einzige, was relevant ist, ist, warum es schlecht ist , sie für den Kontrollfluss zu verwenden, was ein Thema ist, das Sie nicht angesprochen haben. Zum Beispiel wurden C ++ - Templates für eine Sache entworfen, aber sie eignen sich perfekt für die Metaprogrammierung, eine Verwendung, die die Designer nie erwartet hatten.
Thomas Bonini

6
@Krelp: Die Designer haben nie viel erwartet, zum Beispiel , dass sie versehentlich ein Turing-vollständiges Vorlagensystem haben! C ++ Vorlagen sind hier kaum ein gutes Beispiel.
Mason Wheeler

15
@Krelp - C ++ - Vorlagen eignen sich NICHT für die Metaprogrammierung. Sie sind ein Albtraum, bis man sie richtig hinbekommt, und neigen dann dazu, nur Code zu schreiben, wenn man kein Vorlagengenie ist. Vielleicht möchten Sie ein besseres Beispiel auswählen.
Michael Kohne

Ausnahmen beeinträchtigen insbesondere die Funktionszusammensetzung und die Code-Kürze im Allgemeinen. ZB als ich unerfahren war, habe ich eine Ausnahme in einem Projekt verwendet und es hat lange Zeit Probleme verursacht, weil 1) die Leute daran denken müssen, sie zu fangen, 2) Sie können nicht schreiben, const myvar = theFunctionweil 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.
Hi-Angel

2
@AndreasBonini Was sie für Angelegenheiten entworfen / vorgesehen sind, da dies häufig zu Entscheidungen bezüglich der Implementierung von Compiler / Framework / Laufzeit führt, die mit dem Design in Einklang stehen. Beispielsweise kann das Auslösen einer Ausnahme viel „teurer“ sein als eine einfache Rückgabe, da Informationen gesammelt werden, die dazu dienen, den Code zu debuggen, z. B. Stack-Traces usw.
binki

29

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 GOTOrichtige 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 GOTOoder 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.)


7
Wenn wir nur eine ordnungsgemäße Tail-Call-Rekursion gehabt hätten, hätten wir vielleicht die clientseitige Webentwicklung mit JavaScript dominieren und uns dann auf die Serverseite und auf Mobilgeräte als ultimative plattformübergreifende Lösung wie Wildfire ausbreiten können. Leider war es nicht so. Verdammt, unsere unzureichenden erstklassigen Funktionen, Abschlüsse und ereignisgesteuerten Paradigmen. Was wir wirklich brauchten, war die reale Sache.
Erik Reppen

20
@ErikReppen: Mir ist klar, dass du nur sarkastisch bist, aber. . . Ich glaube wirklich nicht, dass die Tatsache, dass wir die clientseitige Webentwicklung mit JavaScript dominiert haben, etwas mit den Funktionen der Sprache zu tun hat. Es hat ein Monopol auf diesem Markt und konnte daher mit vielen Problemen durchkommen, die mit Sarkasmus nicht abgeschrieben werden können.
Ruakh

1
Die Schwanzrekursion wäre ein netter Bonus gewesen (die Funktionen werden zukünftig nicht mehr unterstützt), aber ich würde sagen, dass sie sich aus funktionsbezogenen Gründen gegen VB, Flash, Applets usw. durchgesetzt hat. Wenn es nicht die Komplexität verringert und so einfach normalisiert hätte, hätte es irgendwann echte Konkurrenz gehabt. Ich führe derzeit Node.js aus, um das Umschreiben von 21 Konfigurationsdateien für einen abscheulichen C # + -Java-Stack zu erledigen, und ich weiß, dass ich nicht der einzige da draußen bin, der das tut. Es ist sehr gut in dem, was es gut kann.
Erik Reppen

1
Viele Sprachen haben weder gotos (Goto als schädlich!) Noch Coroutinen oder Fortsetzungen und implementieren eine perfekt gültige Flusskontrolle, indem sie einfach ifs, whiles und Funktionsaufrufe (oder gosubs!) Verwenden. Die meisten davon können tatsächlich verwendet werden, um sich gegenseitig zu emulieren, genauso wie eine Fortsetzung verwendet werden kann, um all das oben Genannte darzustellen. (Zum Beispiel können Sie eine Weile verwenden, um ein if auszuführen, auch wenn es ein stinkender Weg zum Code ist). Es sind also KEINE Ausnahmen erforderlich, um eine erweiterte Flusssteuerung durchzuführen.
Shayne

2
Na ja, vielleicht hast du recht, wenn. "For" in seiner C-Form kann jedoch als umständliche Angabe verwendet werden, und eine Weile kann dann zusammen mit einem if verwendet werden, um eine endliche Zustandsmaschine zu implementieren, die dann alle anderen Flusssteuerungsformen emulieren kann. Wieder stinkender Weg zum Code, aber ja. Und wieder, ich werde als schädlich eingestuft. (Und ich würde mich auch mit der Verwendung von Ausnahmen unter nicht außergewöhnlichen Umständen streiten). Denken Sie daran, dass es absolut gültige und sehr leistungsfähige Sprachen gibt, die weder Goto noch Ausnahmen bieten und einwandfrei funktionieren.
Shayne

19

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 gotoselbst, das in der Form von breakund continuein 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:

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }

    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 eine INSERToder UPDATE-Anweisung, abhängig von den Recordinternen Flags der Methode . Von außen wissen wir nicht, welche Art von Logik enthalten ist store()(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 wir store()nur die SQL-Anweisung erzeugen wollen, nicht sie tatsächlich ausführen. Beispiel:

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }

    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 gotos genau dem, was Mason Wheeler in seiner Antwort gesagt hat:

"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, aber die Routine, die mich (oder etwas weiter oben im Aufrufstapel) aufrief, sollte wissen, wie sie damit umzugehen ist . "

Beide Verwendungen von ControlFlowExceptionswaren 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.


11

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. nullwenn 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/ behandelt rescue. Sie können jedoch throw/ catchfü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 habe int.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 throwund catchsind Schlüsselwörter mit Ausnahmen in vielen anderen Sprachen verbunden.


1
+1 für "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."
Giorgio

8

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:

status = getValue(&inout);
if (status < 0)
{
    logError("message");
    return status;
}

doSomething(*inout);

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 ifAnweisungen 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.


2
Solange solche if-Anweisungen nur unter "außergewöhnlichen" Umständen fehlschlagen, machen die Verzweigungsvorhersagelogiken moderner CPUs ihre Kosten vernachlässigbar. Dies ist ein Ort, an dem Makros tatsächlich helfen können, solange Sie vorsichtig sind und nicht versuchen, zu viel in ihnen zu tun.
James

5

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:

class GOTO(Exception):
  pass

try:
  # Do lots of stuff
  # in here with multiple exit points
  # each exit point does a "raise GOTO()"
except GOTO:
  pass
except Exception as e:
  #display error

6
Wenn Sie die Berechnung irgendwo in einer tief verschachtelten Anweisung beenden und zu einem allgemeinen Fortsetzungscode wechseln müssen, kann dieser bestimmte Ausführungspfad sehr wahrscheinlich als Funktion herausgerechnet werden, wobei returnanstelle von goto.
9000

3
@ 9000 Ich wollte gerade genau das Gleiche kommentieren ... Halte die try:Blöcke bitte auf 1 oder 2 Zeilen, niemals try: # Do lots of stuff.
Wim

1
@ 9000, in manchen Fällen sicher. Aber dann verlieren Sie den Zugriff auf lokale Variablen und verschieben Ihren Code an einen anderen Ort, wenn der Prozess ein kohärenter, linearer Prozess ist.
Gahooa

4
@ Gahooa: Früher habe ich wie du gedacht. Es war ein Zeichen für eine schlechte Struktur meines Codes. Als ich genauer darüber nachdachte, bemerkte ich, dass lokale Kontexte entwirrt werden können und das ganze Durcheinander mit wenigen Parametern, wenigen Codezeilen und einer sehr genauen Bedeutung in kurze Funktionen umgewandelt werden kann. Ich habe nie zurückgeschaut.
9000

4

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.


Hmm, ist das Abstimmen für die tatsächlich gegenteilige Position oder für unzureichende oder kurze Argumentation? Ich bin sehr neugierig.
Joop Eggen

Ich stehe genau vor dieser Zwangslage. Ich hatte gehofft, Sie könnten sehen, was Sie von meiner folgenden Frage halten. Recursively Ausnahmen mit dem Grund (Nachricht) für eine Top - Level - Ausnahme zu akkumulieren codereview.stackexchange.com/questions/107862/...
CL22

@Jodes Ich habe gerade Ihre interessante Technik gelesen, bin aber vorerst ziemlich beschäftigt. Hoffe, dass jemand anderes das Thema beleuchtet.
Joop Eggen

4

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

  • das Problem lösen
  • definitiv negative Folgen haben

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.


Es gibt eine schlimme Konsequenz, zumindest bei Sprachen, die einen vollständigen Stack-Trace bereitstellen. Da der Code stark mit viel Inlining optimiert ist, unterscheiden sich der tatsächliche Stack-Trace und der, den ein Entwickler sehen möchte, erheblich, und daher ist die Erstellung des Stack-Trace kostspielig. Übermäßiges Verwenden von Ausnahmen ist für die Leistung in solchen Sprachen (Java, C #) sehr schlecht.
Maaartinus

"Es ist eine schlechte Art, Dinge zu tun" - Sollte das nicht ausreichen, um es als Anti-Muster zu klassifizieren?
Maybe_Factor

@Maybe_Factor Gemäß der Definition eines Ameisenmusters, Nr.
MirroredFate
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.