Konventionen für Ausnahmen oder Fehlercodes

117

Gestern hatte ich eine hitzige Debatte mit einem Kollegen über die bevorzugte Methode zur Fehlerberichterstattung. Hauptsächlich diskutierten wir die Verwendung von Ausnahmen oder Fehlercodes zum Melden von Fehlern zwischen Anwendungsebenen oder Modulen.

Nach welchen Regeln entscheiden Sie, ob Sie Ausnahmen auslösen oder Fehlercodes für die Fehlerberichterstattung zurückgeben?

Jorge Ferreira
quelle

Antworten:

81

In hochrangigen Sachen Ausnahmen; in Low-Level-Sachen, Fehlercodes.

Das Standardverhalten einer Ausnahme besteht darin, den Stapel abzuwickeln und das Programm zu stoppen. Wenn ich ein Skript schreibe und einen Schlüssel suche, der nicht in einem Wörterbuch enthalten ist, ist dies wahrscheinlich ein Fehler, und ich möchte, dass das Programm angehalten wird und mich lässt weiß alles darüber.

Wenn ich jedoch einen Code schreibe , dessen Verhalten ich in jeder möglichen Situation kennen muss, möchte ich Fehlercodes. Ansonsten muss ich jede Ausnahme kennen, die von jeder Zeile in meiner Funktion ausgelöst werden kann, um zu wissen, was sie tun wird (Lesen Sie die Ausnahme, die eine Fluggesellschaft geerdet hat , um eine Vorstellung davon zu bekommen, wie schwierig dies ist). Es ist mühsam und schwierig, Code zu schreiben, der auf jede Situation (einschließlich der unglücklichen) angemessen reagiert. Dies liegt jedoch daran, dass das Schreiben von fehlerfreiem Code mühsam und schwierig ist und nicht daran, dass Sie Fehlercodes übergeben.

Sowohl Raymond Chen als auch Joel haben einige beredte Argumente gegen die Verwendung von Ausnahmen für alles vorgebracht.

Tom Dunham
quelle
5
+1 für den Hinweis, dass der Kontext etwas mit der Fehlerbehandlungsstrategie zu tun hat.
Alx9r
2
@ Tom, gute Punkte, aber Ausnahmen werden garantiert gefangen. Wie stellen wir sicher, dass Fehlercodes aufgrund von Fehlern abgefangen und nicht stillschweigend ignoriert werden?
Pacerier
13
Ihr einziges Argument gegen Ausnahmen ist also, dass etwas Schlimmes passieren kann, wenn Sie vergessen, eine Ausnahme abzufangen, Sie jedoch die ebenso wahrscheinliche Situation nicht in Betracht ziehen, den zurückgegebenen Wert auf Fehler zu überprüfen? Ganz zu schweigen davon, dass Sie eine Stapelverfolgung erhalten, wenn Sie vergessen, eine Ausnahme abzufangen, während Sie nichts erhalten, wenn Sie vergessen, einen Fehlercode zu überprüfen.
Esailija
3
C ++ 17 führt das nodiscardAttribut ein, das eine Compilerwarnung ausgibt, wenn der Rückgabewert einer Funktion nicht gespeichert wird. Hilft ein wenig, vergessene Fehlercode-Überprüfungen abzufangen. Beispiel: godbolt.org/g/6i6E0B
Zitrax
5
@ Esailijas Kommentar ist hier genau richtig. Das Argument gegen Ausnahmen setzt hier eine hypothetische fehlercodebasierte API voraus, in der alle Fehlercodes dokumentiert sind, und eine hypothetische Programmiererin, die die Dokumentation liest, alle Fehlerfälle, die in ihrer Anwendung logisch möglich sind, korrekt identifiziert und Code schreibt, um jeden von ihnen zu behandeln vergleicht dann dieses Szenario mit einer hypothetischen ausnahmebasierten API und einem Programmierer, bei dem aus irgendeinem Grund einer dieser Schritte schief geht ... obwohl es genauso einfach (wohl einfacher ) ist, alle diese Schritte in einer ausnahmebasierten API richtig zu machen.
Mark Amery
61

Normalerweise bevorzuge ich Ausnahmen, weil sie mehr Kontextinformationen enthalten und den Fehler (bei richtiger Verwendung) dem Programmierer klarer vermitteln können.

Auf der anderen Seite sind Fehlercodes leichter als Ausnahmen, aber schwieriger zu pflegen. Die Fehlerprüfung kann versehentlich weggelassen werden. Fehlercodes sind schwieriger zu pflegen, da Sie einen Katalog mit allen Fehlercodes führen und dann das Ergebnis einschalten müssen, um zu sehen, welcher Fehler ausgelöst wurde. Fehlerbereiche können hier hilfreich sein, denn wenn wir nur daran interessiert sind, ob ein Fehler vorliegt oder nicht, ist es einfacher zu überprüfen (z. B. ist ein HRESULT-Fehlercode größer oder gleich 0 Erfolg und kleiner als Null ist ein Fehler). Sie können versehentlich weggelassen werden, da der Entwickler nicht programmgesteuert nach Fehlercodes sucht. Auf der anderen Seite können Sie Ausnahmen nicht ignorieren.

Zusammenfassend bevorzuge ich in fast allen Situationen Ausnahmen gegenüber Fehlercodes.

Jorge Ferreira
quelle
4
"Fehlercodes sind leichter als Ausnahmen" hängt davon ab, was Sie messen und wie Sie messen. Es ist ziemlich einfach, Tests zu erstellen, die zeigen, dass ausnahmebasierte APIs viel schneller sein können.
Mooing Duck
1
@smink, gute Punkte, aber wie gehen wir mit dem Overhead von Ausnahmen um? Fehlercodes sind nicht nur leicht, sondern im Grunde genommen schwerelos . Ausnahmen sind nicht nur mittelschwere, sie sind schwer -gewicht Objekte enthalten Stack - Informationen und sonstige Sachen , die wir ohnehin nicht verwenden.
Pacerier
3
@ Pacerier: Sie erwägen nur Tests, bei denen Ausnahmen ausgelöst werden. Sie haben zu 100% Recht, dass das Auslösen einer C ++ - Ausnahme erheblich langsamer ist als das Zurückgeben eines Fehlercodes. Zweifellos keine Debatte. Wo wir uns unterscheiden, sind die anderen 99,999% des Codes. Mit Ausnahmen müssen wir nicht die Fehlercode-Rückgaben zwischen den einzelnen Anweisungen überprüfen, wodurch dieser Code um 1-50% schneller wird (oder auch nicht, abhängig von Ihrem Compiler). Dies bedeutet, dass der vollständige Code schneller oder langsamer sein kann, je nachdem, wie der Code geschrieben wird und wie oft Ausnahmen ausgelöst werden.
Mooing Duck
1
@Pacerier: Bei diesem künstlichen Test, den ich gerade geschrieben habe, ist der ausnahmebasierte Code genauso schnell wie die Fehlercodes in MSVC und Clang, jedoch nicht GCC: coliru.stacked-crooked.com/a/e81694e5c508945b (Timings unten). Es scheint, dass die von mir verwendeten GCC-Flags im Vergleich zu den anderen Compilern ungewöhnlich langsame Ausnahmen generiert haben. Ich bin voreingenommen, also kritisieren Sie bitte meinen Test und probieren Sie eine andere Variante aus.
Mooing Duck
4
@Mooing Duck, Wenn Sie sowohl Fehlercodes als auch Ausnahmen gleichzeitig getestet haben, müssen Sie Ausnahmen aktiviert haben. In diesem Fall deuten Ihre Ergebnisse darauf hin, dass die Verwendung von Fehlercodes mit Ausnahmebehandlungsaufwand nicht langsamer ist als die Verwendung nur von Ausnahmen. Die Fehlercodetests müssten mit deaktivierten Ausnahmen ausgeführt werden, um aussagekräftige Ergebnisse zu erhalten.
Mika Haarahiltunen
24

Ich bevorzuge Ausnahmen, weil

  • sie unterbrechen den Fluss der Logik
  • Sie profitieren von einer Klassenhierarchie, die mehr Features / Funktionen bietet
  • Bei ordnungsgemäßer Verwendung kann eine Vielzahl von Fehlern auftreten (z. B. eine InvalidMethodCallException ist auch eine LogicException, da beide auftreten, wenn in Ihrem Code ein Fehler vorliegt, der vor der Laufzeit erkannt werden sollte)
  • Sie können verwendet werden, um den Fehler zu verbessern (dh eine FileReadException-Klassendefinition kann dann Code enthalten, um zu überprüfen, ob die Datei vorhanden oder gesperrt ist usw.).
JamShady
quelle
2
Ihr vierter Punkt ist nicht fair: Ein Fehlerstatus bei der Konvertierung in ein Objekt kann auch Code enthalten, um zu überprüfen, ob die Datei vorhanden oder gesperrt ist usw. Es handelt sich lediglich um eine Variation von stackoverflow.com/a/3157182/632951
Pacerier
1
"Sie unterbrechen den Fluss der Logik". Ausnahmen haben mehr oder weniger die Auswirkungen vongoto
Peterchaula
22

Fehlercodes können von den Aufrufern Ihrer Funktionen ignoriert werden (und sind es oft!). Ausnahmen zwingen sie zumindest dazu, den Fehler auf irgendeine Weise zu behandeln. Auch wenn ihre Version des Umgangs damit darin besteht, einen leeren Fanghandler zu haben (Seufzer).

Maxam
quelle
17

Ausnahmen über Fehlercodes, kein Zweifel. Sie erhalten von Ausnahmen weitgehend die gleichen Vorteile wie bei Fehlercodes, aber auch viel mehr, ohne die Mängel von Fehlercodes. Der einzige Grund für Ausnahmen ist, dass der Aufwand etwas höher ist. In der heutigen Zeit sollte dieser Aufwand für fast alle Anwendungen als vernachlässigbar angesehen werden.

Hier sind einige Artikel, in denen die beiden Techniken diskutiert, verglichen und gegenübergestellt werden:

Es gibt einige gute Links in denen, die Sie weiterlesen können.

Pistos
quelle
16

Ich würde die beiden Modelle niemals mischen ... es ist zu schwierig, von einem zum anderen zu konvertieren, wenn Sie von einem Teil des Stapels, der Fehlercodes verwendet, zu einem höheren Teil wechseln, das Ausnahmen verwendet.

Ausnahmen sind "alles, was die Methode oder Unterroutine daran hindert oder hindert, das zu tun, was Sie von ihr verlangt haben" ... KEINE Nachrichten über Unregelmäßigkeiten oder ungewöhnliche Umstände oder den Zustand des Systems usw. zurückzugeben. Verwenden Sie Rückgabewerte oder ref (oder out) Parameter dafür.

Ausnahmen ermöglichen das Schreiben (und Verwenden) von Methoden mit einer Semantik, die von der Funktion der Methode abhängt. Das heißt, eine Methode, die ein Employee-Objekt oder eine Liste von Employees zurückgibt, kann dazu eingegeben werden, und Sie können sie durch Aufrufen verwenden.

Employee EmpOfMonth = GetEmployeeOfTheMonth();

Bei Fehlercodes geben alle Methoden einen Fehlercode zurück. Für diejenigen, die etwas anderes zurückgeben müssen, um vom aufrufenden Code verwendet zu werden, müssen Sie eine Referenzvariable übergeben, die mit diesen Daten gefüllt werden soll, und den Rückgabewert für das testen Fehlercode und behandeln Sie ihn bei jedem Funktions- oder Methodenaufruf.

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

Wenn Sie so codieren, dass jede Methode nur eine einfache Aufgabe ausführt, sollten Sie eine Ausnahme auslösen, wenn die Methode das gewünschte Ziel der Methode nicht erreichen kann. Ausnahmen sind viel umfangreicher und auf diese Weise einfacher zu verwenden als Fehlercodes. Ihr Code ist viel sauberer - Der Standardfluss des "normalen" Codepfads kann ausschließlich dem Fall gewidmet werden, in dem die Methode das erreichen kann, was Sie wollten ... Und dann den Code zum Bereinigen oder Behandeln des Codes "außergewöhnliche" Umstände, in denen etwas Schlimmes passiert, das den erfolgreichen Abschluss der Methode verhindert, können vom normalen Code getrennt werden. Wenn Sie die Ausnahme dort, wo sie aufgetreten ist, nicht behandeln können und sie den Stapel an eine Benutzeroberfläche weitergeben müssen (oder schlimmer noch, über die Leitung von einer Mid-Tier-Komponente zu einer Benutzeroberfläche), dann mit dem Ausnahmemodell:

Charles Bretana
quelle
Gute Antwort! Out-of-the-Box-Lösung gerade noch rechtzeitig!
Cristian E.
11

In der Vergangenheit bin ich dem Fehlercode-Camp beigetreten (habe zu viel C-Programmierung gemacht). Aber jetzt habe ich das Licht gesehen.

Ja, Ausnahmen belasten das System ein wenig. Sie vereinfachen jedoch den Code und reduzieren die Anzahl der Fehler (und WTFs).

Verwenden Sie also eine Ausnahme, aber verwenden Sie sie mit Bedacht. Und sie werden dein Freund sein.

Als Anmerkung. Ich habe gelernt zu dokumentieren, welche Ausnahme mit welcher Methode ausgelöst werden kann. Leider wird dies von den meisten Sprachen nicht benötigt. Dies erhöht jedoch die Wahrscheinlichkeit, die richtigen Ausnahmen auf der richtigen Ebene zu behandeln.

Toon Krijthe
quelle
1
yap C hinterlässt ein paar Gewohnheiten in uns allen;)
Jorge Ferreira
11

Es kann einige Situationen geben, in denen die Verwendung von Ausnahmen auf saubere, klare und korrekte Weise umständlich ist, aber die überwiegende Mehrheit der Zeitausnahmen ist die offensichtliche Wahl. Der größte Vorteil der Ausnahmebehandlung gegenüber Fehlercodes besteht darin, dass der Ausführungsfluss geändert wird, was aus zwei Gründen wichtig ist.

Wenn eine Ausnahme auftritt, folgt die Anwendung nicht mehr ihrem "normalen" Ausführungspfad. Der erste Grund, warum dies so wichtig ist, ist, dass das Programm angehalten wird und keine unvorhersehbaren Dinge mehr tut, wenn der Autor des Codes nicht wirklich alles daran setzt, schlecht zu sein. Wenn ein Fehlercode nicht überprüft wird und keine geeigneten Maßnahmen als Reaktion auf einen fehlerhaften Fehlercode ergriffen werden, macht das Programm weiter, was es tut, und wer weiß, was das Ergebnis dieser Aktion sein wird. Es gibt viele Situationen, in denen das Programm "was auch immer" tun kann, was sehr teuer werden kann. Stellen Sie sich ein Programm vor, das Leistungsinformationen für verschiedene Finanzinstrumente abruft, die ein Unternehmen verkauft, und diese Informationen an Makler / Großhändler weiterleitet. Wenn etwas schief geht und das Programm weiter läuft, Es könnte fehlerhafte Leistungsdaten an die Makler und Großhändler senden. Ich kenne niemanden sonst, aber ich möchte nicht derjenige sein, der in einem VPs-Büro sitzt und erklärt, warum mein Code dazu geführt hat, dass das Unternehmen Bußgelder im Wert von 7 Ziffern erhalten hat. Die Übermittlung einer Fehlermeldung an Kunden ist im Allgemeinen der Übermittlung falscher Daten vorzuziehen, die als "echt" erscheinen könnten, und die letztere Situation ist mit einem viel weniger aggressiven Ansatz wie Fehlercodes viel einfacher zu lösen.

Der zweite Grund, warum ich Ausnahmen und deren Unterbrechung der normalen Ausführung mag, ist, dass es viel, viel einfacher ist, die Logik „normale Dinge passieren“ von der Logik „etwas ist schief gelaufen“ zu trennen. Für mich ist dies:

try {
    // Normal things are happening logic
catch (// A problem) {
    // Something went wrong logic
}

... ist dem vorzuziehen:

// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}

Es gibt noch andere kleine Dinge über Ausnahmen, die auch nett sind. Eine Reihe von bedingten Logik zu haben, um zu verfolgen, ob bei einer der in einer Funktion aufgerufenen Methoden ein Fehlercode zurückgegeben wurde, und diesen Fehlercode höher zurückzugeben, ist eine Menge Kesselplatte. In der Tat ist es eine Menge Kesselplatte, die schief gehen kann. Ich habe viel mehr Vertrauen in das Ausnahmesystem der meisten Sprachen als in ein Rattennest von If-else-if-else-Aussagen, die Fred geschrieben hat, und ich habe viel bessere Dinge zu tun mit meiner Zeit als Code Überprüfung der Ratte Nest.

Hunde
quelle
8

Sie sollten beide verwenden. Die Sache ist zu entscheiden, wann jeder verwendet werden soll .

Es gibt einige Szenarien, in denen Ausnahmen die offensichtliche Wahl sind :

  1. In einigen Situationen können Sie mit dem Fehlercode nichts anfangen , und Sie müssen ihn nur auf einer höheren Ebene im Aufrufstapel behandeln , normalerweise nur den Fehler protokollieren, dem Benutzer etwas anzeigen oder das Programm schließen. In diesen Fällen müssten Sie bei Fehlercodes die Fehlercodes manuell Stufe für Stufe aufblasen, was mit Ausnahmen offensichtlich viel einfacher ist. Der Punkt ist, dass dies für unerwartete und nicht handhabbare Situationen ist.

  2. In Bezug auf Situation 1 (in der etwas Unerwartetes und Unhandhabbares passiert, das Sie einfach nicht protokollieren möchten) können Ausnahmen hilfreich sein, da Sie möglicherweise Kontextinformationen hinzufügen . Wenn ich beispielsweise eine SqlException in meinen untergeordneten Datenhelfern erhalte, möchte ich diesen Fehler in der untergeordneten Ebene abfangen (wo ich den SQL-Befehl kenne, der den Fehler verursacht hat), damit ich diese Informationen erfassen und mit zusätzlichen Informationen erneut werfen kann . Bitte beachten Sie hier das Zauberwort: neu werfen und nicht schlucken . Die erste Regel für die Ausnahmebehandlung: Schlucken Sie keine Ausnahmen . Beachten Sie außerdem, dass mein innerer Fang nichts protokollieren muss, da der äußere Fang den gesamten Stack-Trace enthält und diesen möglicherweise protokolliert.

  3. In einigen Situationen haben Sie eine Folge von Befehlen, und wenn einer von ihnen fehlschlägt , sollten Sie Ressourcen bereinigen / entsorgen (*), unabhängig davon, ob es sich um eine nicht behebbare Situation handelt (die ausgelöst werden sollte) oder um eine wiederherstellbare Situation (in diesem Fall können Sie dies lokal oder im Anrufercode behandeln, aber Sie brauchen keine Ausnahmen). Offensichtlich ist es viel einfacher, alle diese Befehle in einem einzigen Versuch zu platzieren, anstatt Fehlercodes nach jeder Methode zu testen und im finally-Block zu bereinigen / zu entsorgen. Bitte beachten Sie, dass Sie , wenn Sie möchten, dass der Fehler in die Luft sprudelt (was wahrscheinlich das ist, was Sie wollen), ihn nicht einmal abfangen müssen - Sie verwenden nur das endgültige zum Bereinigen / Entsorgen - Sie sollten catch / retrow nur verwenden, wenn Sie möchten Kontextinformationen hinzufügen (siehe Punkt 2).

    Ein Beispiel wäre eine Folge von SQL-Anweisungen innerhalb eines Transaktionsblocks. Auch dies ist eine "unhandhabbare" Situation, selbst wenn Sie sich entschließen, sie frühzeitig zu fangen (lokal behandeln, anstatt nach oben zu sprudeln). Es ist immer noch eine fatale Situation, in der das beste Ergebnis darin besteht, alles abzubrechen oder zumindest einen großen abzubrechen Teil des Prozesses.
    (*) Dies ist wie das on error goto, was wir in alten Visual Basic verwendet haben

  4. In Konstruktoren können Sie nur Ausnahmen auslösen.

In allen anderen Situationen, in denen Sie Informationen zurückgeben, für die der Anrufer Maßnahmen ergreifen kann / sollte , ist die Verwendung von Rückkehrcodes wahrscheinlich die bessere Alternative. Dies schließt alle erwarteten "Fehler" ein , da sie wahrscheinlich vom unmittelbaren Aufrufer behandelt werden sollten und kaum zu viele Ebenen im Stapel hochgeblasen werden müssen.

Natürlich ist es immer möglich, erwartete Fehler als Ausnahmen zu behandeln und dann sofort eine Ebene darüber abzufangen, und es ist auch möglich, jede Codezeile in einem Try-Catch zu erfassen und für jeden möglichen Fehler Maßnahmen zu ergreifen. IMO, dies ist ein schlechtes Design, nicht nur, weil es viel ausführlicher ist, sondern insbesondere, weil die möglichen Ausnahmen, die möglicherweise ausgelöst werden, ohne Lesen des Quellcodes nicht offensichtlich sind - und Ausnahmen von jeder tiefen Methode ausgelöst werden können, wodurch unsichtbare Gotos entstehen . Sie unterbrechen die Codestruktur, indem sie mehrere unsichtbare Austrittspunkte erstellen, die das Lesen und Überprüfen von Code erschweren. Mit anderen Worten, Sie sollten niemals Ausnahmen als Flusskontrolle verwenden, weil das für andere schwer zu verstehen und aufrechtzuerhalten wäre. Es kann sogar schwierig werden, alle möglichen Codeflüsse zum Testen zu verstehen.
Nochmals: Für eine korrekte Bereinigung / Entsorgung können Sie try-finally verwenden, ohne etwas zu fangen .

Die populärste Kritik an Rückkehrcodes ist, dass "jemand die Fehlercodes ignorieren könnte, aber im gleichen Sinne kann jemand auch Ausnahmen verschlucken. Eine schlechte Ausnahmebehandlung ist bei beiden Methoden einfach . Das Schreiben eines guten fehlercodebasierten Programms ist jedoch immer noch viel einfacher als ein ausnahmebasiertes Programm zu schreiben . Und wenn einer aus irgendeinem Grund beschließt, alle Fehler (die alten on error resume next) zu ignorieren , können Sie dies leicht mit Rückkehrcodes tun, und Sie können dies nicht ohne viele Try-Catches tun.

Die zweitbeliebteste Kritik an Rückkehrcodes ist, dass "es schwierig ist, in die Luft zu sprudeln" - aber das liegt daran, dass die Leute nicht verstehen, dass Ausnahmen für nicht wiederherstellbare Situationen gelten, während Fehlercodes dies nicht tun.

Die Entscheidung zwischen Ausnahmen und Fehlercodes ist eine Grauzone. Es ist sogar möglich, dass Sie einen Fehlercode von einer wiederverwendbaren Geschäftsmethode erhalten müssen, und dann entscheiden Sie sich, diesen in eine Ausnahme zu packen (möglicherweise Informationen hinzuzufügen) und ihn in die Luft sprudeln zu lassen. Es ist jedoch ein Konstruktionsfehler anzunehmen, dass ALLE Fehler als Ausnahmen ausgelöst werden sollten.

Etwas zusammenfassen:

  • Ich verwende gerne Ausnahmen, wenn ich eine unerwartete Situation habe, in der nicht viel zu tun ist, und normalerweise möchten wir einen großen Codeblock oder sogar die gesamte Operation oder das gesamte Programm abbrechen. Dies ist wie das alte "on error goto".

  • Ich verwende gerne Rückkehrcodes, wenn ich Situationen erwartet habe, in denen der Anrufercode Maßnahmen ergreifen kann / sollte. Dies umfasst die meisten Geschäftsmethoden, APIs, Validierungen usw.

Dieser Unterschied zwischen Ausnahmen und Fehlercodes ist eines der Entwurfsprinzipien der GO-Sprache, die "Panik" für schwerwiegende unerwartete Situationen verwendet, während regelmäßig erwartete Situationen als Fehler zurückgegeben werden.

Bei GO sind jedoch auch mehrere Rückgabewerte zulässig , was bei der Verwendung von Rückkehrcodes sehr hilfreich ist, da Sie gleichzeitig einen Fehler und etwas anderes zurückgeben können. Unter C # / Java können wir dies ohne Parameter, Tupel oder (meine Lieblings-) Generika erreichen, die in Kombination mit Aufzählungen dem Aufrufer eindeutige Fehlercodes liefern können:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

Wenn ich meiner Methode eine neue mögliche Rückgabe hinzufüge, kann ich sogar alle Aufrufer überprüfen, ob sie diesen neuen Wert beispielsweise in einer switch-Anweisung abdecken. Mit Ausnahmen kann man das wirklich nicht machen. Wenn Sie Rückkehrcodes verwenden, kennen Sie normalerweise alle möglichen Fehler im Voraus und testen sie. Mit Ausnahmen wissen Sie normalerweise nicht, was passieren könnte. Das Umschließen von Aufzählungen in Ausnahmen (anstelle von Generika) ist eine Alternative (solange klar ist, welche Art von Ausnahmen jede Methode auslösen wird), aber IMO ist das Design immer noch schlecht.

drizin
quelle
4

Meine Argumentation wäre, wenn Sie einen Low-Level-Treiber schreiben, der wirklich Leistung benötigt, und dann Fehlercodes verwenden. Wenn Sie diesen Code jedoch in einer übergeordneten Anwendung verwenden und ein wenig Overhead bewältigen kann, schließen Sie diesen Code mit einer Schnittstelle ein, die diese Fehlercodes überprüft und Ausnahmen auslöst.

In allen anderen Fällen sind Ausnahmen wahrscheinlich der richtige Weg.

Claudiu
quelle
4

Ich kann hier auf dem Zaun sitzen, aber ...

  1. Das hängt von der Sprache ab.
  2. Egal für welches Modell Sie sich entscheiden, seien Sie konsequent, wie Sie es verwenden.

In Python ist die Verwendung von Ausnahmen Standard, und ich bin sehr glücklich, meine eigenen Ausnahmen zu definieren. In C gibt es überhaupt keine Ausnahmen.

In C ++ (zumindest in der STL) werden Ausnahmen normalerweise nur für wirklich außergewöhnliche Fehler ausgelöst (ich sehe sie selbst praktisch nie). Ich sehe keinen Grund, in meinem eigenen Code etwas anderes zu tun. Ja, es ist einfach, Rückgabewerte zu ignorieren, aber C ++ zwingt Sie auch nicht, Ausnahmen abzufangen. Ich denke, man muss sich nur angewöhnen, es zu tun.

Die Codebasis, an der ich arbeite, ist hauptsächlich C ++ und wir verwenden fast überall Fehlercodes, aber es gibt ein Modul, das Ausnahmen für Fehler auslöst, einschließlich sehr ungewöhnlicher, und der gesamte Code, der dieses Modul verwendet, ist ziemlich schrecklich. Dies kann jedoch nur daran liegen, dass wir Ausnahmen und Fehlercodes gemischt haben. Der Code, der konsequent Fehlercodes verwendet, ist viel einfacher zu bearbeiten. Wenn unser Code konsequent Ausnahmen verwendet, wäre es vielleicht nicht so schlimm. Das Mischen der beiden scheint nicht so gut zu funktionieren.

jrb
quelle
4

Da ich mit C ++ arbeite und RAII habe, um die Verwendung sicher zu machen, verwende ich fast ausschließlich Ausnahmen. Es zieht die Fehlerbehandlung aus dem normalen Programmablauf und macht die Absicht klarer.

Ich lasse jedoch Ausnahmen für außergewöhnliche Umstände. Wenn ich erwarte, dass ein bestimmter Fehler häufig auftritt, überprüfe ich, ob der Vorgang erfolgreich ist, bevor ich ihn ausführe, oder rufe eine Version der Funktion auf, die stattdessen Fehlercodes verwendet (Gefällt mir TryParse()).

Finsternis
quelle
3

Methodensignaturen sollten Ihnen mitteilen, was die Methode tut. So etwas wie long errorCode = getErrorCode (); mag in Ordnung sein, aber long errorCode = fetchRecord (); ist verwirrend.

Paul Croarkin
quelle
3

Mein Ansatz ist, dass wir beide, dh Ausnahmen- und Fehlercodes, gleichzeitig verwenden können.

Ich werde verwendet, um verschiedene Arten von Ausnahmen zu definieren (z. B. DataValidationException oder ProcessInterruptExcepion) und in jeder Ausnahme eine detailliertere Beschreibung jedes Problems zu definieren.

Ein einfaches Beispiel in Java:

public class DataValidationException extends Exception {


    private DataValidation error;

    /**
     * 
     */
    DataValidationException(DataValidation dataValidation) {
        super();
        this.error = dataValidation;
    }


}

enum DataValidation{

    TOO_SMALL(1,"The input is too small"),

    TOO_LARGE(2,"The input is too large");


    private DataValidation(int code, String input) {
        this.input = input;
        this.code = code;
    }

    private String input;

    private int code;

}

Auf diese Weise verwende ich Ausnahmen, um Kategoriefehler zu definieren, und Fehlercodes, um detailliertere Informationen über das Problem zu definieren.

Sakana
quelle
2
ähm ... throw new DataValidationException("The input is too small")? Einer der Vorteile von Ausnahmen besteht darin, detaillierte Informationen zuzulassen.
Eva
2

Ausnahmen gelten für außergewöhnliche Umstände, dh wenn sie nicht Teil des normalen Codeflusses sind.

Es ist durchaus legitim, Ausnahmen und Fehlercodes zu mischen, wobei Fehlercodes den Status von etwas darstellen und nicht einen Fehler bei der Ausführung des Codes an sich (z. B. Überprüfen des Rückkehrcodes eines untergeordneten Prozesses).

Aber wenn ein außergewöhnlicher Umstand eintritt, glaube ich, dass Ausnahmen das ausdrucksstärkste Modell sind.

Es gibt Fälle, in denen Sie möglicherweise Fehlercodes anstelle von Ausnahmen bevorzugen oder verwenden müssen, und diese wurden bereits angemessen behandelt (abgesehen von anderen offensichtlichen Einschränkungen wie der Compilerunterstützung).

Wenn Sie jedoch in die andere Richtung gehen und Ausnahmen verwenden, können Sie noch höhere Abstraktionen für Ihre Fehlerbehandlung erstellen, wodurch Ihr Code noch ausdrucksvoller und natürlicher wird. Ich würde wärmstens empfehlen, diesen ausgezeichneten, aber unterschätzten Artikel des C ++ - Experten Andrei Alexandrescu zum Thema "Enforcements" zu lesen: http://www.ddj.com/cpp/184403864 . Obwohl es sich um einen C ++ - Artikel handelt, sind die Prinzipien allgemein anwendbar, und ich habe das Durchsetzungskonzept recht erfolgreich in C # übersetzt.

philsquared
quelle
2

Erstens stimme ich Toms Antwort zu dass für High-Level-Inhalte Ausnahmen verwendet werden und für Low-Level-Inhalte Fehlercodes verwendet werden, sofern es sich nicht um Service Oriented Architecture (SOA) handelt.

In SOA, wo Methoden auf verschiedenen Computern aufgerufen werden können, werden Ausnahmen möglicherweise nicht über die Leitung übertragen. Stattdessen verwenden wir Erfolgs- / Fehlerantworten mit einer Struktur wie unten (C #):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

Und verwenden Sie wie folgt:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

Wenn diese in Ihren Serviceantworten konsistent verwendet werden, entsteht ein sehr schönes Muster für die Behandlung von Erfolg / Misserfolg in der Anwendung. Dies ermöglicht eine einfachere Fehlerbehandlung bei asynchronen Aufrufen innerhalb von Diensten sowie zwischen Diensten.

orad
quelle
1

Ich würde Ausnahmen für alle Fehlerfälle bevorzugen, außer wenn ein Fehler ein erwartbares fehlerfreies Ergebnis einer Funktion ist, die einen primitiven Datentyp zurückgibt. Wenn Sie beispielsweise den Index eines Teilstrings in einer größeren Zeichenfolge finden, wird normalerweise -1 zurückgegeben, wenn er nicht gefunden wird, anstatt eine NotFoundException auszulösen.

Die Rückgabe ungültiger Zeiger, die möglicherweise dereferenziert sind (z. B. NullPointerException in Java verursachen), ist nicht zulässig.

Die Verwendung mehrerer verschiedener numerischer Fehlercodes (-1, -2) als Rückgabewerte für dieselbe Funktion ist normalerweise ein schlechter Stil, da Clients möglicherweise eine "== -1" -Prüfung anstelle von "<0" durchführen.

Eine Sache, die Sie hier beachten sollten, ist die Entwicklung der APIs im Laufe der Zeit. Eine gute API ermöglicht es, das Fehlerverhalten auf verschiedene Arten zu ändern und zu erweitern, ohne Clients zu beschädigen. Wenn beispielsweise ein Client-Fehlerhandle auf 4 Fehlerfälle überprüft wurde und Sie Ihrer Funktion einen fünften Fehlerwert hinzufügen, testet der Client-Handler dies möglicherweise nicht und bricht ab. Wenn Sie Ausnahmen auslösen, wird dies für Clients normalerweise einfacher, auf eine neuere Version einer Bibliothek zu migrieren.

Eine andere zu berücksichtigende Sache ist, wenn Sie in einem Team arbeiten, wo eine klare Linie für alle Entwickler gezogen werden muss, um eine solche Entscheidung zu treffen. ZB "Ausnahmen für High-Level-Sachen, Fehlercodes für Low-Level-Sachen" ist sehr subjektiv.

In jedem Fall, in dem mehr als ein trivialer Fehlertyp möglich ist, sollte der Quellcode niemals das numerische Literal verwenden, um einen Fehlercode zurückzugeben oder ihn zu behandeln (Rückgabe -7, wenn x == -7 ...), aber immer eine benannte Konstante (return NO_SUCH_FOO, wenn x == NO_SUCH_FOO).

tkruse
quelle
1

Wenn Sie unter einem großen Projekt arbeiten, können Sie nicht nur Ausnahmen oder nur Fehlercodes verwenden. In verschiedenen Fällen sollten Sie unterschiedliche Ansätze verwenden.

Beispielsweise möchten Sie nur Ausnahmen verwenden. Sobald Sie sich jedoch für die asynchrone Ereignisverarbeitung entschieden haben. In diesen Situationen ist es nicht ratsam, Ausnahmen für die Fehlerbehandlung zu verwenden. Die Verwendung von Fehlercodes überall in der Anwendung ist jedoch mühsam.

Daher ist es meiner Meinung nach normal, Ausnahmen und Fehlercodes gleichzeitig zu verwenden.

Gomons
quelle
0

Für die meisten Anwendungen sind Ausnahmen besser. Die Ausnahme ist, wenn die Software mit anderen Geräten kommunizieren muss. Die Domäne, in der ich arbeite, sind industrielle Steuerungen. Hier werden Fehlercodes bevorzugt und erwartet. Meine Antwort ist also, dass es von der Situation abhängt.

Jim C.
quelle
0

Ich denke, es hängt auch davon ab, ob Sie wirklich Informationen wie Stack-Trace aus dem Ergebnis benötigen. Wenn ja, entscheiden Sie sich auf jeden Fall für eine Ausnahme, die ein Objekt mit vielen Informationen zum Problem enthält. Wenn Sie jedoch nur am Ergebnis interessiert sind und sich nicht darum kümmern, warum dieses Ergebnis angezeigt wird, wählen Sie den Fehlercode.

Beispiel: Wenn Sie eine Datei bearbeiten und mit IOException konfrontiert sind, ist der Client möglicherweise daran interessiert zu wissen, von wo aus dies ausgelöst wurde, eine Datei zu öffnen oder eine Datei zu analysieren usw. Sie sollten also besser IOException oder seine spezifische Unterklasse zurückgeben. In einem Szenario, in dem Sie eine Anmeldemethode haben und wissen möchten, ob diese erfolgreich war oder nicht, geben Sie entweder nur einen Booleschen Wert zurück oder geben einen Fehlercode zurück, um die richtige Meldung anzuzeigen. Hier ist der Client nicht daran interessiert zu wissen, welcher Teil der Logik diesen Fehlercode verursacht hat. Er weiß nur, ob sein Berechtigungsnachweis ungültig ist oder die Kontosperre usw.

Ein anderer Anwendungsfall, an den ich denken kann, ist, wenn Daten im Netzwerk übertragen werden. Ihre Remote-Methode kann anstelle von Exception nur Fehlercode zurückgeben, um die Datenübertragung zu minimieren.

Ashah
quelle
0

Meine allgemeine Regel lautet:

  • In einer Funktion kann nur ein Fehler auftreten: Fehlercode verwenden (als Parameter der Funktion)
  • Es kann mehr als ein spezifischer Fehler auftreten: Ausnahme auslösen
DEADBEEF
quelle
-1

Fehlercodes funktionieren auch nicht, wenn Ihre Methode etwas anderes als einen numerischen Wert zurückgibt ...

Omar Kooheji
quelle
3
Ähm, nein. Siehe win32 GetLastError () -Paradigma. Ich verteidige es nicht, sondern stelle nur fest, dass Sie falsch sind.
Tim
4
In der Tat gibt es viele andere Möglichkeiten, dies zu tun. Eine andere Möglichkeit besteht darin, ein Objekt zurückzugeben, das den Fehlercode und den tatsächlichen Rückgabewert enthält. Ein weiterer Weg ist die Referenzübergabe.
Pacerier