Mir wurde gesagt, dass Ausnahmen nur in Ausnahmefällen verwendet werden sollten. Woher weiß ich, ob mein Fall außergewöhnlich ist?

99

In meinem speziellen Fall kann der Benutzer einen String in die Anwendung übergeben, die Anwendung analysiert ihn und weist ihn strukturierten Objekten zu. Manchmal kann der Benutzer etwas Ungültiges eingeben. Zum Beispiel kann ihre Eingabe eine Person beschreiben, aber sie können sagen, dass ihr Alter "Apfel" ist. In diesem Fall setzen Sie die Transaktion zurück und teilen dem Benutzer mit, dass ein Fehler aufgetreten ist, und er muss es erneut versuchen. Möglicherweise muss nicht nur der erste Fehler gemeldet werden, sondern auch jeder Fehler, den wir in der Eingabe finden.

In diesem Fall habe ich argumentiert, wir sollten eine Ausnahme auslösen. Er war anderer Meinung und sagte: "Ausnahmen sollten außergewöhnlich sein: Es wird erwartet, dass der Benutzer ungültige Daten eingibt, so dass dies kein Ausnahmefall ist." scheint richtig zu sein.

Ich verstehe jedoch, dass Ausnahmen deshalb überhaupt erst erfunden wurden. Früher war es Sie hatte das Ergebnis zu überprüfen , um festzustellen , ob ein Fehler aufgetreten ist . Wenn Sie dies nicht überprüfen, können schlimme Dinge passieren, ohne dass Sie es merken.

Ohne Ausnahmen muss jede Ebene des Stapels das Ergebnis der von ihnen aufgerufenen Methoden überprüfen. Wenn ein Programmierer vergisst, eine dieser Ebenen einzuchecken, kann der Code versehentlich fortfahren und beispielsweise ungültige Daten speichern. Scheint auf diese Weise fehleranfälliger zu sein.

Wie auch immer, zögern Sie nicht, alles zu korrigieren, was ich hier gesagt habe. Meine Hauptfrage ist, ob jemand sagt, dass Ausnahmen außergewöhnlich sein sollten. Woher weiß ich, ob mein Fall außergewöhnlich ist?

Daniel Kaplan
quelle
3
mögliches Duplikat? Wenn eine Ausnahme ausgelöst . Es war zwar dort geschlossen, aber ich denke es passt hierher. Es ist immer noch ein bisschen Philosophie, einige Leute und Gemeinschaften neigen dazu, Ausnahmen als eine Art Flusskontrolle zu betrachten.
Thorsten Müller
8
Wenn Benutzer dumm sind, geben sie ungültige Eingaben ein. Wenn Benutzer klug sind, spielen sie mit ungültigen Eingaben. Ungültige Benutzereingaben sind daher keine Ausnahme.
Mouviciel
7
Verwechseln Sie auch eine Ausnahme , die in Java und .NET ein sehr spezifischer Mechanismus ist, nicht mit einem Fehler, der ein viel allgemeinerer Begriff ist. Fehlerbehandlung ist mehr als das Auslösen von Ausnahmen. Diese Diskussion berührt die Nuancen zwischen Ausnahmen und Fehlern .
Eric King
4
"Exceptional"! = "Geschieht selten"
ConditionRacer
3
Ich halte Eric Lipperts ärgerliche Ausnahmen für anständige Ratschläge.
Brian

Antworten:

87

Es wurden Ausnahmen erfunden, um die Fehlerbehandlung mit weniger Code-Unordnung zu vereinfachen. Sie sollten sie in Fällen verwenden, in denen sie die Fehlerbehandlung mit weniger Code-Unordnung erleichtern. Diese "Ausnahmen nur für außergewöhnliche Umstände" stammen aus einer Zeit, in der die Ausnahmebehandlung als inakzeptabler Leistungseinbruch eingestuft wurde. Das ist in der überwiegenden Mehrheit des Codes nicht mehr der Fall, aber die Leute sprechen die Regel immer noch aus, ohne sich an den Grund dafür zu erinnern.

Vor allem in Java, der wahrscheinlich ausnahmeliebendsten Sprache, die jemals erfunden wurde, sollten Sie sich nicht schlecht fühlen, wenn Sie Ausnahmen verwenden , um Ihren Code zu vereinfachen. Tatsächlich verfügt Javas eigene IntegerKlasse nicht über die Möglichkeit, zu überprüfen, ob eine Zeichenfolge eine gültige Ganzzahl ist, ohne möglicherweise a auszulösen NumberFormatException.

Auch wenn Sie sich nicht nur auf die Benutzeroberflächenvalidierung verlassen können, sollten Sie bedenken, dass ein nicht numerischer Wert, der in das Back-End eingegeben wird, ein echter Wert ist, wenn Ihre Benutzeroberfläche ordnungsgemäß entworfen wurde, z außergewöhnlicher Zustand.

Karl Bielefeldt
quelle
10
Schöner Schlag, da. In der von mir entworfenen realen App hat sich der Performance-Hit tatsächlich ausgewirkt, und ich musste ihn ändern, um für bestimmte Parsing-Vorgänge keine Ausnahmen zu werfen.
Robert Harvey
17
Ich sage nicht, dass es nicht immer noch Fälle gibt, in denen der Leistungstreffer ein gültiger Grund ist, aber diese Fälle sind eher die Ausnahme (Wortspiel beabsichtigt) als die Regel.
Karl Bielefeldt
11
@RobertHarvey Der Trick in Java besteht darin, vorgefertigte Ausnahmeobjekte auszulösen und nicht throw new .... Sie können auch benutzerdefinierte Ausnahmen auslösen, bei denen fillInStackTrace () überschrieben wird. Dann sollten Sie keine Leistungseinbußen bemerken, ganz zu schweigen von Treffern .
Ingo
3
+1: Genau das, was ich antworten wollte. Verwenden Sie es, wenn es den Code vereinfacht. Ausnahmen können viel klareren Code liefern, bei dem Sie nicht auf jeder Ebene des Aufrufstapels die Rückgabewerte überprüfen müssen. (Wie alles andere auch, wenn es falsch verwendet wird, kann es Ihren Code zu einem schrecklichen Durcheinander machen.)
Leo
4
@Brendan Angenommen, es tritt eine Ausnahme auf, und der Fehlerbehandlungscode befindet sich 4 Ebenen tiefer im Aufrufstapel. Wenn Sie einen Fehlercode verwenden, müssen alle 4 Funktionen über dem Handler den Typ des Fehlercodes als Rückgabewert haben, und Sie müssen if (foo() == ERROR) { return ERROR; } else { // continue }auf jeder Ebene eine Kette von ausführen . Wenn Sie eine ungeprüfte Ausnahme auslösen, gibt es kein lautes und redundantes "if error return error". Wenn Sie Funktionen als Argumente übergeben, wird durch die Verwendung eines Fehlercodes möglicherweise die Funktionssignatur in einen inkompatiblen Typ geändert, obwohl der Fehler möglicherweise nicht auftritt.
Doval
72

Wann sollte eine Ausnahme ausgelöst werden? Wenn es um Code geht, halte ich die folgende Erklärung für sehr hilfreich:

Eine Ausnahme ist, wenn ein Mitglied die Aufgabe, die es ausführen soll, wie durch seinen Namen angegeben, nicht abschließt . (Jeffry Richter, CLR über C #)

Warum ist es hilfreich? Es legt nahe, dass es vom Kontext abhängt, wann etwas als Ausnahme behandelt werden soll oder nicht. Auf der Ebene der Methodenaufrufe wird der Kontext durch (a) den Namen, (b) die Signatur der Methode und (b) den Clientcode angegeben, der die Methode verwendet oder voraussichtlich verwendet.

Um Ihre Frage zu beantworten, sollten Sie sich den Code ansehen, in dem die Benutzereingaben verarbeitet werden. Es könnte ungefähr so ​​aussehen:

public void Save(PersonData personData) {  }

Weist der Methodenname darauf hin, dass eine Validierung durchgeführt wurde? Nein. In diesem Fall sollte eine ungültige PersonData eine Ausnahme auslösen.

Angenommen, die Klasse hat eine andere Methode, die so aussieht:

public ValidationResult Validate(PersonData personData) {  }

Weist der Methodenname darauf hin, dass eine Validierung durchgeführt wurde? Ja. In diesem Fall sollte eine ungültige PersonData keine Ausnahme auslösen.

Um die Dinge zusammenzufassen, schlagen beide Methoden vor, dass der Client-Code wie folgt aussehen sollte:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

Wenn nicht klar ist, ob eine Methode eine Ausnahme auslösen soll, liegt möglicherweise ein schlecht gewählter Methodenname oder eine schlecht gewählte Signatur vor. Vielleicht ist das Design der Klasse nicht klar. Manchmal müssen Sie das Code-Design ändern, um eine eindeutige Antwort auf die Frage zu erhalten, ob eine Ausnahme ausgelöst werden soll oder nicht.

Theo Lenndorff
quelle
Erst gestern habe ich ein struct"ValidationResult" erstellt und meinen Code so strukturiert, wie Sie ihn beschreiben.
Paul
4
Es hilft nicht, Ihre Frage zu beantworten, aber ich möchte nur darauf hinweisen, dass Sie implizit oder absichtlich das Prinzip der Trennung von Befehlen und Abfragen befolgt haben ( en.wikipedia.org/wiki/Command-query_separation ). ;-)
Theo Lenndorff
Gute Idee! Ein Nachteil: In Ihrem Beispiel wird die Validierung tatsächlich zweimal durchgeführt: Einmal während Validate(Rückgabe von False, wenn ungültig) und einmal während Save(Auslösen einer bestimmten, gut dokumentierten Ausnahme, wenn ungültig). Natürlich könnte das Überprüfungsergebnis im Objekt zwischengespeichert werden, dies würde jedoch die Komplexität erhöhen, da das Überprüfungsergebnis bei Änderungen ungültig werden müsste.
Heinzi
@Heinzi Ich stimme zu. Es könnte so überarbeitet werden, dass Validate()es innerhalb der Save()Methode aufgerufen wird, und bestimmte Details aus dem ValidationResultkönnten verwendet werden, um eine geeignete Nachricht für die Ausnahme zu erstellen.
Phil
3
Das ist besser als die akzeptierte Antwort, denke ich. Werfen, wenn der Anruf nicht das tut, was er tun sollte.
Andy
31

Ausnahmen sollten außergewöhnlich sein: Es wird erwartet, dass der Benutzer ungültige Daten eingibt. Dies ist also kein Ausnahmefall

Zu diesem Argument:

  • Es wird erwartet, dass eine Datei möglicherweise nicht vorhanden ist. Dies ist also kein Ausnahmefall.
  • Es wird erwartet, dass die Verbindung zum Server unterbrochen wird. Dies ist also kein Ausnahmefall
  • Es wird erwartet, dass die Konfigurationsdatei verstümmelt ist, so dass dies kein Ausnahmefall ist
  • Es wird erwartet, dass Ihre Anfrage manchmal ausfällt, so dass dies kein Ausnahmefall ist

Jede Ausnahme, die Sie fangen, müssen Sie erwarten, weil Sie sich entschieden haben, sie zu fangen. Nach dieser Logik sollten Sie also niemals Ausnahmen auslösen, die Sie tatsächlich abfangen möchten.

Daher denke ich, dass "Ausnahmen sollten außergewöhnlich sein" eine schreckliche Faustregel ist.

Was Sie tun sollten, hängt von der Sprache ab. Verschiedene Sprachen haben unterschiedliche Konventionen, wann Ausnahmen ausgelöst werden sollen. Python löst zum Beispiel Ausnahmen für alles aus, und wenn ich in Python bin, folge ich diesem Beispiel. C ++ hingegen wirft relativ wenige Ausnahmen, und da folge ich. Sie können C ++ oder Java wie Python behandeln und Ausnahmen für alles auslösen, aber Sie arbeiten nicht daran, wie die Sprache verwendet werden soll.

Ich bevorzuge Pythons Herangehensweise, halte es aber für eine schlechte Idee, andere Sprachen einzuschleusen.

Winston Ewert
quelle
1
@gnat, ich weiß. Mein Punkt war, dass Sie die Konventionen der Sprache (in diesem Fall Java) befolgen sollten, auch wenn sie nicht Ihr Favorit sind.
Winston Ewert
6
+1 "exceptions should be exceptional" is a terrible rule of thumb.Gut gesagt! Das ist eines dieser Dinge, die Leute wiederholen, ohne darüber nachzudenken.
Andres F.
2
"Erwartet" wird nicht durch subjektive Argumente oder Konventionen definiert, sondern durch den Vertrag der API / Funktion (dies kann explizit sein, wird aber oft nur impliziert). Unterschiedliche Funktionen / APIs / Subsysteme können unterschiedliche Erwartungen haben, z. B. ist für einige Funktionen auf höherer Ebene eine nicht vorhandene Datei ein zu erwartender Fall (sie kann dies einem Benutzer über eine GUI melden), für andere Funktionen auf niedrigerer Ebene ist dies der Fall wahrscheinlich nicht (und sollte daher eine Ausnahme auslösen). Diese Antwort scheint diesen wichtigen Punkt zu verfehlen ...
mikera
1
@mikera, ja, eine Funktion sollte (nur) die in ihrem Vertrag definierten Ausnahmen auslösen. Aber das ist nicht die Frage. Die Frage ist, wie Sie entscheiden, was dieser Vertrag sein soll. Ich behaupte, dass die Faustregel "Ausnahmen sollten außergewöhnlich sein" bei dieser Entscheidung nicht hilfreich ist.
Winston Ewert
1
@supercat, ich denke nicht, dass es wirklich wichtig ist, was am Ende häufiger auftritt. Ich denke, die kritische Frage ist, dass es einen vernünftigen Standard gibt. Wenn ich die Fehlerbedingung nicht explizit behandle, tut mein Code dann so, als wäre nichts passiert, oder erhalte ich eine nützliche Fehlermeldung?
Winston Ewert
30

Ich denke immer an Dinge wie den Zugriff auf den Datenbankserver oder eine Web-API, wenn ich an Ausnahmen denke. Sie erwarten, dass die Server- / Web-API funktioniert, in Ausnahmefällen jedoch möglicherweise nicht (der Server ist ausgefallen). Eine Webanforderung ist in der Regel schnell, in Ausnahmefällen (hohe Auslastung) tritt jedoch möglicherweise eine Zeitüberschreitung auf. Dies liegt außerhalb Ihrer Kontrolle.

Die Eingabedaten Ihrer Benutzer liegen in Ihrer Hand, da Sie überprüfen können, was diese senden und damit tun, was Sie möchten. In Ihrem Fall würde ich die Benutzereingaben validieren, bevor ich überhaupt versuche, sie zu speichern. Ich stimme eher zu, dass Benutzer, die ungültige Daten bereitstellen, erwartet werden sollten und Ihre App dies berücksichtigen sollte, indem sie die Eingabe validiert und die benutzerfreundliche Fehlermeldung ausgibt.

Trotzdem verwende ich in den meisten meiner Domain Model Setter Ausnahmen, bei denen es absolut unwahrscheinlich ist, dass ungültige Daten eingehen. Dies ist jedoch eine letzte Verteidigungslinie, und ich neige dazu, meine Eingabeformulare mit umfangreichen Validierungsregeln zu erstellen , so dass es praktisch keine Chance gibt, diese Domain-Modell-Ausnahme auszulösen. Wenn also ein Setter eine Sache erwartet und eine andere bekommt, ist dies eine Ausnahmesituation, die unter normalen Umständen nicht hätte eintreten dürfen.

EDIT (noch etwas zu beachten):

Wenn Sie vom Benutzer bereitgestellte Daten an die Datenbank senden, wissen Sie im Voraus, was Sie in Ihre Tabellen eingeben sollen und was nicht. Dies bedeutet, dass Daten anhand eines erwarteten Formats validiert werden können. Dies können Sie steuern. Was Sie nicht kontrollieren können, ist ein Serverausfall mitten in Ihrer Anfrage. Wenn Sie also wissen, dass die Abfrage in Ordnung ist und die Daten gefiltert / validiert wurden, versuchen Sie es mit der Abfrage und es schlägt immer noch fehl. Dies ist eine Ausnahmesituation.

In ähnlicher Weise können Sie bei den Webanforderungen nicht wissen, ob das Zeitlimit für die Anforderung abläuft oder die Verbindung nicht hergestellt werden kann, bevor Sie versuchen, sie zu senden. Daher ist dies auch ein Versuch / Fang-Ansatz, da Sie den Server nicht fragen können, ob er einige Millisekunden später funktioniert, wenn Sie die Anforderung senden.

Ivan Pintar
quelle
8
Aber wieso? Warum sind Ausnahmen bei der Behandlung von Problemen, die mehr erwartet werden, weniger nützlich?
Winston Ewert
6
@Pinetree: Überprüfen, ob eine Datei vorhanden ist, bevor eine Datei geöffnet wird, ist eine schlechte Idee. Die Datei existiert möglicherweise nicht mehr zwischen der Prüfung und dem Öffnen, die Datei verfügt möglicherweise nicht über die Berechtigung, mit der Sie sie öffnen können, und die Prüfung auf Existenz und das anschließende Öffnen der Datei erfordert zwei kostspielige Systemaufrufe. Es ist besser, wenn Sie versuchen, die Datei zu öffnen, und dies dann korrigieren.
Winston Ewert
10
Soweit ich sehen kann, sind so gut wie alle möglichen Fehler besser zu beheben, als vorab nach dem Erfolg zu suchen. Ob Sie Ausnahmen oder etwas anderes verwenden, das auf einen Fehler hinweist, ist ein separates Problem. Ich bevorzuge Ausnahmen, weil ich sie nicht versehentlich ignorieren kann.
Winston Ewert
11
Ich bin mit Ihrer Annahme nicht einverstanden, dass ungültige Benutzerdaten nicht als außergewöhnlich angesehen werden können, da sie erwartet werden. Wenn ich einen Parser schreibe und ihn jemand mit nicht analysierbaren Daten füttert, ist das eine Ausnahme. Ich kann nicht weiter analysieren. Wie die Ausnahme behandelt wird, ist eine ganz andere Frage.
ConditionRacer
4
File.ReadAllBytes löst FileNotFoundExceptionbei falscher Eingabe einen aus (z. B. einen nicht vorhandenen Dateinamen). Dies ist der einzig gültige Weg, um diesen Fehler zu gefährden. Was können Sie noch tun, ohne Fehlercodes zurückzugeben?
24.
16

Referenz

Vom Pragmatischen Programmierer:

Wir glauben, dass Ausnahmen selten als Teil des normalen Programmflusses verwendet werden sollten. Ausnahmen sollten für unerwartete Ereignisse reserviert werden. Angenommen, eine nicht abgefangene Ausnahme beendet Ihr Programm und Sie fragen sich: "Wird dieser Code noch ausgeführt, wenn ich alle Ausnahmebehandlungsroutinen entferne?" Wenn die Antwort "nein" lautet, werden möglicherweise Ausnahmen unter nicht außergewöhnlichen Umständen verwendet.

Sie untersuchen das Beispiel des Öffnens einer Datei zum Lesen, und die Datei existiert nicht - sollte das eine Ausnahme auslösen?

Wenn die Datei sollte es gewesen sein, dann wird eine Ausnahme gerechtfertigt. [...] Wenn Sie jedoch keine Ahnung haben, ob die Datei vorhanden sein soll oder nicht, erscheint es nicht außergewöhnlich, wenn Sie sie nicht finden können, und eine Fehlerrückgabe ist angemessen.

Später diskutieren sie, warum sie diesen Ansatz gewählt haben:

[A] n Exception stellt eine sofortige, nicht lokale Übertragung der Kontrolle dar - es ist eine Art Kaskadierung goto. Programme, die Ausnahmen als Teil ihrer normalen Verarbeitung verwenden, leiden unter allen Lesbarkeits- und Wartbarkeitsproblemen des klassischen Spaghetti-Codes. Diese Programme unterbrechen die Kapselung: Routinen und ihre Aufrufer sind über die Ausnahmebehandlung enger miteinander verbunden.

In Bezug auf Ihre Situation

Ihre Frage lautet: "Sollten Validierungsfehler Ausnahmen auslösen?" Die Antwort ist, dass es davon abhängt, wo die Validierung stattfindet.

Befindet sich die betreffende Methode in einem Codeabschnitt, in dem angenommen wird, dass die Eingabedaten bereits validiert wurden, sollten ungültige Eingabedaten eine Ausnahme auslösen. Wenn der Code so konzipiert ist, dass diese Methode die exakte Eingabe eines Benutzers erhält, sind ungültige Daten zu erwarten und eine Ausnahme sollte nicht ausgelöst werden.

Mike Partridge
quelle
11

Hier gibt es eine Menge philosophischer Überlegungen, aber im Allgemeinen sind außergewöhnliche Bedingungen einfach solche, die Sie ohne Benutzereingriff nicht handhaben können oder wollen ( abgesehen von Bereinigung, Fehlerberichterstattung und dergleichen). Mit anderen Worten, es handelt sich um nicht behebbare Zustände.

Wenn Sie einem Programm einen Dateipfad übergeben, um diese Datei auf irgendeine Weise zu verarbeiten, und die durch diesen Pfad angegebene Datei nicht vorhanden ist, ist dies eine Ausnahmebedingung. Sie können in Ihrem Code nichts dagegen tun, als es dem Benutzer zu melden und ihm zu gestatten, einen anderen Dateipfad anzugeben.

Robert Harvey
quelle
1
+1, sehr nahe an dem, was ich sagen wollte. Ich würde sagen, es geht mehr um den Umfang und hat wirklich nichts mit dem Benutzer zu tun. Ein gutes Beispiel hierfür ist der Unterschied zwischen den beiden .Net-Funktionen int.Parse und int.TryParse. Erstere haben keine andere Wahl, als bei schlechten Eingaben eine Ausnahme
auszulösen. Letztere
1
@jmoreno: Also, Sie würden TryParse verwenden, wenn der Code etwas gegen den nicht analysierbaren Zustand tun könnte, und Parse, wenn dies nicht möglich ist.
Robert Harvey
7

Es gibt zwei Bedenken, die Sie berücksichtigen sollten:

  1. Sie diskutieren ein einzelnes Anliegen - nennen wir es, Assignerda dieses Anliegen darin besteht, strukturierten Objekten Eingaben zuzuweisen - und Sie drücken die Einschränkung aus, dass seine Eingaben gültig sind

  2. Eine gut implementierte Benutzeroberfläche hat ein zusätzliches Problem: Validierung der Benutzereingaben und konstruktives Feedback zu Fehlern (nennen wir diesen Teil Validator).

Aus Sicht der AssignerKomponente ist das Auslösen einer Ausnahme absolut sinnvoll, da Sie eine Einschränkung formuliert haben, gegen die verstoßen wurde.

Aus der Sicht der Benutzerfreundlichkeit kann der Benutzer sollte nicht direkt auf dieses Gespräch Assignerin erster Linie. Sie sollten sie reden über die Validator.

In diesem Fall Validatorist eine ungültige Benutzereingabe kein Ausnahmefall, sondern ein Fall, an dem Sie mehr interessiert sind. Hier wäre eine Ausnahme also nicht angebracht, und hier möchten Sie auch alle Fehler identifizieren , anstatt Rettung bei der ersten.

Sie werden bemerken, dass ich nicht erwähnt habe, wie diese Bedenken umgesetzt werden. Sie sprechen anscheinend von Assignerund Ihr Kollege spricht von einer Kombination Validator+Assigner. Sobald Sie feststellen, dass es zwei getrennte (oder trennbare) Probleme gibt, können Sie dies zumindest sinnvoll diskutieren.


Um auf Renans Kommentar einzugehen, gehe ich nur davon aus, dass es offensichtlich ist, welche Fälle in jedem Kontext als außergewöhnlich zu betrachten sind, wenn Sie Ihre beiden unterschiedlichen Bedenken identifiziert haben.

In der Tat, wenn es nicht offensichtlich ist, ob etwas als außergewöhnlich zu bezeichnen ist, würden Sie wahrscheinlich die unabhängigen Probleme in Ihrer Lösung noch nicht vollständig identifizieren.

Ich denke, das ist die direkte Antwort auf

... woher weiß ich, ob mein Fall außergewöhnlich ist?

weiter vereinfachen, bis es offensichtlich ist . Wenn Sie eine Menge einfacher Konzepte haben, die Sie gut verstehen, können Sie klar überlegen, ob Sie sie wieder in Code, Klassen, Bibliotheken oder was auch immer komponieren möchten.

Nutzlos
quelle
-1 Ja, es gibt zwei Bedenken, aber dies beantwortet nicht die Frage "Woher weiß ich, ob mein Fall außergewöhnlich ist?"
RMalke
Der Punkt ist, dass der gleiche Fall in einem Kontext außergewöhnlich sein kann und nicht in einem anderen. Hier wird die Frage beantwortet, über welchen Kontext Sie tatsächlich sprechen (anstatt beide zu verknüpfen).
Useless
... eigentlich vielleicht auch nicht - ich habe stattdessen in meiner Antwort Ihren Punkt angesprochen.
Nutzlos
4

Andere haben gut geantwortet, aber hier ist noch meine kurze Antwort. Ausnahme ist eine Situation, in der etwas in der Umgebung nicht stimmt, was Sie nicht kontrollieren können und Ihr Code überhaupt nicht vorwärts gehen kann. In diesem Fall müssen Sie den Benutzer auch darüber informieren, was schief gelaufen ist, warum Sie nicht weiter gehen können und wie die Lösung lautet.

Manoj R
quelle
3

Ich war noch nie ein großer Fan des Ratschlags, dass Sie Ausnahmen nur in Ausnahmefällen auslösen sollten, zum Teil, weil darin nichts steht (es ist wie zu sagen, dass Sie nur essbare Lebensmittel essen sollten), sondern auch, weil dies der Fall ist ist sehr subjektiv und es ist oft nicht klar, was einen Ausnahmefall darstellt und was nicht.

Es gibt jedoch gute Gründe für diesen Rat: Das Auslösen und Abfangen von Ausnahmen ist langsam, und wenn Sie Ihren Code im Debugger in Visual Studio ausführen, um Sie zu benachrichtigen, wenn eine Ausnahme ausgelöst wird, werden Sie möglicherweise von Dutzenden von Spams infiziert Wenn nicht Hunderte von Nachrichten, bevor Sie zum Problem kommen.

Also in der Regel, wenn:

  • Ihr Code ist fehlerfrei und
  • die Dienste, von denen es abhängt, sind alle verfügbar, und
  • Ihr Benutzer verwendet Ihr Programm so, wie es verwendet werden soll (auch wenn einige der von ihm eingegebenen Daten ungültig sind).

dann sollte Ihr Code niemals eine Ausnahme auslösen, auch wenn diese später abgefangen wird. Um ungültige Daten abzufangen, können Sie Validatoren auf Benutzeroberflächenebene oder Code verwenden, z. B. Int32.TryParse()in der Präsentationsebene.

Für alles andere sollten Sie sich an das Prinzip halten, dass eine Ausnahme bedeutet, dass Ihre Methode nicht das kann, was der Name sagt, was sie tut. Im Allgemeinen ist es TryParse()aus zwei Gründen nicht ratsam, Rückkehrcodes zu verwenden, um einen Fehler anzuzeigen (es sei denn, Ihr Methodenname gibt eindeutig an, dass dies z. B. der Fall ist ). Erstens besteht die Standardantwort auf einen Fehlercode darin, die Fehlerbedingung zu ignorieren und unabhängig davon fortzufahren. Zweitens können Sie allzu leicht mit einigen Methoden, die Rückkehrcodes verwenden, und mit anderen Methoden, die Ausnahmen verwenden, enden und vergessen, welche welche sind. Ich habe sogar Codebasen gesehen, bei denen zwei verschiedene austauschbare Implementierungen derselben Schnittstelle hier unterschiedliche Ansätze verfolgen.

Marmeladenkuchen
quelle
2

Ausnahmen sollten Bedingungen darstellen , die es wahrscheinlich die sofortige Berufung Code nicht zu handhaben , auch wenn die anrufende Methode könnte hergestellt werden. Angenommen, Code, der einige Daten aus einer Datei liest, geht zu Recht davon aus, dass eine gültige Datei mit einem gültigen Datensatz endet und keine Informationen aus einem Teildatensatz extrahieren muss.

Wenn die Routine read-data keine Ausnahmen verwendet, sondern lediglich meldet, ob der Lesevorgang erfolgreich war oder nicht, müsste der aufrufende Code folgendermaßen aussehen:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

usw. für jede nützliche Arbeit drei Codezeilen ausgeben. Wenn dagegen readIntegerwill beim Erreichen des Endes einer Datei eine Ausnahme auslöst und der Aufrufer die Ausnahme einfach weiterleiten kann, lautet der Code wie folgt:

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

Viel einfacher und sauberer aussehend, mit weitaus größerem Schwerpunkt auf dem Fall, dass die Dinge normal funktionieren. Beachten Sie, dass in Fällen, in denen der unmittelbare Aufrufer die Behandlung einer Bedingung erwartet, eine Methode, die einen Fehlercode zurückgibt, oft hilfreicher ist als eine, die eine Ausnahme auslöst. So summieren Sie beispielsweise alle Ganzzahlen in einer Datei:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

gegen

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

Der Code, der nach den ganzen Zahlen fragt, erwartet, dass einer dieser Aufrufe fehlschlägt. Wenn der Code eine Endlosschleife verwendet, die bis dahin ausgeführt wird, ist dies weitaus weniger elegant als die Verwendung einer Methode, die Fehler über ihren Rückgabewert anzeigt.

Da Klassen häufig nicht wissen, welche Bedingungen ihre Clients erwarten oder nicht erwarten, ist es oft hilfreich, zwei Versionen von Methoden anzubieten, die auf eine Weise fehlschlagen können, die einige Anrufer erwarten und andere nicht. Auf diese Weise können solche Methoden bei beiden Anrufertypen problemlos verwendet werden. Beachten Sie auch, dass selbst "try" -Methoden Ausnahmen auslösen sollten, wenn Situationen auftreten, die der Aufrufer wahrscheinlich nicht erwartet. Zum Beispiel tryReadIntegersollte keine Ausnahme ausgelöst werden, wenn eine saubere End-of-File-Bedingung auftritt (wenn der Anrufer dies nicht erwartet hätte, hätte der Anrufer verwendetreadInteger). Andererseits sollte es wahrscheinlich eine Ausnahme auslösen, wenn die Daten nicht gelesen werden konnten, weil z. B. der Memory Stick, auf dem sie gespeichert waren, nicht eingesteckt war. Während solche Ereignisse immer als Möglichkeit erkannt werden sollten, ist es unwahrscheinlich, dass der Code für den sofortigen Aufruf bereit ist, als Antwort etwas Nützliches zu tun. Es sollte auf keinen Fall auf die gleiche Weise gemeldet werden, wie dies bei einer Dateiende-Bedingung der Fall wäre.

Superkatze
quelle
2

Das Wichtigste beim Schreiben von Software ist die Lesbarkeit. Alle anderen Überlegungen sind zweitrangig, einschließlich ihrer Effizienz und Richtigkeit. Wenn es lesbar ist, kann der Rest bei der Wartung erledigt werden. Wenn es nicht lesbar ist, ist es besser, es einfach wegzuwerfen. Aus diesem Grund sollten Sie Ausnahmen auslösen, wenn die Lesbarkeit verbessert wird.

Denken Sie beim Schreiben eines Algorithmus nur an die Person in der Zukunft, die ihn lesen wird. Wenn Sie an einen Ort kommen, an dem ein potenzielles Problem auftreten könnte, fragen Sie sich, ob der Leser sehen möchte, wie Sie mit diesem Problem jetzt umgehen , oder ob der Leser es vorziehen würde, einfach mit dem Algorithmus fortzufahren.

Ich denke gerne an ein Rezept für Schokoladenkuchen. Wenn Sie aufgefordert werden, die Eier hinzuzufügen, haben Sie die Wahl: Entweder nehmen Sie Eier an und fahren mit dem Rezept fort, oder es wird erläutert, wie Sie Eier erhalten, wenn Sie keine Eier haben. Es könnte ein ganzes Buch mit Techniken für die Jagd auf wilde Hühner füllen, um Ihnen beim Backen eines Kuchens zu helfen. Das ist gut, aber die meisten Leute werden dieses Rezept nicht lesen wollen. Die meisten Leute würden es vorziehen, einfach anzunehmen, dass Eier verfügbar sind, und mit dem Rezept weitermachen. Das ist ein Urteil, das die Autoren beim Schreiben von Rezepten treffen müssen.

Es gibt keine garantierten Regeln darüber, was eine gute Ausnahme ausmacht und welche Probleme sofort behoben werden sollten, da Sie dazu die Gedanken Ihres Lesers lesen müssen. Das Beste, was Sie jemals tun werden, sind Faustregeln, und "Ausnahmen gelten nur für außergewöhnliche Umstände" ist eine ziemlich gute. Wenn ein Leser Ihre Methode liest, sucht er in der Regel in 99% der Fälle nach den Möglichkeiten, die die Methode bietet, und es ist ihnen lieber, wenn keine bizarren Eckfälle vorliegen, z. Sie möchten den normalen Ablauf Ihrer Software direkt sehen, eine Anweisung nach der anderen, als ob niemals Probleme auftreten würden.

Geo
quelle
2

Möglicherweise muss nicht nur der erste Fehler gemeldet werden, sondern auch jeder Fehler, den wir in der Eingabe finden.

Aus diesem Grund können Sie hier keine Ausnahme auslösen. Eine Ausnahme unterbricht den Validierungsprozess sofort. Es würde also viel Arbeit geben, um dies zu erreichen.

Ein schlechtes Beispiel:

Validierungsmethode für DogKlassen mit Ausnahmen:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Wie man es nennt:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Das Problem hierbei ist, dass der Überprüfungsprozess, um alle Fehler zu erhalten, das Überspringen bereits gefundener Ausnahmen erfordern würde. Das oben Genannte könnte funktionieren, aber dies ist ein klarer Missbrauch von Ausnahmen . Die Art der Überprüfung, nach der Sie gefragt wurden, sollte erfolgen, bevor die Datenbank berührt wird. Sie müssen also nichts zurücksetzen. Und die Ergebnisse der Validierung sind wahrscheinlich Validierungsfehler (hoffentlich Null).

Der bessere Ansatz ist:

Methodenaufruf:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Validierungsmethode:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

Warum? Es gibt unzählige Gründe, und die meisten wurden in den anderen Antworten angesprochen. Einfach ausgedrückt: Es ist viel einfacher, für andere zu lesen und zu verstehen. Zweitens möchten Sie dem Benutzer Stack-Traces anzeigen, um ihm zu erklären, dass er seine dogfalsch eingerichtet hat?

Wenn während des Festschreibens im zweiten Beispiel immer noch ein Fehler auftritt , obwohl Ihr Prüfer die dogProbleme mit null validiert hat, ist es richtig, eine Ausnahme auszulösen . Wie: Keine Datenbankverbindung, der Datenbankeintrag wurde in der Zwischenzeit von einer anderen Person geändert, oder so ähnlich.

Matthias Ronge
quelle