Die moderne Art der Fehlerbehandlung…

118

Ich habe über dieses Problem schon eine Weile nachgedacht und finde mich ständig mit Vorbehalten und Widersprüchen konfrontiert. Deshalb hoffe ich, dass jemand zu folgenden Schlussfolgerungen kommen kann:

Bevorzugen Sie Ausnahmen gegenüber Fehlercodes

Soweit mir bekannt ist, sollten Sie nach vierjähriger Tätigkeit in der Branche beim Lesen von Büchern, Blogs usw. Ausnahmen auslösen und keine Fehlercodes zurückgeben (nicht unbedingt ein Fehlercode, sondern ein Fehlercode) Typ, der einen Fehler darstellt).

Aber - mir scheint das zu widersprechen ...

Codierung auf Schnittstellen, keine Implementierungen

Wir codieren in Schnittstellen oder Abstraktionen, um die Kopplung zu verringern. Wir kennen oder wollen den spezifischen Typ und die Implementierung einer Schnittstelle nicht kennen. Wie können wir also möglicherweise wissen, nach welchen Ausnahmen wir suchen sollten? Die Implementierung kann 10 verschiedene Ausnahmen auslösen oder keine. Wenn wir eine Ausnahme feststellen, treffen wir sicherlich Annahmen über die Implementierung?

Es sei denn - die Schnittstelle hat ...

Ausnahmespezifikationen

In einigen Sprachen können Entwickler angeben, dass bestimmte Methoden bestimmte Ausnahmen auslösen (Java verwendet beispielsweise das throwsSchlüsselwort.) Aus Sicht des aufrufenden Codes scheint dies in Ordnung zu sein - wir wissen explizit, welche Ausnahmen möglicherweise abgefangen werden müssen.

Aber - das scheint darauf hinzudeuten ...

Undichte Abstraktion

Warum sollte eine Schnittstelle angeben, welche Ausnahmen ausgelöst werden können? Was ist, wenn die Implementierung keine Ausnahme oder andere Ausnahmen auslösen muss? Auf Schnittstellenebene ist es unmöglich zu wissen, welche Ausnahmen eine Implementierung auslösen möchte.

Damit...

Schlussfolgern

Warum werden Ausnahmen bevorzugt, wenn sie (in meinen Augen) den Best Practices für Software zu widersprechen scheinen? Und wenn die Fehlercodes so schlecht sind (und ich nicht unbedingt die Fehlercodes verkaufen muss), gibt es eine andere Alternative? Was ist der aktuelle (oder zukünftige) Stand der Technik für die Fehlerbehandlung, der die Anforderungen der oben beschriebenen Best Practices erfüllt, sich jedoch nicht auf den Aufruf von Code zur Überprüfung des Rückgabewerts von Fehlercodes stützt?

RichK
quelle
12
Ich verstehe Ihr Argument über undichte Abstraktionen nicht. Die Angabe, welche Ausnahme eine bestimmte Methode auslösen kann , ist Teil der Schnittstellenspezifikation . Ebenso wie der Typ des Rückgabewerts Teil der Schnittstellenspezifikation ist. Ausnahmen kommen einfach nicht "von der linken Seite" der Funktion, aber sie sind immer noch Teil der Benutzeroberfläche.
Den
@deceze Wie kann eine Schnittstelle möglicherweise angeben, was eine Implementierung auslösen kann? Es können alle Ausnahmen im Typensystem ausgelöst werden! Und dass viele Sprachen Ausnahmespezifikationen nicht unterstützen, deutet darauf hin, dass sie ziemlich zwielichtig sind
RichK
1
Ich stimme zu, dass es schwierig sein kann, die verschiedenen Ausnahmen zu verwalten, die eine Methode auslöst, wenn sie selbst andere Methoden verwendet und ihre Ausnahmen nicht intern abfängt. Trotzdem ist es kein Widerspruch.
Den
1
Die undichte Annahme dabei ist , dass Code kann behandelt eine Ausnahme , wenn sie die Grenzfläche austritt. Das ist sehr unwahrscheinlich, da es nicht annähernd genug weiß, was tatsächlich schief gelaufen ist. Es weiß nur, dass etwas schief gelaufen ist und die Schnittstellenimplementierung nicht wusste, wie sie damit umgehen sollte. Die Chancen, dass es einen besseren Job machen kann, stehen schlecht, abgesehen davon, dass es gemeldet und das Programm beendet wird. Oder ignorieren, wenn die Schnittstelle für den ordnungsgemäßen Programmbetrieb nicht kritisch ist. Ein Implementierungsdetail, das Sie nicht in einem Schnittstellenvertrag codieren können und sollten.
Hans Passant

Antworten:

31

Zuallererst würde ich dieser Aussage nicht zustimmen:

Bevorzugen Sie Ausnahmen gegenüber Fehlercodes

Dies ist nicht immer der Fall: Schauen Sie sich beispielsweise Objective-C (mit dem Foundation-Framework) an. Dort ist der NSError die bevorzugte Methode, um Fehler zu behandeln, obwohl ein Java-Entwickler wahre Ausnahmen nennt: @try, @catch, @throw, NSException class, etc.

Es ist jedoch wahr, dass viele Schnittstellen ihre Abstraktionen mit den geworfenen Ausnahmen verlieren. Ich bin der Meinung, dass dies nicht an der "Ausnahmebedingung" der Fehlerausbreitung / -behandlung liegt. Im Allgemeinen glaube ich, dass der beste Rat zur Fehlerbehandlung der folgende ist:

Behandeln Sie den Fehler / die Ausnahme auf der niedrigstmöglichen Ebene

Ich denke, wenn man sich an diese Faustregel hält, kann die Menge an "Leckagen" aus Abstraktionen sehr begrenzt und begrenzt sein.

Ich bin der Meinung, dass Ausnahmen, die von einer Methode ausgelöst werden, Teil ihrer Deklaration sein sollten: Sie sind Teil des Vertrags , der durch diese Schnittstelle definiert wird: Diese Methode führt A aus oder schlägt mit B oder C fehl.

Wenn es sich bei einer Klasse beispielsweise um einen XML-Parser handelt, sollte ein Teil des Entwurfs anzeigen, dass die bereitgestellte XML-Datei einfach falsch ist. In Java tun Sie dies normalerweise, indem Sie die erwarteten Ausnahmen deklarieren und sie zum throwsTeil der Deklaration der Methode hinzufügen . Wenn andererseits einer der Parsing-Algorithmen fehlschlägt, gibt es keinen Grund, diese Ausnahme über unbehandelt zu bestehen.

Auf eines kommt es an: Gutes Interface-Design. Wenn Sie Ihre Benutzeroberfläche gut genug gestalten, sollten Sie nicht von einer Reihe von Ausnahmen heimgesucht werden. Ansonsten stören Sie nicht nur Ausnahmen.

Ich denke auch, dass die Entwickler von Java sehr starke Sicherheitsgründe hatten, um Ausnahmen von einer Methodendeklaration / -definition aufzunehmen.

Eine letzte Sache: Einige Sprachen, zum Beispiel Eiffel, haben andere Mechanismen zur Fehlerbehandlung und enthalten einfach keine Wurfmöglichkeiten. Dort wird automatisch eine Art 'Ausnahme' ausgelöst, wenn eine Nachbedingung für eine Routine nicht erfüllt ist.

K.Steff
quelle
12
+1 für das Negieren von "Ausnahmen gegenüber Fehlercodes bevorzugen". Mir wurde beigebracht, dass Ausnahmen gut und gut sind, solange sie tatsächlich Ausnahmen sind und nicht die Regel. Das Aufrufen einer Methode, die eine Ausnahme auslöst, um festzustellen, ob eine Bedingung erfüllt ist oder nicht, ist eine äußerst schlechte Praxis.
Neil
4
@ JoshuaDrake: Er ist definitiv falsch. Ausnahmen und gotosind sehr unterschiedlich. Beispielsweise gehen Ausnahmen immer in die gleiche Richtung - den Aufrufstapel hinunter. Und zweitens ist der Schutz vor Überraschungsausnahmen genau die gleiche Vorgehensweise wie bei DRY. Wenn Sie beispielsweise in C ++ RAII verwenden, um die Bereinigung sicherzustellen, wird in allen Fällen die Bereinigung sichergestellt , nicht nur bei Ausnahmen, sondern auch bei allen normalen Kontrollabläufen. Dies ist unendlich zuverlässiger. try/finallyetwas Ähnliches vollbringt. Wenn Sie die Bereinigung ordnungsgemäß garantieren, müssen Sie Ausnahmen nicht als Sonderfall betrachten.
DeadMG
4
@ JoshuaDrake Sorry, aber Joel ist weit weg. Ausnahmen sind nicht dasselbe wie goto, man geht immer mindestens eine Ebene höher. Und was machst du, wenn das Level darüber den Fehlercode nicht sofort verarbeiten kann? Noch einen Fehlercode zurückgeben? Ein Teil des Problems mit dem Fehler ist, dass sie ignoriert werden können, was zu schlimmeren Problemen als dem Auslösen einer Ausnahme führt.
Andy
25
-1, weil ich mit "Behandle den Fehler / die Ausnahme auf der niedrigstmöglichen Ebene" überhaupt nicht einverstanden bin - das ist falsch, Punkt. Behandeln Sie Fehler / Ausnahmen auf der entsprechenden Ebene. Sehr oft ist das eine viel höhere Ebene, und in diesem Fall sind Fehlercodes ein großer Schmerz. Der Grund für die Bevorzugung von Ausnahmen gegenüber Fehlercodes besteht darin, dass Sie frei wählen können, auf welcher Ebene sie behandelt werden sollen, ohne dass sich dies auf die dazwischen liegenden Ebenen auswirkt.
Michael Borgwardt
2
@ Giorgio: Siehe artima.com/intv/handcuffs.html - insbesondere Seite 2 und 3.
Michael Borgwardt
27

Ich möchte nur darauf hinweisen, dass Ausnahmen und Fehlercodes nicht die einzige Möglichkeit sind, mit Fehlern und alternativen Codepfaden umzugehen.

Aus dem Kopf können Sie einen Ansatz wie den von Haskell wählen, bei dem Fehler über abstrakte Datentypen mit mehreren Konstruktoren signalisiert werden können (denken Sie an diskriminierte Enums oder Null-Zeiger, aber typsicher und mit der Möglichkeit, Syntax hinzuzufügen Zucker- oder Hilfsfunktionen, die den Code fließen lassen).

func x = do
    a <- operationThatMightFail 10
    b <- operationThatMightFail 20
    c <- operationThatMightFail 30
    return (a + b + c)

operationThatMightfail ist eine Funktion, die einen in Vielleicht eingeschlossenen Wert zurückgibt. Es funktioniert wie ein Zeiger, der auf null gesetzt werden kann, aber die do-Notation garantiert, dass das Ganze auf null gesetzt wird, wenn eines von a, b oder c fehlschlägt. (und der Compiler schützt Sie vor versehentlicher NullPointerException)

Eine andere Möglichkeit ist die Übergabe eines Fehlerbehandlungsobjekts als zusätzliches Argument an jede aufgerufene Funktion. Dieser Fehlerbehandler verfügt über eine Methode für jede mögliche "Ausnahme", die von der Funktion signalisiert werden kann, an die Sie ihn übergeben, und die von dieser Funktion verwendet werden kann, um die Ausnahmen dort zu behandeln, wo sie auftreten, ohne dass der Stapel notwendigerweise über Ausnahmen zurückgespult werden muss.

Common LISP tut dies und macht es möglich, indem syntaktische Unterstützung (implizite Argumente) und die eingebauten Funktionen diesem Protokoll folgen.

hugomg
quelle
1
Das ist wirklich ordentlich. Vielen Dank für die Beantwortung des Teils über Alternativen :)
RichK
1
Ausnahmen sind übrigens einer der funktionalsten Teile der modernen imperativen Sprachen. Ausnahmen bilden eine Monade. Ausnahmen verwenden die Mustererkennung. Ausnahmen helfen dabei, den Anwendungsstil zu imitieren, ohne tatsächlich zu lernen, was Maybeist.
9000,
Ich habe kürzlich ein Buch über SML gelesen. Es wurden Optionstypen, Ausnahmen (und Fortsetzungen) genannt. Es wurde empfohlen, Optionstypen zu verwenden, wenn das Auftreten eines undefinierten Falls eher häufig zu erwarten ist, und Ausnahmen zu verwenden, wenn das Auftreten eines undefinierten Falls sehr selten ist.
Giorgio
8

Ja, Ausnahmen können undichte Abstraktionen verursachen. Aber sind Fehlercodes in dieser Hinsicht nicht noch schlimmer?

Eine Möglichkeit, dieses Problem zu lösen, besteht darin, dass die Schnittstelle genau angibt, welche Ausnahmen unter welchen Umständen ausgelöst werden können, und dass Implementierungen ihr internes Ausnahmemodell dieser Spezifikation zuordnen müssen, indem Ausnahmen abgefangen, konvertiert und gegebenenfalls erneut ausgelöst werden. Wenn Sie eine "Präfekt" -Schnittstelle möchten, ist dies der richtige Weg.

In der Praxis ist es normalerweise ausreichend, Ausnahmen anzugeben, die logisch Teil der Schnittstelle sind und die ein Client möglicherweise abfangen und bearbeiten möchte. Es ist allgemein bekannt, dass es andere Ausnahmen geben kann, wenn Fehler auf niedriger Ebene auftreten oder ein Fehler auftritt, und die ein Client im Allgemeinen nur behandeln kann, indem er eine Fehlermeldung anzeigt und / oder die Anwendung herunterfährt. Zumindest kann die Ausnahme noch Informationen enthalten, die bei der Diagnose des Problems hilfreich sind.

Tatsächlich geschieht mit Fehlercodes so ziemlich das Gleiche, nur auf implizitere Weise und mit einer weitaus größeren Wahrscheinlichkeit, dass Informationen verloren gehen und die App in einem inkonsistenten Zustand endet.

Michael Borgwardt
quelle
1
Ich verstehe nicht, warum ein Fehlercode eine undichte Abstraktion verursachen kann. Wenn ein Fehlercode anstelle eines normalen Rückgabewerts zurückgegeben wird und dieses Verhalten in der Spezifikation der Funktion / Methode beschrieben ist, liegt IMO kein Leck vor. Oder übersehen ich etwas?
Giorgio
Wenn der Fehlercode für die Implementierung spezifisch ist, während die API implementierungsunabhängig sein soll, werden bei Angabe und Rückgabe des Fehlercodes unerwünschte Implementierungsdetails gelöscht. Typisches Beispiel: Eine Protokollierungs-API mit einer dateibasierten und einer DB-basierten Implementierung, bei der möglicherweise der Fehler "Datenträger voll" und bei der letzteren der Fehler "DB-Verbindung vom Host abgelehnt" auftritt.
Michael Borgwardt
Ich verstehe was du meinst. Wenn Sie implementierungsspezifische Fehler melden möchten, kann die API nicht implementierungsunabhängig sein (weder mit Fehlercodes noch mit Ausnahmen). Ich denke, die einzige Lösung wäre, einen implementierungsunabhängigen Fehlercode wie "Ressource nicht verfügbar" zu definieren oder zu entscheiden, dass die API nicht implementierungsunabhängig ist.
Giorgio
1
@Giorgio: Ja, das Melden von implementierungsspezifischen Fehlern ist mit einer implementierungsunabhängigen API schwierig. Sie können dies jedoch mit Ausnahmen tun, da sie (anders als Fehlercodes) mehrere Felder haben können. Sie können also den Typ der Ausnahme verwenden, um die allgemeinen Fehlerinformationen (ResourceMissingException) bereitzustellen und einen implementierungsspezifischen Fehlercode / eine implementierungsspezifische Fehlermeldung als Feld einzufügen. Beste aus beiden Welten :-).
sleske
Und übrigens, genau das macht java.lang.SQLException. Es hat getSQLState(generische) und getErrorCode(herstellerspezifische). Wenn es nur richtige Unterklassen hätte ...
sleske
5

Viele gute Sachen hier, ich möchte nur hinzufügen, dass wir alle vorsichtig mit Code sein sollten, der Ausnahmen als Teil des normalen Kontrollflusses verwendet. Manchmal geraten Menschen in eine Falle, in der alles, was nicht der übliche Fall ist, zur Ausnahme wird. Ich habe sogar eine Ausnahme gesehen, die als Bedingung für die Beendigung einer Schleife verwendet wurde.

Ausnahmen bedeuten: "Etwas, mit dem ich hier nicht umgehen kann, ist passiert. Ich muss zu jemand anderem gehen, um herauszufinden, was zu tun ist." Ein Benutzer, der eine ungültige Eingabe eingibt, ist keine Ausnahme (dies sollte von der Eingabe lokal behandelt werden, indem er erneut fragt usw.).

Ein weiterer entarteter Fall von Ausnahmeverwendung, den ich gesehen habe, sind Menschen, deren erste Antwort "eine Ausnahme auslösen" lautet. Dies geschieht fast immer ohne das Schreiben des catch (Faustregel: Schreiben Sie zuerst den catch, dann die throw-Anweisung). In großen Anwendungen wird dies problematisch, wenn eine nicht abgefangene Ausnahme aus den Unterregionen auftaucht und das Programm in die Luft sprengt.

Ich bin kein Anti-Ausnahmefall, aber sie scheinen Singletons von vor ein paar Jahren zu sein: Sie werden viel zu häufig und unangemessen verwendet. Sie sind perfekt für den beabsichtigten Gebrauch, aber dieser Fall ist nicht so umfassend, wie manche meinen.

anon
quelle
4

Undichte Abstraktion

Warum sollte eine Schnittstelle angeben, welche Ausnahmen ausgelöst werden können? Was ist, wenn die Implementierung keine Ausnahme oder andere Ausnahmen auslösen muss? Auf Schnittstellenebene ist es unmöglich zu wissen, welche Ausnahmen eine Implementierung auslösen möchte.

Nee. Ausnahmespezifikationen befinden sich im selben Bereich wie Rückgabe- und Argumenttypen - sie sind Teil der Schnittstelle. Wenn Sie diese Spezifikation nicht einhalten können, implementieren Sie die Schnittstelle nicht. Wenn Sie nie werfen, ist das in Ordnung. Die Angabe von Ausnahmen in einer Schnittstelle ist nicht undicht.

Fehlercodes sind mehr als schlecht. Sie sind schrecklich. Sie müssen manuell daran denken, sie jedes Mal für jeden Anruf zu überprüfen und weiterzugeben. Dies verstößt zunächst gegen DRY und sprengt massiv Ihren Fehlerbehandlungscode. Diese Wiederholung ist ein weitaus größeres Problem als alle Ausnahmen. Sie können eine Ausnahme niemals stillschweigend ignorieren, aber die Leute können und tun es stillschweigend, um Rückkehrcodes zu ignorieren - definitiv eine schlechte Sache.

DeadMG
quelle
Fehlercodes können jedoch einfacher zu verwenden sein, wenn Sie über gute syntaktische Zucker- oder Hilfsmethoden verfügen. In einigen Sprachen können Sie den Compiler und das Typsystem so einstellen, dass Sie niemals vergessen, einen Fehlercode zu behandeln. Was den Ausnahmeschnittstellenteil angeht, denke ich, dass er über die berüchtigte Klobigkeit von Javas geprüften Ausnahmen nachgedacht hat. Während sie auf den ersten Blick als eine absolut vernünftige Idee erscheinen, verursachen sie in der Praxis viele schmerzhafte kleine Probleme.
Hugomg
@missingno: Das liegt daran, dass Java wie üblich eine schreckliche Implementierung hat, nicht daran, dass geprüfte Ausnahmen von Natur aus schlecht sind.
DeadMG
1
@missingno: Welche Probleme können durch aktivierte Ausnahmen verursacht werden?
Giorgio
@ Giorgio: Es ist sehr schwer, jede Art von Ausnahme vorherzusagen, die eine Methode auslösen könnte, da dies andere Unterklassen und Code berücksichtigen muss, die noch geschrieben werden müssen. In der Praxis gibt es hässliche Workarounds, die Informationen wegwerfen, z. B. die Wiederverwendung einer Systemausnahmeklasse für alles oder das häufige Abfangen und erneutes Auslösen innerer Ausnahmen. Ich habe auch gehört, dass überprüfte Ausnahmen eine große Hürde waren, als sie versuchten, der Sprache anonyme Funktionen hinzuzufügen.
Hugomg
@missingno: AFAIK anonyme Funktionen in Java sind nur syntaktischer Zucker für anonyme innere Klassen mit genau einer Methode. Ich bin mir nicht sicher, warum geprüfte Ausnahmen ein Problem darstellen würden (aber ich gebe zu, dass ich nicht viel über das Thema weiß). Ja, es ist schwer vorherzusagen, welche Ausnahmen eine Methode auslösen wird. Deshalb ist es meiner Meinung nach nützlich, Ausnahmen überprüft zu haben, damit Sie nicht raten müssen. Natürlich können Sie schlechten Code schreiben, um damit umzugehen, aber dies können Sie auch mit ungeprüften Ausnahmen tun. Ich weiß jedoch, dass die Debatte ziemlich komplex ist und ehrlich gesagt sehe ich auf beiden Seiten Vor- und Nachteile.
Giorgio
2

Die Ausnahmebehandlung kann über eine eigene Schnittstellenimplementierung verfügen. Führen Sie je nach Art der Ausnahmebedingung die gewünschten Schritte aus.

Die Lösung für Ihr Entwurfsproblem besteht darin, zwei Schnittstellen- / Abstraktionsimplementierungen zu haben. Eine für die Funktionalität und die andere für die Ausnahmebehandlung. Rufen Sie abhängig vom Typ der erfassten Ausnahme die entsprechende Ausnahmetypklasse auf.

Die Implementierung von Fehlercodes ist eine orthodoxe Methode zur Behandlung von Ausnahmen. Es ist wie die Verwendung von String vs. String Builder.

Estefany Velez
quelle
4
Mit anderen Worten: Implementierungen werfen Unterklassen der in der API definierten Ausnahmen.
Andrew Cooke
2

IM-very-HO-Ausnahmen sind von Fall zu Fall zu beurteilen, da sie durch eine Unterbrechung des Kontrollflusses die tatsächliche und wahrgenommene Komplexität Ihres Codes in vielen Fällen unnötig erhöhen. Wenn Sie die Diskussion über das Auslösen von Ausnahmen in Ihren Funktionen beiseite lassen - was Ihren Steuerungsfluss tatsächlich verbessern kann -, sollten Sie Folgendes berücksichtigen, um Ausnahmen über Aufrufgrenzen hinweg auszulösen:

Das Zulassen, dass ein Angerufener Ihren Kontrollfluss unterbricht, bietet möglicherweise keinen wirklichen Vorteil, und es gibt möglicherweise keine sinnvolle Möglichkeit, mit der Ausnahme umzugehen. Ein direktes Beispiel: Wenn Sie das Observable-Muster implementieren (in einer Sprache wie C #, in der überall Ereignisse vorliegen und die throwsin der Definition nicht explizit angegeben ist ), gibt es keinen wirklichen Grund dafür, dass der Observer Ihren Kontrollfluss unterbricht, wenn er abstürzt kein sinnvoller umgang mit ihren sachen (natürlich sollte ein guter nachbar beim beobachten nicht werfen, aber niemand ist perfekt).

Die obige Beobachtung kann auf jede lose gekoppelte Schnittstelle ausgedehnt werden (wie Sie betont haben); Ich denke, es ist eigentlich eine Norm, dass nach dem Erstellen von 3-6 Stack-Frames eine nicht erfasste Ausnahme wahrscheinlich in einem Codeabschnitt endet, der entweder:

  • ist zu abstrakt, um die Ausnahme in irgendeiner sinnvollen Weise zu behandeln, selbst wenn die Ausnahme selbst verfremdet ist;
  • führt eine generische Funktion aus (es könnte Sie nicht weniger interessieren, warum Sie versagt haben, z. B. eine Nachrichtenpumpe oder das Observable);
  • ist spezifisch, aber mit einer anderen Verantwortung, und es sollte sich wirklich keine Sorgen machen;

In Anbetracht des oben Gesagten ist das Dekorieren von Schnittstellen mit throwsSemantik nur ein geringfügiger Funktionsgewinn, da sich viele Anrufer über Schnittstellenverträge nur darum kümmern würden, wenn Sie scheitern, und nicht darum, warum.

Ich würde sagen, es wird dann eine Frage des Geschmacks und der Zweckmäßigkeit: Ihr Hauptaugenmerk liegt darauf, nach einer "Ausnahme" Ihren Zustand sowohl beim Anrufer als auch beim Angerufenen ordnungsgemäß wiederherzustellen, wenn Sie viel Erfahrung mit dem Verschieben von Fehlercodes haben aus einem C-Hintergrund) oder wenn Sie in einer Umgebung arbeiten, in der Ausnahmen böse werden können (C ++), glaube ich nicht, dass das Herumwerfen von Dingen für eine nette, saubere OOP so wichtig ist, dass Sie sich nicht auf Ihre alte verlassen können Muster, wenn Sie mit ihm unangenehm sind. Vor allem, wenn es zu einem Bruch des SoC kommt.

Aus theoretischer Sicht kann eine SoC-koschere Art der Behandlung von Ausnahmen direkt aus der Beobachtung abgeleitet werden, dass der direkte Anrufer sich in den meisten Fällen nur darum kümmert, dass Sie gescheitert sind, und nicht darum, warum. Der Angerufene wirft, jemand ganz in der Nähe (2-3 Frames) fängt eine aufgestaute Version ab und die eigentliche Ausnahme wird immer an einen spezialisierten Fehlerbehandler (auch wenn nur die Verfolgung) gesenkt - hier wäre AOP nützlich, weil diese Behandler sind wahrscheinlich horizontal.

vski
quelle
1

Bevorzugen Sie Ausnahmen gegenüber Fehlercodes

  • Beide sollten koexistieren.

  • Gibt einen Fehlercode zurück, wenn Sie ein bestimmtes Verhalten erwarten.

  • Ausnahmebedingung zurückgeben, wenn Sie nicht mit einem Verhalten gerechnet haben.

  • Fehlercodes werden normalerweise mit einer einzelnen Nachricht verknüpft, wenn der Ausnahmetyp erhalten bleibt. Eine Nachricht kann jedoch variieren

  • Die Ausnahme hat eine Stapelverfolgung, wenn der Fehlercode dies nicht tut. Ich verwende keine Fehlercodes, um ein defektes System zu debuggen.

Codierung zu Schnittstellen, nicht zu Implementierungen

Dies mag spezifisch für JAVA sein, aber wenn ich meine Schnittstellen deklariere, gebe ich nicht an, welche Ausnahmen von einer Implementierung dieser Schnittstelle ausgelöst werden könnten, es macht einfach keinen Sinn.

Wenn wir eine Ausnahme feststellen, treffen wir sicherlich Annahmen über die Implementierung?

Dies liegt ganz bei Ihnen. Sie können versuchen, eine ganz bestimmte Art von Ausnahme abzufangen und dann eine allgemeinere abzufangen Exception. Warum nicht Exception den Stack verbreiten lassen und dann damit umgehen? Alternativ können Sie sich die Aspektprogrammierung ansehen, bei der die Ausnahmebehandlung zu einem "steckbaren" Aspekt wird.

Was ist, wenn die Implementierung keine Ausnahme oder andere Ausnahmen auslösen muss?

Ich verstehe nicht, warum es ein Problem für Sie ist. Ja, Sie haben möglicherweise eine Implementierung, die niemals fehlschlägt oder Ausnahmen auslöst, und Sie haben möglicherweise eine andere Implementierung, die ständig fehlschlägt und Ausnahmen auslöst. Wenn dies der Fall ist, geben Sie in der Schnittstelle keine Ausnahmen an, und Ihr Problem ist behoben.

Würde es etwas ändern, wenn Ihre Implementierung anstelle einer Ausnahme ein Ergebnisobjekt zurückgeben würde? Dieses Objekt enthält das Ergebnis Ihrer Aktion sowie etwaige Fehler / Ausfälle. Sie können dieses Objekt dann abfragen.

CodeART
quelle
1

Undichte Abstraktion

Warum sollte eine Schnittstelle angeben, welche Ausnahmen ausgelöst werden können? Was ist, wenn die Implementierung keine Ausnahme oder andere Ausnahmen auslösen muss? Auf Schnittstellenebene ist es unmöglich zu wissen, welche Ausnahmen eine Implementierung auslösen möchte.

Nach meiner Erfahrung würde der Code, der den Fehler empfängt (sei es über eine Ausnahme, einen Fehlercode oder etwas anderes), die genaue Fehlerursache normalerweise nicht berücksichtigen - er würde auf die gleiche Weise auf einen Fehler reagieren, mit Ausnahme einer möglichen Meldung des Fehlers Fehler (sei es ein Fehlerdialog oder eine Art Protokoll); und diese Berichterstattung würde orthogonal zu dem Code erfolgen, der die fehlerhafte Prozedur aufgerufen hat. Beispielsweise könnte dieser Code den Fehler an einen anderen Teil des Codes weiterleiten, der bestimmte Fehler zu melden weiß (z. B. Formatieren einer Nachrichtenzeichenfolge) und möglicherweise Kontextinformationen anfügt.

Natürlich ist es in einigen Fällen wird benötigt spezifische Semantik , um Fehler zu befestigen und zu unterschiedlich , auf dem react basierter Fehler ist aufgetreten. Solche Fälle sollten in der Schnittstellenspezifikation dokumentiert werden. Die Schnittstelle behält sich jedoch möglicherweise das Recht vor, andere Ausnahmen ohne besondere Bedeutung auszulösen.

Ambroz Bizjak
quelle
1

Ich finde, dass Ausnahmen es ermöglichen, einen strukturierteren und präziseren Code für das Melden und Behandeln von Fehlern zu schreiben: Um Fehlercodes zu verwenden, müssen die Rückgabewerte nach jedem Aufruf überprüft und entschieden werden, was im Falle eines unerwarteten Ergebnisses zu tun ist.

Andererseits stimme ich zu, dass Ausnahmen Implementierungsdetails enthalten, die für den Code, der eine Schnittstelle aufruft, verborgen sein sollten. Da nicht von vornherein bekannt ist, welcher Code welche Ausnahmen auslösen kann (es sei denn, sie sind in der Methodensignatur wie in Java deklariert), führen wir mithilfe von Ausnahmen sehr komplexe implizite Abhängigkeiten zwischen verschiedenen Teilen des Codes ein gegen das Prinzip der Minimierung von Abhängigkeiten.

Zusammenfassend:

  • Ich denke, Ausnahmen ermöglichen einen saubereren Code und eine aggressivere Vorgehensweise beim Testen und Debuggen, da nicht abgefangene Ausnahmen viel sichtbarer und schwerer zu ignorieren sind als Fehlercodes (scheitern bald).
  • Auf der anderen Seite können nicht gefundene Ausnahmefehler, die beim Testen nicht entdeckt wurden, in einer Produktionsumgebung in Form eines Absturzes auftreten. Unter bestimmten Umständen ist dieses Verhalten nicht akzeptabel und in diesem Fall halte ich die Verwendung von Fehlercodes für einen robusteren Ansatz.
Giorgio
quelle
1
Ich stimme dir nicht zu. Ja, nicht abgefangene Ausnahmen können eine Anwendung zum Absturz bringen, ungeprüfte Fehlercodes jedoch auch. Wenn Sie Ausnahmen richtig verwenden, sind die einzigen Ausnahmen (wie OutOfMemory) fatal, und für diese ist es das Beste, was Sie tun können, wenn Sie sofort abstürzen.
sleske
Der Fehlercode ist Bestandteil des Vertrages zwischen dem Anrufer m1 und dem Angerufenen m2: Die möglichen Fehlercodes werden nur in der Schnittstelle von m2 definiert. Mit Ausnahmen (es sei denn, Sie verwenden Java und deklarieren alle ausgelösten Ausnahmen in Methodensignaturen) haben Sie einen impliziten Vertrag zwischen dem Aufrufer m1 und allen Methoden, die von m2 rekursiv aufgerufen werden können. Natürlich ist es ein Fehler, einen zurückgegebenen Fehlercode nicht zu überprüfen, aber es ist immer möglich, dies zu tun. Andererseits ist es nicht immer möglich, alle von einer Methode ausgelösten Ausnahmen zu überprüfen, es sei denn, Sie wissen, wie sie implementiert sind.
Giorgio
Erstens: Sie können alle Ausnahmen überprüfen - führen Sie einfach die "Pokemon-Ausnahmebehandlung" durch (müssen alle abfangen - dh catch Exceptionoder sogar Throwableoder gleichwertig).
sleske
1
In der Praxis werden bei ordnungsgemäßem API-Design alle Ausnahmen angegeben, die der Client sinnvoll behandeln kann. Diese müssen speziell abgefangen werden. Dies sind die Äquivalente von Fehlercodes. Jede andere Ausnahme bedeutet "interner Fehler" und die Anwendung muss das entsprechende Subsystem herunterfahren oder zumindest herunterfahren. Sie können diese fangen, wenn Sie möchten (siehe oben), aber Sie sollten sie normalerweise nur in die Luft sprudeln lassen. Dieses "Sprudeln" ist der Hauptvorteil der Verwendung von Ausnahmen. Sie können sie je nach Bedarf weiter oben einholen oder auch nicht.
sleske
-1

Entweder nach rechts voreingenommen.

Es kann nicht ignoriert werden, es muss gehandhabt werden, es ist völlig transparent. Und WENN Sie den richtigen Linkshänder-Fehlertyp verwenden, werden die gleichen Informationen wie bei einer Java-Ausnahme übertragen.

Nachteil? Code mit korrekter Fehlerbehandlung sieht widerlich aus (gilt für alle Mechanismen).

Keynan
quelle
Dies scheint nichts Wesentliches über die in den vorherigen 10 Antworten gemachten und erklärten Punkte zu bieten. Warum zwei Jahre alte Frage mit solchen Sachen stoßen
Mücke
Außer, dass hier niemand das Recht erwähnte, das auch voreingenommen ist. hugomg hat sich intensiv mit haskell befasst, ist aber vielleicht ein shit error handler, da er keine erklärung darüber hinterlässt, warum der fehler aufgetreten ist, und auch keine direkte methode zur wiederherstellung und rückrufe sind eine der größten sünden bei der gestaltung von kontrollabläufen. Und diese Frage tauchte bei Google auf.
Keynan