Diese Frage gilt für alle OO-Programmiersprachen, die die Ausnahmebehandlung unterstützen. Ich verwende C # nur zu Illustrationszwecken.
Ausnahmen sollen normalerweise ausgelöst werden, wenn ein Problem auftritt, das der Code nicht sofort verarbeiten kann, und dann in einer catch
Klausel an einer anderen Stelle (normalerweise einem äußeren Stapelrahmen) abgefangen werden .
F: Gibt es legitime Situationen, in denen Ausnahmen nicht ausgelöst und abgefangen, sondern einfach von einer Methode zurückgegeben und dann als Fehlerobjekte weitergegeben werden?
Diese Frage stellte sich für mich, weil die .NET 4- System.IObserver<T>.OnError
Methode genau Folgendes vorschlägt: Ausnahmen, die als Fehlerobjekte weitergegeben werden.
Schauen wir uns ein anderes Szenario an, die Validierung. Angenommen, ich folge der üblichen Weisheit und unterscheide daher zwischen einem Fehlerobjekttyp IValidationError
und einem separaten Ausnahmetyp ValidationException
, mit dem unerwartete Fehler gemeldet werden:
partial interface IValidationError { }
abstract partial class ValidationException : System.Exception
{
public abstract IValidationError[] ValidationErrors { get; }
}
(Der System.Component.DataAnnotations
Namespace verhält sich ähnlich.)
Diese Typen könnten wie folgt eingesetzt werden:
partial interface IFoo { } // an immutable type
partial interface IFooBuilder // mutable counterpart to prepare instances of above type
{
bool IsValid(out IValidationError[] validationErrors); // true if no validation error occurs
IFoo Build(); // throws ValidationException if !IsValid(…)
}
Jetzt frage ich mich, ob ich das oben Gesagte nicht vereinfachen könnte:
partial class ValidationError : System.Exception { } // = IValidationError + ValidationException
partial interface IFoo { } // (unchanged)
partial interface IFooBuilder
{
bool IsValid(out ValidationError[] validationErrors);
IFoo Build(); // may throw ValidationError or sth. like AggregateException<ValidationError>
}
F: Was sind die Vor- und Nachteile dieser beiden unterschiedlichen Ansätze?
AggregateException
stattdessen? Guter Punkt.Antworten:
Wenn es nie geworfen wird, ist es keine Ausnahme. Es ist ein Objekt
derived
aus einer Exception-Klasse, obwohl es dem Verhalten nicht folgt. Zu diesem Zeitpunkt ist es eine reine Semantik, es eine Ausnahme zu nennen, aber ich sehe nichts Falsches daran, es nicht zu werfen. Aus meiner Sicht ist es genau dasselbe, eine Ausnahme nicht auszulösen, wie eine innere Funktion, die sie abfängt und ihre Ausbreitung verhindert.Ist es für eine Funktion gültig, Ausnahmeobjekte zurückzugeben?
Absolut. Hier ist eine kurze Liste von Beispielen, wo dies angebracht sein kann:
Ist es nicht schlimm zu werfen?
Eine Ausnahme auszulösen ist wie folgt: "Geh ins Gefängnis. Geh nicht vorbei!" im Brettspiel Monopoly. Es weist einen Compiler an, den gesamten Quellcode bis zum Abfangen zu überspringen, ohne einen dieser Quellcodes auszuführen. Es hat nichts mit Fehlern, dem Melden oder Stoppen von Fehlern zu tun. Ich denke gerne daran, eine Ausnahme als "super return" -Anweisung für eine Funktion auszulösen. Die Ausführung des Codes wird an eine Stelle zurückgegeben, die viel höher ist als die des ursprünglichen Aufrufers.
Das Wichtigste hierbei ist, zu verstehen, dass der wahre Wert von Ausnahmen im
try/catch
Muster und nicht in der Instanziierung des Ausnahmeobjekts liegt. Das Ausnahmeobjekt ist nur eine Statusnachricht.In Ihrer Frage scheinen Sie die Verwendung dieser beiden Dinge zu verwechseln: Ein Sprung zu einem Handler der Ausnahme und der Fehlerzustand, den die Ausnahme darstellt. Nur weil Sie einen Fehlerstatus angenommen und ihn in eine Ausnahme eingeschlossen haben, bedeutet dies nicht, dass Sie dem
try/catch
Muster oder seinen Vorteilen folgen .quelle
Exception
Klasse abgeleitet wurde, aber nicht dem Verhalten folgt." - Und genau das ist das Problem: Ist es legitim, einException
abgeleitetes Objekt in einer Situation zu verwenden, in der es weder vom Hersteller des Objekts noch von einer aufrufenden Methode geworfen wird? wo es nur als "Zustandsnachricht" dient, die herumgereicht wird, ohne den Kontrollfluss "Super Return"? Oder bin ich zu verletzen einen unausgesprochenentry
/catch(Exception)
Vertrag so schlecht , dass ich dies nie tun, und verwenden Sie stattdessen einen separaten, nichtException
Typen?No
Sie keine Verträge brechen. Daspopular opinion
wäre, dass Sie das nicht tun sollten. Die Programmierung steckt voller Beispiele, in denen Ihr Quellcode nichts falsch macht, aber jeder es nicht mag. Der Quellcode sollte immer so geschrieben werden, dass klar ist, was die Absicht ist. Helfen Sie wirklich einem anderen Programmierer, indem Sie auf diese Weise eine Ausnahme verwenden? Wenn ja, dann mach das. Ansonsten wundere dich nicht, wenn es dir nicht gefällt.Das Zurückgeben von Ausnahmen, anstatt sie auszulösen, kann semantisch sinnvoll sein, wenn Sie eine Hilfsmethode zum Analysieren der Situation haben und eine entsprechende Ausnahme zurückgeben, die dann vom Aufrufer ausgelöst wird (Sie können dies eine "Ausnahmefactory" nennen). Das Auslösen einer Ausnahme in dieser Fehleranalysefunktion würde bedeuten, dass während der Analyse selbst ein Fehler aufgetreten ist, während das Zurückgeben einer Ausnahme bedeutet, dass die Art des Fehlers erfolgreich analysiert wurde.
Ein möglicher Anwendungsfall könnte eine Funktion sein, die HTTP-Antwortcodes in Ausnahmen verwandelt :
Beachten Sie, dass das Auslösen einer Ausnahme bedeutet, dass die Methode falsch verwendet wurde oder einen internen Fehler aufwies, während das Zurückgeben einer Ausnahme bedeutet, dass der Fehlercode erfolgreich identifiziert wurde.
quelle
return
Anweisungen erforderlich , damit der Compiler aufhört, sich zu beschweren.System.Exit()
. Der Code nach dem Funktionsaufruf wird nicht ausgeführt. Das klingt nicht nach einem guten Entwurfsmuster für eine Funktion.Wenn das Ausnahmeobjekt das erwartete Ergebnis der Operation ist, dann ja. Die Fälle, an die ich denken kann, beinhalten jedoch immer irgendwann einen Wurffang:
Variationen des Musters "Try" (wobei eine Methode eine zweite Methode zum Auslösen von Ausnahmen einschließt, die Ausnahme jedoch abfängt und stattdessen einen Booleschen Wert zurückgibt, der den Erfolg angibt). Anstatt einen Booleschen Wert zurückzugeben, können Sie eine beliebige ausgelöste Ausnahme zurückgeben (null, wenn dies erfolgreich war). Auf diese Weise erhält der Endbenutzer mehr Informationen, während die Erfolgs- oder Fehleranzeige beibehalten wird.
Workflow-Fehlerbehandlung. Ähnlich wie bei der Kapselung von "try pattern" -Methoden ist möglicherweise ein Workflow-Schritt in einem Befehlskettenmuster abstrahiert. Wenn ein Glied in der Kette eine Ausnahme auslöst, ist es häufig einfacher, diese Ausnahme im abstrahierenden Objekt abzufangen und als Ergebnis dieser Operation zurückzugeben, sodass die Workflow-Engine und der eigentliche Code, die als Workflow-Schritt ausgeführt werden, keinen umfangreichen Versuch erfordern -Fanglogik für sich.
quelle
OperationResult
Klasse mit derbool
Eigenschaft "Successful", einerGetExceptionIfAny
Methode und einerAssertSuccessful
Methode zu haben (die die Ausnahme auslösen würde,GetExceptionIfAny
wenn sie nicht null ist). NachdemGetExceptionIfAny
ein Verfahren sein , anstatt eine Eigenschaft würde die Verwendung von statischen unveränderlichen Fehlerobjekten für Fälle ermöglichen , in denen es nicht viel sinnvoll zu sagen war.Nehmen wir an, Sie haben eine Aufgabe in einem Thread-Pool ausgeführt. Wenn diese Aufgabe eine Ausnahme auslöst, handelt es sich um einen anderen Thread, sodass Sie ihn nicht abfangen. Thread wo es ausgeführt wurde einfach sterben.
Stellen Sie sich nun vor, dass etwas (entweder Code in dieser Task oder die Thread-Pool-Implementierung) diese Ausnahme abfängt, und speichern Sie sie zusammen mit der Task, und betrachten Sie die Task als abgeschlossen (nicht erfolgreich). Jetzt können Sie fragen, ob die Aufgabe beendet ist (und entweder fragen, ob sie geworfen wurde oder ob sie erneut in Ihren Thread geworfen werden kann (oder besser, neue Ausnahme mit Original als Ursache)).
Wenn Sie es manuell machen, können Sie feststellen, dass Sie eine neue Ausnahme erstellen, diese auslösen und abfangen und dann in einem anderen Thread speichern, indem Sie sie abrufen, abfangen und darauf reagieren. Es kann also sinnvoll sein, das Werfen und Fangen zu überspringen, es einfach zu speichern und die Aufgabe zu beenden und dann einfach zu fragen, ob es eine Ausnahme gibt, und darauf zu reagieren. Dies kann jedoch zu einem komplizierteren Code führen, wenn an derselben Stelle wirklich Ausnahmen ausgelöst werden.
PS: Dies wurde mit Erfahrung in Java geschrieben, wo Stack-Trace-Informationen erstellt werden, wenn eine Ausnahme erstellt wird (im Gegensatz zu C #, wo sie beim Auslösen erstellt werden). Ein in Java nicht ausgelöster Ausnahmeansatz ist daher langsamer als in C # (sofern er nicht vorab erstellt und wiederverwendet wird), verfügt jedoch über Stack-Trace-Informationen.
Im Allgemeinen würde ich es vermeiden, eine Ausnahme zu erstellen und diese niemals auszulösen (es sei denn, die Leistungsoptimierung nach der Profilerstellung weist auf einen Engpass hin). Zumindest in Java, wo das Erstellen von Ausnahmen teuer ist (Stack-Trace). In C # ist es möglich, aber IMO ist es überraschend und sollte daher vermieden werden.
quelle
Es hängt von Ihrem Design ab, normalerweise würde ich einem Anrufer keine Ausnahmen zurückgeben, sondern sie werfen und fangen und dabei belassen. Normalerweise wird Code so geschrieben, dass er früh und schnell fehlschlägt. Betrachten Sie beispielsweise den Fall, dass eine Datei geöffnet und verarbeitet wird (dies ist C # PsuedoCode):
In diesem Fall würden wir einen Fehler machen und die Verarbeitung der Datei beenden, sobald ein fehlerhafter Datensatz festgestellt wird.
Angenommen, wir möchten die Datei weiter verarbeiten, auch wenn eine oder mehrere Zeilen fehlerhaft sind. Der Code könnte ungefähr so aussehen:
In diesem Fall geben wir also die Ausnahme an den Aufrufer zurück, obwohl ich wahrscheinlich keine Ausnahme zurückgeben würde, sondern eine Art Fehlerobjekt, da die Ausnahme abgefangen und bereits behandelt wurde. Dies ist kein Nachteil beim Zurückgeben einer Ausnahme, aber andere Aufrufer könnten die Ausnahme erneut auslösen, was möglicherweise nicht wünschenswert ist, da das Ausnahmeobjekt dieses Verhalten aufweist. Wenn Sie möchten, dass Anrufer die Möglichkeit haben, eine Ausnahme erneut auszulösen, geben Sie andernfalls die Informationen aus dem Ausnahmeobjekt heraus, erstellen Sie ein kleineres, leichtes Objekt und geben Sie dieses stattdessen zurück. Das schnelle Versagen ist in der Regel sauberer Code.
In Ihrem Überprüfungsbeispiel würde ich wahrscheinlich keine Ausnahmeklasse erben oder Ausnahmen auslösen, da Überprüfungsfehler häufig vorkommen können. Es wäre weniger aufwändig, ein Objekt zurückzugeben, als eine Ausnahme auszulösen, wenn 50% meiner Benutzer ein Formular beim ersten Versuch nicht korrekt ausfüllen.
quelle
Ja. Ich bin zum Beispiel kürzlich auf eine Situation gestoßen, in der ich festgestellt habe, dass ausgelöste Ausnahmen in einem ASMX-Webdienst kein Element in der resultierenden SOAP-Nachricht enthalten, sodass ich sie generieren musste.
Illustrativer Code:
quelle
In den meisten Sprachen (afaik) gibt es Ausnahmen mit zusätzlichen Extras. Meist wird eine Momentaufnahme des aktuellen Stack-Trace im Objekt gespeichert. Dies ist nützlich für das Debuggen, kann aber auch Auswirkungen auf den Arbeitsspeicher haben.
Wie @ThinkingMedia bereits sagte, verwenden Sie die Ausnahme wirklich als Fehlerobjekt.
In Ihrem Codebeispiel tun Sie dies anscheinend hauptsächlich zur Wiederverwendung von Code und um Kompositionen zu vermeiden. Ich persönlich halte das nicht für einen guten Grund.
Ein weiterer möglicher Grund wäre, die Sprache so auszutricksen, dass Sie ein Fehlerobjekt mit einem Stack-Trace erhalten. Dies gibt Ihrem Fehlerbehandlungscode zusätzliche kontextbezogene Informationen.
Andererseits können wir davon ausgehen, dass das Aufrechterhalten der Stapelverfolgung Speicherkosten verursacht. Wenn Sie beispielsweise anfangen, diese Objekte irgendwo zu sammeln, kann dies zu einem unangenehmen Gedächtnisverlust führen. Dies hängt natürlich stark davon ab, wie Ausnahmestapel-Traces in der jeweiligen Sprache / Engine implementiert sind.
Also, ist es eine gute Idee?
Bisher habe ich nicht gesehen, dass es verwendet oder empfohlen wird. Das bedeutet aber nicht viel.
Die Frage ist: Ist der Stack-Trace wirklich nützlich für Ihre Fehlerbehandlung? Oder allgemeiner: Welche Informationen möchten Sie dem Entwickler oder Administrator bereitstellen?
Das typische Wurf / Versuch / Fang-Muster macht es irgendwie notwendig, einen Stapel-Trace zu haben, da Sie sonst keine Ahnung haben, woher er kommt. Für ein zurückgegebenes Objekt stammt es immer aus der aufgerufenen Funktion. Der Stack-Trace enthält möglicherweise alle Informationen, die der Entwickler benötigt. Möglicherweise reicht jedoch etwas weniger Schweres und Spezifischeres aus.
quelle
Die Antwort ist ja. Weitere Informationen zu Ausnahmen finden Sie unter http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions. Öffnen Sie den Pfeil für den C ++ - Stilleitfaden von Google. Sie können dort die Argumente sehen, für die sie sich entschieden haben, aber sagen, dass sie sich wahrscheinlich entschieden hätten, wenn sie es noch einmal tun müssten.
Es ist jedoch anzumerken, dass die Sprache Go aus ähnlichen Gründen auch nicht idiomatisch Ausnahmen verwendet.
quelle