Sind Fehlervariablen ein Anti-Pattern oder ein gutes Design?

44

Um mehrere mögliche Fehler zu behandeln, die die Ausführung nicht anhalten sollten, habe ich eine errorVariable, die Clients überprüfen und zum Auslösen von Ausnahmen verwenden können. Ist das ein Anti-Pattern? Gibt es eine bessere Möglichkeit, damit umzugehen? Ein Beispiel dafür finden Sie in der mysqli- API von PHP . Angenommen, Sichtbarkeitsprobleme (Accessoren, öffentlicher und privater Bereich, ist die Variable in einer Klasse oder global?) Werden korrekt behandelt.

Mikayla Maki
quelle
6
Das, wofür try/ catchexistiert. Darüber hinaus können Sie Ihren try/ catchviel weiter oben im Stapel an einem geeigneten Ort ablegen, um ihn zu handhaben (was eine stärkere Trennung von Bedenken ermöglicht).
jpmc26
Beachten Sie Folgendes: Wenn Sie die ausnahmebasierte Behandlung verwenden und eine Ausnahme erhalten, möchten Sie dem Benutzer nicht zu viele Informationen anzeigen. Verwenden Sie einen Fehlerhandler wie Elmah oder Raygun.io, um ihn abzufangen und dem Benutzer eine allgemeine Fehlermeldung anzuzeigen. Zeigen Sie dem Benutzer NIEMALS einen Stack-Trace oder eine bestimmte Fehlermeldung an, da diese Informationen über die Funktionsweise der App enthalten, die missbraucht werden können.
Nzall
4
@Nate Ihr Hinweis gilt nur für sicherheitskritische Anwendungen, bei denen der Benutzer nicht vertrauenswürdig ist. Vage Fehlermeldungen sind selbst ein Anti-Pattern. Senden von Fehlerberichten über das Netzwerk ohne die ausdrückliche Zustimmung des Benutzers.
Piedar
3
@piedar Ich habe eine separate Frage erstellt, bei der dies freier diskutiert werden kann: programmers.stackexchange.com/questions/245255/…
Nzall
26
Ein allgemeines Prinzip beim API-Design, das Sie weit bringt, besteht darin, immer zu schauen, was PHP tut, und dann genau das Gegenteil zu tun.
Philipp

Antworten:

65

Wenn eine Sprache von Natur aus Ausnahmen unterstützt, wird es bevorzugt, Ausnahmen auszulösen, und die Clients können die Ausnahme abfangen, wenn sie nicht möchten, dass dies zu einem Fehler führt. Tatsächlich erwarten die Clients Ihres Codes Ausnahmen und es treten viele Fehler auf, da sie die Rückgabewerte nicht überprüfen.

Die Verwendung von Ausnahmen bietet eine Reihe von Vorteilen, wenn Sie die Wahl haben.

Mitteilungen

Ausnahmen enthalten vom Benutzer lesbare Fehlermeldungen, die von den Entwicklern zum Debuggen verwendet oder den Benutzern auf Wunsch sogar angezeigt werden können. Wenn der konsumierende Code die Ausnahme nicht verarbeiten kann, kann er sie immer protokollieren, sodass die Entwickler die Protokolle durchgehen können, ohne bei jeder anderen Ablaufverfolgung anhalten zu müssen, um den Rückgabewert zu ermitteln und ihn in einer Tabelle zuzuordnen, um herauszufinden, um was es sich handelt tatsächliche Ausnahme.

Bei Rückgabewerten können auf einfache Weise keine zusätzlichen Informationen bereitgestellt werden. Einige Sprachen unterstützen das Tätigen von Methodenaufrufen, um die letzte Fehlermeldung zu erhalten, daher wird diese Sorge ein wenig gemildert. Dies erfordert jedoch, dass der Aufrufer zusätzliche Aufrufe durchführt, und manchmal den Zugriff auf ein "spezielles Objekt", das diese Informationen enthält.

Im Falle von Ausnahmemeldungen gebe ich so viel Kontext wie möglich an, wie zum Beispiel:

Für den Benutzer "bar", auf den im Benutzerprofil verwiesen wurde, konnte keine Richtlinie mit dem Namen "foo" abgerufen werden.

Vergleichen Sie dies mit einem Rückkehrcode -85. Welches würdest du bevorzugen?

Stapel aufrufen

Ausnahmen haben in der Regel auch detaillierte Aufruflisten, mit deren Hilfe Code schneller und schneller debuggt werden kann. Auf Wunsch können sie auch vom aufrufenden Code protokolliert werden. Dies ermöglicht es den Entwicklern, das Problem in der Regel auf die genaue Linie genau zu lokalisieren, und ist daher sehr leistungsfähig. Vergleichen Sie dies erneut mit einer Protokolldatei mit Rückgabewerten (z. B. -85, 101, 0 usw.). Welchen bevorzugen Sie?

Scheitern Sie schnell voreingenommen Ansatz

Wenn eine Methode an einer fehlgeschlagenen Stelle aufgerufen wird, wird eine Ausnahme ausgelöst. Der aufrufende Code muss die Ausnahme entweder explizit unterdrücken, oder sie schlägt fehl. Ich fand das wirklich erstaunlich, weil der Code während der Entwicklung und des Testens (und sogar in der Produktion) schnell ausfällt und die Entwickler gezwungen sind, ihn zu reparieren. Wenn bei Rückgabewerten die Überprüfung auf einen Rückgabewert fehlschlägt, wird der Fehler unbemerkt ignoriert und der Fehler tritt an einer unerwarteten Stelle auf, was in der Regel einen viel höheren Aufwand beim Debuggen und Beheben verursacht.

Ein- und Auspacken von Ausnahmen

Ausnahmen können in andere Ausnahmen eingeschlossen und bei Bedarf wieder entfernt werden. Beispielsweise kann Ihr Code ArgumentNullExceptionden aufrufenden Code in einen Zeilenumbruch setzen, UnableToRetrievePolicyExceptionda dieser Vorgang im aufrufenden Code fehlgeschlagen ist. Während dem Benutzer möglicherweise eine Meldung angezeigt wird, die dem oben angegebenen Beispiel ähnelt, wird die Ausnahme möglicherweise durch einen Diagnosecode entpackt, der feststellt, ArgumentNullExceptiondass das Problem durch einen verursacht wurde. Dies bedeutet, dass es sich um einen Codierungsfehler im Code Ihres Verbrauchers handelt. Dies könnte dann eine Warnung auslösen, damit der Entwickler den Code korrigieren kann. Solche erweiterten Szenarien sind mit den Rückgabewerten nicht einfach zu implementieren.

Einfachheit des Codes

Dieser ist etwas schwieriger zu erklären, aber ich habe durch diese Codierung sowohl mit Rückgabewerten als auch mit Ausnahmen gelernt. Der Code, der unter Verwendung von Rückgabewerten geschrieben wurde, rief normalerweise auf und überprüfte dann in einer Reihe von Schritten, wie hoch der Rückgabewert war. In einigen Fällen wird eine andere Methode aufgerufen und es werden nun weitere Überprüfungen auf die Rückgabewerte dieser Methode durchgeführt. Mit Ausnahmen ist die Ausnahmebehandlung in den meisten, wenn nicht allen Fällen viel einfacher. Sie haben einen try / catch / finally-Block, wobei die Laufzeitumgebung versucht, den Code in den finally-Blöcken zur Bereinigung auszuführen. Sogar verschachtelte try / catch / finally-Blöcke sind relativ einfacher zu verfolgen und zu verwalten als verschachtelte if / else-Blöcke und zugehörige Rückgabewerte aus mehreren Methoden.

Fazit

Wenn die von Ihnen verwendete Plattform Ausnahmen unterstützt (z. B. Java oder .NET), sollten Sie auf jeden Fall davon ausgehen, dass es keine andere Möglichkeit gibt, Ausnahmen auszulösen, da diese Plattformen Richtlinien zum Auslösen von Ausnahmen enthalten und Ihre Clients dies erwarten damit. Wenn ich Ihre Bibliothek verwenden würde, würde ich nicht die Mühe machen, die Rückgabewerte zu überprüfen, da ich davon ausgehe, dass Ausnahmen ausgelöst werden. So ist die Welt auf diesen Plattformen.

Wenn es jedoch C ++ wäre, wäre es etwas schwieriger zu bestimmen, da bereits eine große Codebasis mit Rückgabecodes vorhanden ist und eine große Anzahl von Entwicklern so eingestellt ist, dass sie Werte im Gegensatz zu Ausnahmen zurückgeben (z. B. Windows ist voll mit HRESULTs). . Darüber hinaus kann es in vielen Anwendungen auch zu Leistungsproblemen kommen (oder zumindest als solche wahrgenommen werden).

Omer Iqbal
quelle
5
Windows gibt HRESULT-Werte von C ++ - Funktionen zurück, um die C-Kompatibilität in den öffentlichen APIs aufrechtzuerhalten (und weil Sie in eine Welt voller Verletzungen geraten, in der Sie versuchen, Ausnahmen über Grenzen hinweg zu marshallen). Folgen Sie nicht blind dem Modell des Betriebssystems, wenn Sie eine Anwendung schreiben.
Cody Grey
2
Das einzige, was ich hinzufügen möchte, ist die Erwähnung einer lockeren Kopplung. Mit Ausnahmen können Sie viele unerwartete Situationen an der am besten geeigneten Stelle behandeln. Zum Beispiel in einer Web - Anwendung, mögen Sie ein 500 auf zurückkehren jede Ausnahme Code wurde nicht vorbereitet, anstatt den Web - App zum Absturz bringen. Sie benötigen also eine Art catch all oben in Ihrem Code (oder in Ihrem Framework). Eine ähnliche Situation besteht in Desktop-GUIs. Sie können jedoch auch weniger allgemeine Handler an verschiedenen Stellen im Code platzieren, um eine Vielzahl von Fehlersituationen auf eine Weise zu behandeln, die für den aktuellen Prozess geeignet ist.
jpmc26
2
@TrentonMaki Wenn Sie von Fehlern von C ++ - Konstruktoren sprechen, finden Sie hier die beste Antwort: parashift.com/c++-faq-lite/ctors-can-throw.html . Kurz gesagt, werfen Sie eine Ausnahme, aber denken Sie daran, mögliche Lecks zuerst zu beseitigen. Ich kenne keine anderen Sprachen, in denen es schlecht ist, nur direkt von einem Konstruktor zu werfen. Ich denke, die meisten Benutzer einer API würden lieber Ausnahmen abfangen, als Fehlercodes zu überprüfen.
Ogre Psalm33
3
"Solche erweiterten Szenarien sind mit den Rückgabewerten nicht einfach zu implementieren." Sicher kannst du! Alles, was Sie tun müssen, ist eine ErrorStateReturnVariableSuperklasse zu erstellen , und eine ihrer Eigenschaften ist InnerErrorState(was eine Instanz von ist ErrorStateReturnVariable), welche implementierenden Unterklassen eine Kette von Fehlern anzeigen können ... oh, warten Sie. : p
Brian S
5
Nur weil eine Sprache Ausnahmen unterstützt, sind sie kein Allheilmittel. Ausnahmen führen versteckte Ausführungspfade ein, und daher müssen ihre Auswirkungen richtig kontrolliert werden. try / catch sind leicht hinzuzufügen, aber es ist schwer , die Wiederherstellung richtig zu machen , ...
Matthieu M.
21

Fehlervariablen sind ein Relikt aus Sprachen wie C, in denen Ausnahmen nicht verfügbar waren. Heute sollten Sie sie vermeiden, außer wenn Sie eine Bibliothek schreiben, die möglicherweise von einem C-Programm (oder einer ähnlichen Sprache ohne Ausnahmebehandlung) verwendet wird.

Wenn Sie einen Fehlertyp haben, der besser als "Warnung" klassifiziert werden könnte (= Ihre Bibliothek kann ein gültiges Ergebnis liefern und der Anrufer kann die Warnung ignorieren, wenn er denkt, dass dies nicht wichtig ist), dann eine Statusanzeige in Form einer Variablen kann auch in Sprachen mit Ausnahmen sinnvoll sein. Aber Vorsicht. Aufrufer der Bibliothek neigen dazu, solche Warnungen zu ignorieren, auch wenn sie dies nicht sollten. Überlegen Sie also zweimal, bevor Sie ein solches Konstrukt in Ihre Bibliothek aufnehmen.

Doc Brown
quelle
1
Danke für die Erklärung! Ihre Antwort + Omer Iqbal hat meine Frage beantwortet.
Mikayla Maki
Eine andere Möglichkeit, mit "Warnungen" umzugehen, besteht darin, standardmäßig eine Ausnahme auszulösen und eine Art optionales Flag zu haben, um zu verhindern, dass die Ausnahme ausgelöst wird.
Cyanfish
1
@Cyanfish: Ja, aber man muss aufpassen, dass man solche Dinge nicht überarbeitet, besonders wenn man Bibliotheken erstellt. Besser einen einfachen und funktionierenden Warnmechanismus als 2, 3 oder mehr.
Doc Brown
Eine Ausnahme sollte nur ausgelöst werden, wenn ein fehlerhaftes, unbekanntes oder nicht behebbares Szenario aufgetreten ist - außergewöhnliche Umstände . Sie sollten mit Leistungseinbußen rechnen, wenn Sie Ausnahmen
erstellen
@Gusdor: absolut, deshalb sollte eine typische "Warnung" IMHO standardmäßig keine Ausnahme auslösen. Dies hängt aber auch ein wenig von der Abstraktionsebene ab. Manchmal kann ein Dienst oder eine Bibliothek einfach nicht entscheiden, ob ein ungewöhnliches Ereignis aus Sicht des Anrufers als Ausnahme behandelt werden soll. Persönlich bevorzuge ich in solchen Situationen, dass die lib nur ein Warnkennzeichen setzt (keine Ausnahme), dass der Anrufer dieses Kennzeichen testet und eine Ausnahme auslöst, wenn er dies für angemessen hält. Das ist , ist das, was ich im Sinn hatte , als ich oben geschrieben habe „es ist besser zu liefern einen Warnmechanismus in einem libary“.
Doc Brown
20

Es gibt mehrere Möglichkeiten, einen Fehler anzuzeigen:

  • eine zu überprüfende Fehlervariable: C , Go , ...
  • eine Ausnahme: Java , C # , ...
  • ein "Condition" -Handler: Lisp (nur?), ...
  • eine polymorphe Rückkehr: Haskell , ML , Rust , ...

Das Problem der Fehlervariable besteht darin, dass das Überprüfen leicht vergessen wird.

Das Problem bei Ausnahmen besteht darin, dass verborgene Ausführungspfade erstellt werden. Obwohl try / catch einfach zu schreiben ist, ist es wirklich schwierig, eine ordnungsgemäße Wiederherstellung in der catch-Klausel sicherzustellen (keine Unterstützung von Typsystemen / Compilern).

Das Problem von Condition Handlern ist, dass sie nicht gut zusammengesetzt sind: Wenn Sie dynamischen Code ausführen (virtuelle Funktionen), ist es unmöglich vorherzusagen, welche Bedingungen behandelt werden sollen. Wenn derselbe Zustand an mehreren Stellen hervorgerufen werden kann, kann nicht gesagt werden, dass jedes Mal eine einheitliche Lösung angewendet werden kann, und es wird schnell unordentlich.

Polymorphe Renditen ( Either a bin Haskell) sind meine bisherige Lieblingslösung:

  • explizit: kein versteckter Ausführungsweg
  • explizit: vollständig dokumentiert in Funktionstyp Signatur (keine Überraschungen)
  • Schwer zu ignorieren: Sie müssen eine Musterübereinstimmung durchführen, um das gewünschte Ergebnis zu erzielen, und den Fehlerfall behandeln

Das einzige Problem ist, dass sie möglicherweise zu übermäßiger Überprüfung führen können. Die Sprachen, in denen sie verwendet werden, haben Idiome, um die Aufrufe der Funktionen zu verketten, die sie verwenden. Möglicherweise ist jedoch noch ein bisschen mehr Tipparbeit / Unordnung erforderlich. In Haskell wären dies Monaden ; Dies ist jedoch weitaus beängstigender, als es sich anhört (siehe Eisenbahnorientierte Programmierung) .

Matthieu M.
quelle
1
Gute Antwort! Ich hatte gehofft, ich könnte eine Liste von Möglichkeiten zum Behandeln von Fehlern erhalten.
Mikayla Maki
Arrghhhh! Wie oft habe ich dies gesehen "Das Problem der Fehlervariable ist, dass es leicht ist, das Überprüfen zu vergessen". Das Problem mit Ausnahmen ist, dass man leicht vergisst, es zu fangen. Dann stürzt Ihre Anwendung ab. Ihr Chef möchte keine Kosten dafür bezahlen, aber Ihre Kunden verwenden Ihre App nicht mehr, weil sie von den Abstürzen frustriert sind. Alles wegen etwas, das die Programmausführung nicht beeinträchtigt hätte, wenn Fehlercodes zurückgegeben und ignoriert worden wären. Das einzige Mal, dass ich gesehen habe, wie Leute Fehlercodes ignorierten, war, als es nicht so wichtig war. Code weiter oben in der Kette weiß, dass etwas schief gelaufen ist.
Dunk
1
@Dunk: Ich glaube nicht, dass sich meine Antwort für Ausnahmen entschuldigt. Dies hängt jedoch möglicherweise von der Art des Codes ab, den Sie schreiben. Meine persönliche Berufserfahrung tendiert dazu, Systeme zu bevorzugen, die bei Vorhandensein von Fehlern ausfallen , da eine stille Datenbeschädigung schlimmer (und unentdeckt) ist und die Daten, an denen ich arbeite, für den Kunden wertvoll sind (dies bedeutet natürlich auch dringende Korrekturen).
Matthieu M.
Es geht nicht um stille Datenkorruption. Es geht darum, Ihre App zu verstehen und zu wissen, wann und wo Sie überprüfen müssen, ob eine Operation erfolgreich war oder nicht. In vielen Fällen kann es zu Verzögerungen bei der Ermittlung und Behandlung kommen. Es sollte nicht jemand anderem überlassen sein, mir mitzuteilen, wann ich eine fehlgeschlagene Operation ausführen muss, die eine Ausnahme erfordert. Es ist meine App, ich weiß, wann und wo ich relevante Probleme behandeln möchte. Wenn Leute Apps schreiben, die Daten beschädigen könnten, dann ist das nur eine wirklich schlechte Arbeit. Das Schreiben von Apps, die abstürzen (was ich oft sehe), macht auch einen schlechten Job.
Dunk
12

Ich finde es schrecklich. Ich überarbeite gerade eine Java-App, die Rückgabewerte anstelle von Ausnahmen verwendet. Obwohl Sie möglicherweise überhaupt nicht mit Java arbeiten, gilt dies meines Erachtens dennoch.

Sie erhalten folgenden Code:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

Oder dieses:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

Ich möchte lieber, dass die Aktionen selbst Ausnahmen auslösen, sodass Sie am Ende Folgendes erhalten:

x.doActionA();
x.doActionB();

Sie können dies in einen Try-Catch einwickeln und die Meldung aus der Ausnahme abrufen oder die Ausnahme ignorieren, wenn Sie beispielsweise etwas löschen, das möglicherweise bereits nicht mehr vorhanden ist. Außerdem wird die Stapelverfolgung beibehalten, sofern Sie eine haben. Auch die Methoden selbst werden einfacher. Anstatt die Ausnahmen selbst zu behandeln, werfen sie nur das, was schief gelaufen ist.

Aktueller (schrecklicher) Code:

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

Neu und verbessert:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

Strack-Trace bleibt erhalten und die Nachricht ist in der Ausnahme verfügbar, anstatt das nutzlose "Etwas ist schief gelaufen!".

Natürlich können Sie bessere Fehlermeldungen liefern, und das sollten Sie auch. Aber dieser Beitrag ist hier, weil der aktuelle Code, an dem ich arbeite, schmerzhaft ist und Sie nicht das Gleiche tun sollten.

mrjink
quelle
1
Allerdings gibt es Situationen, in denen Ihr "Neues und Verbessertes" den Kontext verlieren würde, in dem die Ausnahme anfänglich auftritt. In der "aktuellen (schrecklichen)) Version von doActionA () hätte die catch-Klausel beispielsweise Zugriff auf Instanzvariablen und andere Informationen aus dem umschließenden Objekt, um eine nützlichere Nachricht zu erhalten.
InformedA
1
Das stimmt, passiert aber derzeit nicht. Und Sie können die Ausnahme jederzeit in doActionA abfangen und in eine andere Ausnahme mit einer Statusmeldung einschließen. Dann haben Sie immer noch die Stapelverfolgung und die nützliche Nachricht. throw new Exception("Something went wrong with " + instanceVar, ex);
Mrjink
Ich stimme zu, es könnte in Ihrer Situation nicht passieren. Sie können die Informationen jedoch nicht "immer" in doActionA () einfügen. Warum? Der Aufrufer von doActionA () ist möglicherweise der einzige, der die Informationen enthält, die Sie einschließen müssen.
InformedA
2
Bitten Sie den Aufrufer, ihn einzuschließen, wenn er die Ausnahme behandelt. Gleiches gilt jedoch für die ursprüngliche Frage. Sie können jetzt nichts tun, was Sie mit Ausnahmen nicht tun können, und dies führt zu saubererem Code. Ich bevorzuge Ausnahmen gegenüber zurückgegebenen Booleschen Werten oder Fehlermeldungen.
Mrjink
Sie gehen in der Regel von der falschen Annahme aus, dass eine Ausnahme, die innerhalb einer der "someOperation" auftritt, auf dieselbe Weise behandelt und bereinigt werden kann. Im wirklichen Leben müssen Sie Ausnahmen für jede Operation abfangen und behandeln. Sie werfen also nicht nur eine Ausnahme wie in Ihrem Beispiel. Die Verwendung von Ausnahmen führt dann entweder zu einer Reihe verschachtelter Try-Catch-Blöcke oder zu einer Reihe von Try-Catch-Blöcken. Dadurch ist der Code häufig weitaus weniger lesbar. Ich bin nicht gegen Ausnahmen eingestellt, sondern verwende das für die jeweilige Situation geeignete Tool. Ausnahmen sind nur 1 Werkzeug.
Dunk
5

"Um mit mehreren möglichen Fehlern fertig zu werden, sollte die Ausführung nicht unterbrochen werden."

Wenn Sie meinen, dass die Fehler die Ausführung der aktuellen Funktion nicht anhalten, sondern dem Aufrufer auf irgendeine Weise gemeldet werden sollen, dann haben Sie einige Optionen, die nicht wirklich erwähnt wurden. Dieser Fall ist eigentlich eher eine Warnung als ein Fehler. Werfen / Zurückkehren ist keine Option, da dies die aktuelle Funktion beendet. Ein einzelner Fehlermeldungsparameter oder eine einzelne Rückgabe lässt höchstens einen dieser Fehler zu.

Zwei von mir verwendete Muster sind:

  • Eine Fehler- / Warnungssammlung, die entweder übergeben oder als Mitgliedsvariable gespeichert wird. Dem du Sachen anhängst und einfach weiter verarbeitest. Ich persönlich mag diesen Ansatz nicht wirklich, da ich das Gefühl habe, dass er den Anrufer entmachtet.

  • Übergeben Sie ein Fehler- / Warnhandlerobjekt (oder legen Sie es als Elementvariable fest). Und jeder Fehler ruft eine Mitgliedsfunktion des Handlers auf. Auf diese Weise kann der Anrufer entscheiden, was mit solchen nicht beendenden Fehlern zu tun ist.

Was Sie an diese Auflistungen / Handler übergeben, sollte genügend Kontext enthalten, damit der Fehler "korrekt" behandelt werden kann. - Eine Zeichenfolge ist normalerweise zu klein. Das Übergeben einer Ausnahmebedingung ist häufig sinnvoll - wird jedoch manchmal missbilligt (als Missbrauch von Ausnahmen) .

Typischer Code, der einen Fehlerbehandler verwendet, sieht möglicherweise so aus

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}
Michael Anderson
quelle
3
+1 für das Bemerken, dass der Benutzer eher eine Warnung als einen Fehler wünscht. Erwähnenswert wäre vielleicht Pythons warningsPaket, das ein anderes Muster für dieses Problem liefert.
James_pic
Danke für die tolle Antwort! Dies ist mehr das, was ich sehen wollte, zusätzliche Muster für die Behandlung von Fehlern, bei denen das traditionelle Try / Catch möglicherweise nicht ausreicht.
Mikayla Maki
Es kann nützlich sein zu erwähnen, dass ein übergebenes Error-Callback-Objekt eine Ausnahme auslösen kann, wenn bestimmte Fehler oder Warnungen auftreten - dies kann in der Tat das Standardverhalten sein -, aber es kann auch nützlich sein, mit welchen Mitteln es fragen kann die aufrufende Funktion etwas zu tun. Beispielsweise kann eine Methode zum Behandeln eines Parsingfehlers dem Aufrufer einen Wert geben, von dem angenommen wird, dass die Analyse zurückgegeben wurde.
Supercat
5

Oft ist nichts falsch daran, dieses oder jenes Muster zu verwenden, solange Sie das Muster verwenden, das alle anderen verwenden. In der Objective-C- Entwicklung besteht das bevorzugte Muster darin, einen Zeiger zu übergeben, mit dem die aufgerufene Methode ein NSError-Objekt ablegen kann. Ausnahmen sind Programmierfehlern vorbehalten und führen zu einem Absturz (es sei denn, Sie haben Java- oder .NET- Programmierer, die ihre erste iPhone-App schreiben). Und das funktioniert ganz gut.

gnasher729
quelle
4

Die Frage ist bereits beantwortet, aber ich kann mir nicht helfen.

Sie können nicht wirklich erwarten, dass Exception eine Lösung für alle Anwendungsfälle bietet. Jemanden hämmern?

Es gibt Fälle, in denen Ausnahmen nicht das Ende aller Ausnahmen sind. Wenn beispielsweise eine Methode eine Anforderung empfängt und für die Validierung aller übergebenen Felder verantwortlich ist, und nicht nur für die erste, müssen Sie denken, dass dies möglich sein sollte Geben Sie die Fehlerursache für mehr als ein Feld an. Es sollte auch möglich sein anzugeben, ob die Art der Validierung den Benutzer daran hindert, weiterzugehen oder nicht. Ein Beispiel dafür wäre ein nicht sicheres Passwort. Sie können dem Benutzer eine Meldung anzeigen, die besagt, dass das eingegebene Kennwort nicht sehr sicher ist, aber stark genug.

Sie könnten argumentieren, dass alle diese Überprüfungen als Ausnahme am Ende des Überprüfungsmoduls ausgegeben werden könnten, aber es handelt sich um Fehlercodes, die in nichts anderem als im Namen enthalten sind.

Die Lektion hier lautet also: Ausnahmen haben ihren Platz, ebenso wie Fehlercodes. Mit Bedacht gewählt.

Alexandre Santos
quelle
Ich denke, das impliziert eher ein schlechtes Design. Eine Methode, die Argumente akzeptiert, sollte in der Lage sein, mit diesen umzugehen oder nicht - nichts dazwischen. In Ihrem Beispiel sollte eine Validator(Schnittstelle) in die betreffende Methode (oder das Objekt dahinter) eingefügt sein. Abhängig von der Injektion wird Validatordie Methode mit falschen Passwörtern fortgesetzt - oder nicht. Der umgebende Code könnte dann ein probieren, WeakValidatorwenn der Benutzer danach fragt, zB ein WeakPasswordExceptionvon dem ursprünglich ausprobierten geworfen wurde StrongValidator.
18.
Ah, aber ich habe nicht gesagt, dass dies keine Schnittstelle oder ein Validator sein kann. Ich habe JSR303 nicht einmal erwähnt. Und wenn Sie sorgfältig lesen, habe ich darauf geachtet, kein schwaches Passwort zu sagen, sondern es war nicht sehr stark. Ein schwaches Passwort wäre ein Grund, den Fluss zu stoppen und den Benutzer nach einem stärkeren Passwort zu fragen.
Alexandre Santos
Und was würden Sie mit einem mittelstarken, aber nicht wirklich schwachen Passwort tun? Sie unterbrechen den Ablauf und zeigen dem Benutzer eine Meldung an, dass das eingegebene Kennwort nicht sehr sicher ist. Also hab ein MiddlyStrongValidatoroder etwas. Und wenn dies Ihren Ablauf nicht wirklich unterbrochen hat, Validatormuss der zuvor aufgerufen worden sein, d. H. Bevor der Ablauf fortgesetzt wird, während der Benutzer noch sein Kennwort (oder ähnliches) eingibt. Aber dann war die Validierung überhaupt nicht Teil der fraglichen Methode. :) Wahrscheinlich doch Geschmackssache ...
18.
@jhr In den Validatoren, die ich geschrieben habe, erstelle ich normalerweise eine AggregateException(oder eine ähnliche ValidationException) und setze für jedes Validierungsproblem in InnerExceptions bestimmte Ausnahmen. Dies kann beispielsweise sein BadPasswordException: "Das Kennwort des Benutzers ist kürzer als die Mindestlänge von 6" oder MandatoryFieldMissingException: "Der Vorname muss für den Benutzer angegeben werden" usw. Dies entspricht nicht den Fehlercodes. Alle diese Meldungen können dem Benutzer so angezeigt werden, dass sie verstanden werden. Wenn NullReferenceExceptionstattdessen ein geworfen wurde, ist ein Fehler aufgetreten.
Omer Iqbal
4

Es gibt Anwendungsfälle, bei denen Fehlercodes Ausnahmen vorzuziehen sind.

Wenn der Code trotz des Fehlers fortgesetzt werden kann, jedoch eine Berichterstellung erforderlich ist, ist eine Ausnahme eine schlechte Wahl, da Ausnahmen den Ablauf beenden. Wenn Sie beispielsweise eine Datendatei einlesen und feststellen, dass sie nicht terminale fehlerhafte Daten enthält, ist es möglicherweise besser, den Rest der Datei einzulesen und den Fehler zu melden, als einen vollständigen Fehler zu melden.

In den anderen Antworten wurde erläutert, warum Ausnahmen generell Fehlercodes vorzuziehen sind.

Jack Aidley
quelle
Wenn eine Warnung protokolliert werden muss oder so, sei es. Aber wenn ein Fehler "gemeldet werden muss", womit Sie dem Benutzer Bericht erstatten, kann nicht garantiert werden, dass der umgebende Code Ihren Rückkehrcode liest und tatsächlich meldet.
18.
Nein, ich meine es dem Anrufer zu melden. Es liegt in der Verantwortung des Anrufers, zu entscheiden, ob der Benutzer über den Fehler informiert werden muss oder nicht, genau wie mit einer Ausnahme.
Jack Aidley
1
@jhr: Wann gibt es überhaupt eine Garantie für irgendetwas? Der Vertrag für eine Klasse kann festlegen, dass Kunden bestimmte Verantwortlichkeiten haben. Wenn Kunden sich an den Vertrag halten, werden sie die Dinge tun, die der Vertrag erfordert. Andernfalls ist der Client-Code für alle Konsequenzen verantwortlich. Wenn man sich vor versehentlichen Fehlern durch den Client schützen möchte und Kontrolle über den Typ hat, der von einer Serialisierungsmethode zurückgegeben wird, könnte man ein Flag für "unbestätigte mögliche Beschädigung" einfügen und Clients nicht erlauben, Daten von ihm zu lesen, ohne eine AcknowledgePossibleCorruptionMethode aufzurufen . .
supercat
1
... aber eine Objektklasse zu haben, die Informationen zu Problemen enthält, kann hilfreicher sein, als Ausnahmen auszulösen oder einen Pass-Fail-Fehlercode zurückzugeben. Es wäre Aufgabe der Anwendung, diese Informationen in geeigneter Weise zu verwenden (z. B. wenn Sie die Datei "Foo" laden, den Benutzer darüber informieren, dass die Daten möglicherweise nicht zuverlässig sind, und den Benutzer auffordern, beim Speichern einen neuen Namen zu wählen).
Supercat
1
Es gibt eine Garantie, wenn Sie Ausnahmen verwenden: Wenn Sie sie nicht abfangen, werden sie höher geworfen - der schlimmste Fall bis zur Benutzeroberfläche. Keine solche Garantie, wenn Sie Rückkehrcodes verwenden, die niemand liest. Folgen Sie der API, wenn Sie sie verwenden möchten. Genau! Aber es gibt leider Raum für Fehler ...
18.
2

Es ist definitiv nichts Falsches daran, keine Ausnahmen zu verwenden, wenn Ausnahmen nicht gut passen.

Wenn die Ausführung von Code sollte nicht unterbrochen werden (zB auf Benutzereingaben handeln , die mehrere Fehler enthalten können, wie ein Programm zu kompilieren oder ein Formular - Prozess), finde ich , dass wie Fehler in Fehlervariablen zu sammeln has_errorsund error_messagesin der Tat viel eleganter Entwurf ist als Wurf eine Ausnahme beim ersten Fehler. Es ermöglicht, alle Fehler in der Benutzereingabe zu finden, ohne den Benutzer dazu zu zwingen, diese unnötigerweise erneut zu übermitteln.

ikh
quelle
Interessant auf die Frage zu nehmen. Ich denke, das Problem mit meiner Frage und meinem Verständnis ist eine unklare Terminologie. Was Sie beschreiben, ist keine Ausnahme, aber es ist ein Fehler. Wie sollen wir das nennen?
Mikayla Maki
1

In einigen dynamischen Programmiersprachen können Sie sowohl Fehlerwerte als auch die Ausnahmebehandlung verwenden . Dazu wird anstelle des normalen Rückgabewerts ein nicht ausgelöstes Ausnahmeobjekt zurückgegeben, das wie ein Fehlerwert überprüft werden kann, aber eine Ausnahme auslöst, wenn es nicht aktiviert ist.

In Perl 6 erfolgt dies über fail, wobei bei Verwendung von no fatal;scope ein spezielles nicht geworfenes Ausnahmeobjekt zurückgegeben Failurewird.

In Perl 5 können Sie Contextual :: Return verwenden, mit dem Sie dies tun können return FAIL.

Jakub Narębski
quelle
-1

Ich halte es für eine schlechte Idee, eine Fehlervariable zur Validierung zu haben, es sei denn, es gibt etwas sehr Spezifisches. Der Zweck scheint darin zu bestehen, die für die Validierung aufgewendete Zeit zu sparen (Sie können einfach den Variablenwert zurückgeben).

Aber wenn Sie irgendetwas ändern, müssen Sie diesen Wert trotzdem neu berechnen. Ich kann nicht mehr über das Anhalten und Ausnahmewerfen sagen.

EDIT: Ich wusste nicht, dass dies eine Frage des Software-Paradigmas ist und nicht eines bestimmten Falls.

Lassen Sie mich meine Punkte in einem bestimmten Fall näher erläutern, in dem meine Antwort sinnvoll wäre

  1. Ich habe eine Sammlung von Entitätsobjekten
  2. Ich habe prozedurale Webdienste, die mit diesen Entitätsobjekten arbeiten

Es gibt zwei Arten von Fehlern:

  1. Die Fehler bei der Verarbeitung treten in der Serviceebene auf
  2. Die Fehler, weil es Inkonsistenzen in den Entitätsobjekten gibt

In der Serviceschicht gibt es keine andere Wahl, als Result-Objekt als Wrapper zu verwenden, was der Äquivalenz der Fehlervariablen entspricht. Das Simulieren einer Ausnahme über einen Dienstaufruf über ein Protokoll wie http ist möglich, aber definitiv nicht sinnvoll. Ich spreche nicht über diese Art von Fehler und dachte nicht, dass dies die Art von Fehler ist, die in dieser Frage gestellt wird.

Ich habe über die zweite Art von Fehler nachgedacht. Und meine Antwort bezieht sich auf diese zweite Art von Fehlern. In den Entity-Objekten gibt es für uns eine Auswahl, einige von ihnen sind es

  • Verwendung einer Validierungsvariablen
  • löst sofort eine Ausnahme aus, wenn ein Feld vom Setter falsch gesetzt wurde

Die Verwendung der Validierungsvariablen entspricht der Verwendung einer einzelnen Validierungsmethode für jedes Entitätsobjekt. Insbesondere kann der Benutzer den Wert so einstellen, dass der Setter als reiner Setter erhalten bleibt, keine Nebenwirkung auftritt (dies ist häufig eine gute Praxis), oder er kann die Validierung in jeden Setter integrieren und das Ergebnis in einer Validierungsvariablen speichern. Dies hat den Vorteil, dass Zeit gespart wird. Das Überprüfungsergebnis wird in der Überprüfungsvariablen zwischengespeichert, sodass beim mehrmaligen Aufrufen von validation () durch den Benutzer keine mehrfache Überprüfung erforderlich ist.

In diesem Fall empfiehlt es sich, eine einzige Überprüfungsmethode zu verwenden, ohne überhaupt eine Überprüfung zum Zwischenspeichern von Überprüfungsfehlern zu verwenden. Dies hilft, den Setter als nur Setter zu halten.

InformedA
quelle
Ich verstehe, wovon du redest. Interessanter Ansatz. Ich bin froh, dass es Teil des Antwortsatzes ist.
Mikayla Maki