10 Stimmen

Java - wo und wie sollten Ausnahmen verwendet werden?

Ich habe einige Dinge über die Behandlung von Ausnahmen in Java gelesen, um besseren Code schreiben zu können. OK, ich gebe zu, ich habe mich schuldig gemacht; ich habe zu viele try-catch{}-Blöcke verwendet, ich habe ex.printStackTrace() in den Fängen, nicht einmal mit einem richtigen Logger (eigentlich der System.out y System.err wurden umgeleitet zu einer PrintWriter so wurde ein Protokoll erstellt). Nach ein paar Stunden des Lesens befinde ich mich jedoch an einem seltsamen Ort: dem Unbekannten. Wenn die Ausnahmen so konzipiert sind, dass sie Informationen über abnormale Flusszustände weitergeben, woher weiß man dann, WO die richtige Ebene ist, um etwas mit dieser Information zu tun?

Wenn beispielsweise ein Datenbankfehler auftritt, sollte man dann einen Nullwert oder einen Fehlercode zurückgeben oder die Ausnahme auslösen? Wenn die Ausnahme ausgelöst wird, WO sollte diese Ausnahme behandelt werden? Ich verstehe, dass es keinen Sinn macht, eine Ausnahme zu protokollieren, wenn man nichts dagegen tun kann. In GUI-Anwendungen könnte das jedoch leicht die GUI zerstören (ich verwende SWT und habe das schon zu oft gesehen), selbst für den Fall, dass die menuShown() Methode (ein ArrayIndexOutOfBounds Ausnahme wird die Anwendung geschlossen, wenn sie nicht behandelt wird). Das Beispiel könnte ewig so weitergehen, aber hier ist die Zusammenfassung der Fragen:

  1. Hat die übermäßige Verwendung von try-catch() negative Auswirkungen auf die Leistung?
  2. Ist es besser, bestimmte Ausnahmetypen zu verwenden? Was ist, wenn ich eine der folgenden Ausnahmen nicht der möglichen X Arten von Ausnahmen, die auftreten können, nicht abzufangen?
    Ehrlich gesagt, habe ich in 2-3 Jahren gerade einmal 10% der Java-Standardausnahmen kennengelernt und verwendet. Ja, jemand hat gesagt, dass der Aufrufer, wenn er nicht weiß, wie er mit den geworfenen Ausnahmen umgehen soll, NICHT das RECHT haben sollte, die werfende Methode aufzurufen. Ist das richtig?
  3. Ich habe diesen Artikel von Anders Hejlsberg und sagt, dass geprüfte Ausnahmen schlecht sind. Sollte das darauf hindeuten, dass es in manchen Fällen ratsam ist, Ausnahmen zu schlucken?
  4. Ein Bild sagt mehr als 1000 Worte; ich denke, einige Beispiele werden hier sehr hilfreich sein hier.

Ich weiß, dass das Thema ewig währt, aber eigentlich freue ich mich darauf, ein mittelgroßes Projekt mit 150 Klassen zu überprüfen und dabei Ihre Ratschläge zu nutzen. Vielen Dank dafür.

7voto

Jeff Storey Punkte 54752

Die allgemeine Faustregel für Ausnahmen lautet: Wenn Sie etwas dagegen tun können, fangen Sie sie ab und behandeln Sie sie, wenn nicht, geben Sie sie an die nächste Methode weiter. Um auf einige Ihrer Besonderheiten einzugehen:

  1. Nein, die Verwendung von übermäßigen try/catch hat keine Auswirkungen auf die Leistung
  2. Verwenden Sie die spezifischste Art von Ausnahme, die Sie können. Zum Beispiel sollten Sie generell keine Exception auslösen, wenn Sie es vermeiden können. Indem Sie einen bestimmten Typ auslösen, teilen Sie dem Benutzer mit, was schiefgehen kann. Sie können die Exception aber auch in einen allgemeineren Typ umwandeln, so dass Aufrufer, die sich nicht mit der spezifischen Exception befassen, nichts davon erfahren müssen (z. B. ist es einer grafischen Benutzeroberfläche egal, ob es sich um eine IOException oder eine ArrayIndexOutOFBoundsException handelt).
  3. Sie werden Leute finden, die angekreuzte Ausnahmen mehr mögen und Sie werden Leute finden, die nicht angekreuzte Ausnahmen mehr mögen. Im Allgemeinen versuche ich, ungeprüfte Ausnahmen zu verwenden, weil man bei den meisten geprüften Ausnahmen nicht viel tun kann, und man kann ungeprüfte Ausnahmen trotzdem behandeln, man muss es nur nicht. Ich ertappe mich häufig dabei, dass ich geprüfte Ausnahmen zurückwerfe, weil ich nicht viel dagegen tun kann (eine andere Strategie ist, eine geprüfte Ausnahme abzufangen und als ungeprüfte zurückzuwerfen, damit Klassen, die in der Kette weiter oben stehen, sie nicht abfangen müssen, wenn sie nicht wollen).

Ich protokolliere Ausnahmen in der Regel gerne an dem Punkt, an dem sie auftreten - auch wenn ich nichts dagegen tun kann, hilft es mir, das Problem zu diagnostizieren. Wenn Sie damit nicht vertraut sind, sollten Sie sich auch die Methode Thread.setDefaultUncaughtExceptionHandler ansehen. Damit können Sie Ausnahmen behandeln, die von niemandem abgefangen werden, und etwas damit machen. Dies ist besonders nützlich bei einer GUI-Anwendung, da die Ausnahme sonst möglicherweise nicht gesehen wird.

Um auf einige Beispiele einzugehen:

try {
   // some database operation
}
catch (IOException ex) {
   // retry the database operation. then if an IO exception occurs rethrow it. this shows an example doing something other than just catch, logging and/or rethrowing.       
}

Wenn Sie möchten, erläutere ich Ihnen gerne die einzelnen Punkte.

3voto

Jay Punkte 26044

Viele gute Antworten, ich möchte nur ein paar Punkte hinzufügen, die noch nicht erwähnt wurden.

  1. Ihre Ausnahmetypen sollten so spezifisch sein, wie ein Aufrufer sie wahrscheinlich unterscheiden kann. Das heißt, wenn es zwei mögliche Fehler gibt, A und B, und ein Aufrufer wahrscheinlich in beiden Fällen genau das Gleiche tut, dann machen Sie eine einzige Ausnahmeklasse. Wenn es wahrscheinlich ist, dass ein Aufrufer zwei verschiedene Dinge tut, dann machen Sie zwei Ausnahmeklassen.

Bei vielen, wahrscheinlich den meisten, der von mir erstellten Ausnahmen kann das Programm realistischerweise nur eine Fehlermeldung anzeigen und dem Benutzer die Möglichkeit geben, seine Eingaben zu ändern und es erneut zu versuchen. Die meisten Überprüfungsfehler - ungültiges Datumsformat, nicht vorhandene Ziffern in einem numerischen Feld usw. - fallen in diese Kategorie. Für diese erstelle ich einen einzigen Ausnahmetyp, den ich gewöhnlich "BadInputException" oder "ValidationException" nenne, und verwende diese Ausnahmeklasse im gesamten System. Wenn ein Fehler auftritt, werfe ich eine neue BadInputException("Der Betrag darf nur Ziffern enthalten")" oder ähnliches, und der Aufrufer zeigt es an und lässt den Benutzer es erneut versuchen.

Wenn der Anrufer jedoch mit einiger Wahrscheinlichkeit in verschiedenen Fällen unterschiedliche Dinge tun wird, sollten Sie verschiedene Ausnahmen vorsehen.

Einfache Faustregel: Wenn Sie zwei oder mehr Ausnahmen haben, die IMMER mit identischem, doppeltem Code behandelt werden, fassen Sie sie zu einer einzigen Ausnahme zusammen. Wenn Ihr Catch-Block zusätzliche Prüfungen durchführt, um herauszufinden, um welche Art von Fehler es sich wirklich handelt, sollten es zwei (oder mehr) Ausnahmeklassen sein. Ich habe Code gesehen, der exception.getMessage ausführt und dann nach Schlüsselwörtern in der Nachricht sucht, um herauszufinden, was das Problem war. Das ist hässlich. Machen Sie mehrere Ausnahmen und machen Sie es sauber.

  1. Es gibt drei gute Gründe für die Verwendung von Ausnahmen anstelle anderer Methoden zur Fehlerbehandlung.

(a) Es vermeidet das Problem der "magischen" Rückgabewerte, wie z.B. "non-null string" ist eine echte Antwort, aber "null" bedeutet, dass ein Fehler aufgetreten ist. Oder noch schlimmer: "NF" bedeutet "Datei nicht gefunden", "NV" bedeutet "ungültiges Format", und alles andere ist die richtige Antwort. Bei Ausnahmen ist eine Ausnahme eine Ausnahme und ein Rückgabewert ist ein Rückgabewert.

(b) Ausnahmen lassen die Hauptcodezeile sauber aus. Wenn ein Fehler auftritt, möchte man in der Regel eine ganze Reihe von Verarbeitungsschritten überspringen, die ohne gültige Daten keinen Sinn machen, und direkt eine Fehlermeldung ausgeben und den Vorgang beenden oder neu versuchen, oder was auch immer angemessen ist. In den schlechten alten Zeiten würden wir "GOTO panic-abort" schreiben. GOTOs sind aus all den Gründen gefährlich, die schon oft diskutiert wurden. Ausnahmen beseitigen den vielleicht letzten guten Grund, ein GOTO zu verwenden.

(c) Vielleicht eine Begleiterscheinung von (b), Sie können das Problem auf der entsprechenden Ebene behandeln. Wenn ein Fehler auftritt, möchte man manchmal genau dieselbe Funktion erneut versuchen - ein E/A-Fehler könnte beispielsweise eine vorübergehende Kommunikationsstörung darstellen. Im anderen Extremfall könnte man zehn Ebenen tief in Unterprogrammen stecken, wenn ein Fehler auftritt, der nicht anders behandelt werden kann, als das gesamte Programm zu sprengen und eine "Sorry, Armageddon ist eingetreten, alle hier drin sind tot"-Meldung auszugeben. Mit Ausnahmen ist es nicht nur einfach, die richtige Ebene zu wählen, sondern man kann auch in verschiedenen Modulen unterschiedliche Entscheidungen treffen.

1voto

Justin Standard Punkte 21139

Eine Sache, die wir in unserem Team gemacht haben, ist, dass wir benutzerdefinierte Ausnahmen für unsere Fehler haben. Wir verwenden das Hibernate Validator-Framework, aber Sie können dies mit jedem Framework oder mit Standardausnahmen tun.

Wir haben zum Beispiel eine ValidationException, um Validierungsfehler zu behandeln. Wir haben eine ApplicationException, um Systemfehler zu behandeln.

Sie sollten Ihr Try-Catching auf ein Minimum reduzieren. In unserem Fall wird der Validator ALLE Validierungen in "InvalidValue"-Objekten sammeln und dann eine einzige ValidationException mit den darin gebündelten Informationen über ungültige Werte auslösen. Dann können Sie dem Benutzer mitteilen, welche Felder fehlerhaft waren, usw.

In dem von Ihnen erwähnten Fall eines Datenbankfehlers möchten Sie den Stacktrace möglicherweise nicht an die Benutzeroberfläche senden (die Protokollierung ist eine gute Idee). In diesem Fall können Sie die Datenbankausnahme abfangen und dann Ihre eigene ApplicationException an die GUI auslösen. Ihre Benutzeroberfläche muss nicht wissen, wie sie mit einer unendlichen Anzahl von Serverfehlern umzugehen hat, sondern kann so eingestellt werden, dass sie mit der allgemeineren ApplicationException umgeht - möglicherweise meldet sie, dass es ein Problem mit dem Server gibt, und weist den Benutzer darauf hin, dass er sich an Ihre Kundendienstabteilung wenden soll, um das Problem zu melden.

Und schließlich kann man manchmal nicht umhin, wegen der externen APIs, auf die man sich verlässt, eine Menge Try/Catch-Blöcke zu verwenden. Das ist in Ordnung. Wie bereits erwähnt, fangen Sie die externe Ausnahme ab und formatieren Sie sie in eine, die für IHRE Anwendung sinnvoller ist. Dann werfen Sie die benutzerdefinierte Ausnahme.

1voto

Kaleb Brasee Punkte 49955
  1. Ich habe zwar keine Zahlen, aber ich glaube nicht, dass try-catch einen signifikanten Einfluss auf die Leistung hat (nicht, dass ich es gesehen hätte). Ich denke, wenn man nicht auf viele Ausnahmen stößt, sind die Auswirkungen auf die Leistung im Grunde gleich Null. Aber in jedem Fall ist es am besten, sich zuerst um die korrekte Implementierung des Codes zu kümmern und erst danach eine gute Leistung zu erzielen - das Zweite ist viel einfacher zu erreichen, wenn das Erste erledigt ist.

  2. Ich denke, die Ausnahmeklasse sollte genau angeben, was die Ausnahme wirklich ist. Das Problem, das ich mit den SQLExceptions von Java habe, ist, dass sie keine Informationen darüber liefern, was wirklich schief gelaufen ist. Spring verwendet eine Reihe von beschreibenderen Datenbankausnahmen (Deadlock-Ausnahmen, Datenintegritätsausnahmen usw.). Auf diese Weise können Sie feststellen, was das Problem wirklich war.

  3. Geprüfte Ausnahmen können lästig sein, aber ich glaube nicht, dass sie immer schlecht sind. Spring verwendet zum Beispiel ungeprüfte Ausnahmen für Datenbankfehler, aber ich prüfe immer noch auf sie und behandle sie entweder 1) direkt vor Ort, wenn möglich, oder 2) verpacke sie in eine allgemeinere Ausnahme, die zeigt, dass die Komponente fehlgeschlagen ist.

  4. Leider fallen mir keine guten spezifischen Ausnahmen ein. Wie ich jedoch schon sagte, habe ich die Ausnahmeregeln von Spring als hilfreich und dennoch nicht als lästig empfunden, so dass Sie vielleicht einen Blick in die Spring-Dokumente werfen könnten. Die Spring-Datenbankklassen sind ein gutes Beispiel.

1voto

Pascal Thivent Punkte 548176
  • Die übermäßige Verwendung von try-catch() hat negative Auswirkungen auf die Leistung?

Das klingt nach Mikro-Optimierung, und wenn dies wirklich eine Auswirkung auf die Leistung hat, müssen Sie sich mit einer Menge größerer Leistungsprobleme befassen, bevor Sie sich diesem Problem stellen.

  • Ist die Verwendung bestimmter Ausnahmetypen besser? Was ist, wenn ich es versäumt habe, eine der möglichen X Arten von Ausnahmen abzufangen, die auftreten könnten? Ehrlich gesagt, habe ich in 2-3 Jahren gerade mal 10% der Java-Standardausnahmen gehört und verwendet. Ja, jemand hat gesagt, dass der Aufrufer, wenn er nicht weiß, wie er mit den geworfenen Ausnahmen umgehen soll, NICHT das RECHT haben sollte, die werfende Methode aufzurufen. Ist das richtig?

Ich bin nicht sicher, ob ich die Frage verstanden habe, aber ich würde sagen: "Wenn du nicht weißt, was du mit einer Ausnahme machen sollst, wirf sie zurück".

  • Ich habe diesen Artikel von Anders Hejlsberg gelesen, der besagt, dass geprüfte Ausnahmen schlecht sind. Sollte das bedeuten, dass bequeme Ausnahme schlucken in einigen Fällen empfohlen wird?

Aber nein. Das bedeutet nur, dass ungeprüfte Ausnahmen in einigen Fällen bevorzugt werden sollten, insbesondere wenn der Benutzer nicht weiß, was er mit einer geprüften Ausnahme tun soll (z. B. SQL-Ausnahme), oder wenn es keine Möglichkeit zur Wiederherstellung gibt...

  • Ein Bild sagt mehr als 1000 Worte ich denke, einige Beispiele werden hier sehr hilfreich sein.

Der Frühling DataAccessException ist ein sehr gutes Beispiel. Kapitel prüfen 10. DAO-Unterstützung .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X