532 Stimmen

Das Argument gegen kontrollierte Ausnahmen

Seit einigen Jahren ist es mir nicht gelungen, eine vernünftige Antwort auf die folgende Frage zu finden: Warum sind einige Entwickler so gegen geprüfte Ausnahmen? Ich habe zahlreiche Gespräche geführt, Dinge in Blogs gelesen, gelesen, was Bruce Eckel zu sagen hatte (die erste Person, die ich sah, die sich gegen sie aussprach).

Ich schreibe gerade einen neuen Code und achte sehr genau darauf, wie ich mit Ausnahmen umgehe. Ich versuche, den Standpunkt der "Wir mögen keine geprüften Ausnahmen"-Leute zu verstehen, aber ich kann ihn immer noch nicht nachvollziehen.

Jedes Gespräch, das ich führe, endet mit der gleichen Frage, die nicht beantwortet wird... lassen Sie mich das klarstellen:

Im Allgemeinen (von der Art, wie Java entwickelt wurde),

  • Error ist für Dinge gedacht, die man niemals einfangen sollte (VM hat eine Erdnussallergie und jemand hat ein Glas Erdnüsse auf ihn fallen lassen)
  • RuntimeException ist für Dinge, die der Programmierer falsch gemacht hat (Programmierer ist über das Ende eines Arrays hinausgegangen)
  • Exception (außer RuntimeException ) ist für Dinge gedacht, die sich der Kontrolle des Programmierers entziehen (die Festplatte füllt sich beim Schreiben in das Dateisystem, das Limit für Dateihandles für den Prozess wurde erreicht und Sie können keine weiteren Dateien öffnen).
  • Throwable ist einfach der übergeordnete Typ für alle Ausnahmetypen.

Ein häufiges Argument, das ich höre, ist, dass, wenn eine Ausnahme auftritt, der Entwickler nur das Programm beenden muss.

Ein weiteres Argument, das ich häufig höre, ist, dass geprüfte Ausnahmen das Refactoring von Code erschweren.

Zu dem Argument "alles, was ich tun werde, ist, das Programm zu beenden" sage ich, dass Sie, selbst wenn Sie es beenden, eine vernünftige Fehlermeldung anzeigen müssen. Wenn Sie sich mit der Behandlung von Fehlern begnügen, werden Ihre Benutzer nicht sehr erfreut sein, wenn das Programm ohne eine klare Angabe des Grundes beendet wird.

Für die Leute, die meinen, dass dies das Refactoring erschwert, bedeutet dies, dass nicht die richtige Abstraktionsebene gewählt wurde. Anstatt eine Methode zu deklarieren, die ein IOException El IOException sollte in eine Ausnahme umgewandelt werden, die dem Geschehen besser entspricht.

Ich habe kein Problem damit, Main mit catch(Exception) (oder in einigen Fällen catch(Throwable) um sicherzustellen, dass das Programm ordnungsgemäß beendet werden kann - aber ich fange immer die spezifischen Ausnahmen ab, die ich benötige. Auf diese Weise kann ich zumindest eine entsprechende Fehlermeldung ausgeben.

Die Frage, die nie beantwortet wird, ist diese:

Wenn Sie werfen RuntimeException Unterklassen anstelle von Exception Unterklassen, wie können Sie dann wissen, was Sie fangen sollen?

Wenn die Antwort "fangen" lautet Exception dann gehen Sie auch mit Programmierfehlern genauso um wie mit Systemausnahmen. Das halte ich für falsch.

Wenn Sie sich Throwable dann behandeln Sie Systemausnahmen und VM-Fehler (und Ähnliches) auf die gleiche Weise. Das halte ich für falsch.

Wenn die Antwort lautet, dass Sie nur die Ausnahmen abfangen, von denen Sie wissen, dass sie ausgelöst werden, wie können Sie dann wissen, welche Ausnahmen ausgelöst werden? Was passiert, wenn Programmierer X eine neue Ausnahme auslöst und vergessen hat, sie abzufangen? Das erscheint mir sehr gefährlich.

Ich würde sagen, dass ein Programm, das einen Stack-Trace anzeigt, falsch ist. Empfinden Leute, die keine geprüften Ausnahmen mögen, das nicht auch so?

Wenn Sie also keine geprüften Ausnahmen mögen, können Sie bitte erklären, warum nicht UND die Frage beantworten, die nicht beantwortet wird?

Ich bin nicht auf der Suche nach Ratschlägen, wann ich eines der beiden Modelle verwenden sollte, sondern nach folgenden Informationen warum Menschen erstrecken sich von RuntimeException weil sie sich nicht gerne von Exception und/oder warum sie eine Ausnahme abfangen und dann eine RuntimeException statt ihre Methode mit Überwürfen zu versehen. Ich möchte die Gründe für die Abneigung gegen geprüfte Ausnahmen verstehen.

4voto

Thomas W Punkte 13662

Geprüfte Ausnahmen waren in ihrer ursprünglichen Form ein Versuch, Unvorhergesehenes und nicht Fehler zu behandeln. Das lobenswerte Ziel war es, bestimmte vorhersehbare Punkte hervorzuheben (keine Verbindung möglich, Datei nicht gefunden usw.) und sicherzustellen, dass die Entwickler diese behandeln.

Was das ursprüngliche Konzept nie vorsah, war, dass eine Vielzahl von systemischen und nicht behebbaren Fehlern gemeldet werden musste. Es war nie richtig, diese Fehler als geprüfte Ausnahmen zu deklarieren.

Fehler sind im Code generell möglich, und EJB-, Web- und Swing/AWT-Container sorgen bereits dafür, indem sie einen Exception-Handler für "fehlgeschlagene Anfragen" bereitstellen. Die einfachste korrekte Strategie besteht darin, die Transaktion zurückzusetzen und einen Fehler zurückzugeben.

Ein entscheidender Punkt ist, dass Laufzeit- und geprüfte Ausnahmen sind funktional gleichwertig. Es gibt keine Behandlung oder Wiederherstellung, die geprüfte Ausnahmen tun können, die Laufzeitausnahmen nicht können.

Das größte Argument gegen "geprüfte" Ausnahmen ist, dass die meisten Ausnahmen nicht behoben werden können. Die einfache Tatsache ist, dass uns der Code/das Subsystem, das den Fehler verursacht hat, nicht gehört. Wir können die Implementierung nicht sehen, wir sind nicht dafür verantwortlich und können sie auch nicht beheben.

Wenn unsere Anwendung keine DB ist, sollten wir nicht versuchen, die DB zu reparieren. Das würde gegen die Prinzip der Verkapselung .

Besonders problematisch waren die Bereiche JDBC (SQLException) und RMI für EJB (RemoteException). Anstatt, wie im ursprünglichen Konzept der "geprüften Ausnahme", behebbare Ausnahmen zu identifizieren, zwangen diese dazu, weit verbreitete systemische Zuverlässigkeitsprobleme, die eigentlich nicht behebbar sind, zu deklarieren.

Der andere schwerwiegende Fehler im Java-Design war, dass die Behandlung von Ausnahmen korrekt auf der höchstmöglichen "Geschäfts-" oder "Anforderungs"-Ebene angesiedelt sein sollte. Der Grundsatz lautet hier "früh werfen, spät fangen". Geprüfte Ausnahmen stehen dem nur im Weg.

In Java gibt es das offensichtliche Problem, dass Tausende von nichtssagenden Try-Catch-Blöcken erforderlich sind, von denen ein erheblicher Teil (mehr als 40 %) falsch codiert ist. Fast keiner dieser Blöcke implementiert eine echte Behandlung oder Zuverlässigkeit, sondern verursacht einen erheblichen Codierungsaufwand.

Und schließlich sind "geprüfte Ausnahmen" mit der funktionalen Programmierung von FP so gut wie unvereinbar.

Ihr Beharren auf "sofort behandeln" steht im Widerspruch zu den Best Practices für die Behandlung von Ausnahmen ("catch late") und zu jeder FP-Struktur, die Schleifen oder den Kontrollfluss abstrahiert.

Viele Leute reden von der "Behandlung" geprüfter Ausnahmen, aber sie reden sich dabei um Kopf und Kragen. Nach einem Fehler mit ungültigen, unvollständigen oder falschen Daten weiterzumachen zu so tun, als ob Erfolg hat nichts zu bedeuten. Das ist Ingenieurkunst und Zuverlässigkeit in ihrer niedrigsten Form.

Ein sauberes Scheitern ist die grundlegendste richtige Strategie für die Behandlung einer Ausnahme. Die Transaktion zurückzunehmen, den Fehler zu protokollieren und dem Benutzer eine "Fehler"-Antwort mitzuteilen, ist eine gute Praxis - und verhindert vor allem, dass falsche Geschäftsdaten in der Datenbank gespeichert werden.

Andere Strategien für die Behandlung von Ausnahmen sind "retry", "reconnect" oder "skip", auf Geschäfts-, Subsystem- oder Anforderungsebene. All dies sind allgemeine Zuverlässigkeitsstrategien, die bei Laufzeitausnahmen gut/ besser funktionieren.

Und schließlich ist es weitaus besser, mit einem Fehler abzuschließen, als mit falschen Daten zu arbeiten. Eine Fortsetzung führt entweder zu Sekundärfehlern, die weit von der ursprünglichen Ursache entfernt sind und schwerer zu beheben sind, oder sie führt schließlich dazu, dass fehlerhafte Daten übertragen werden. Dafür werden Leute entlassen.

Siehe:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

3voto

Powerlord Punkte 84404

Ein Problem bei geprüften Ausnahmen ist, dass Ausnahmen oft an Methoden einer Schnittstelle angehängt werden, wenn auch nur eine Implementierung dieser Schnittstelle sie verwendet.

Ein weiteres Problem mit geprüften Ausnahmen ist, dass sie oft missbraucht werden. Das perfekte Beispiel dafür ist in java.sql.Connection 's [close()](http://java.sun.com/javase/6/docs/api/java/sql/Connection.html#close()) Methode. Sie kann eine SQLException auch wenn Sie bereits ausdrücklich angegeben dass Sie mit der Verbindung fertig sind. Welche Informationen könnte close() möglicherweise übermitteln, die Sie interessieren würden?

Normalerweise, wenn ich eine Verbindung schließe() * sieht es in etwa so aus:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

Auch, erhalten Sie mich nicht auf die verschiedenen Parse-Methoden und NumberFormatException gestartet ... .NET's TryParse, die Ausnahmen nicht werfen, ist so viel einfacher zu verwenden, es ist schmerzhaft zu haben, zurück zu Java (wir verwenden sowohl Java und C#, wo ich arbeite) gehen.

* Als zusätzlicher Kommentar, eine PooledConnection's Connection.close() macht nicht einmal schließen eine Verbindung, aber Sie müssen trotzdem die SQLException abfangen, da es sich um eine geprüfte Ausnahme handelt.

3voto

Aleksandr Dubinsky Punkte 20646

Der Programmierer muss wissen alle der Ausnahmen, die eine Methode auslösen kann, um sie korrekt zu verwenden. Einem unvorsichtigen Programmierer hilft es also nicht unbedingt, Fehler zu vermeiden, wenn man ihm nur einige der Ausnahmen an den Kopf wirft.

Der geringe Nutzen wird durch die hohen Kosten aufgewogen (insbesondere bei größeren, weniger flexiblen Codebasen, bei denen eine ständige Änderung der Schnittstellensignaturen nicht sinnvoll ist).

Statische Analyse kann schön sein, aber eine wirklich zuverlässige statische Analyse verlangt dem Programmierer oft strenge Arbeit ab. Es gibt eine Kosten-Nutzen-Rechnung, und die Messlatte für eine Prüfung, die zu einem Kompilierzeitfehler führt, muss hoch angesetzt werden. Es wäre hilfreicher, wenn die IDE die Aufgabe übernähme, mitzuteilen, welche Ausnahmen eine Methode auslösen kann (einschließlich derjenigen, die unvermeidbar sind). Auch wenn es ohne erzwungene Ausnahmendeklarationen vielleicht nicht so zuverlässig wäre, würden die meisten Ausnahmen immer noch in der Dokumentation deklariert werden, und die Zuverlässigkeit einer IDE-Warnung ist nicht so entscheidend.

3voto

oxbow_lakes Punkte 131223

Ich denke, dass dies eine ausgezeichnete Frage ist und überhaupt nicht argumentativ ist. Ich denke, dass 3rd-Party-Bibliotheken (im Allgemeinen) werfen sollte unkontrolliert Ausnahmen. Dies bedeutet, dass Sie Ihre Abhängigkeiten von der Bibliothek isolieren können (d.h. Sie müssen nicht entweder deren Ausnahmen erneut auslösen oder die Exception - in der Regel schlechte Praxis). Der Frühling DAO-Schicht ist ein hervorragendes Beispiel dafür.

Andererseits sollten Ausnahmen von der Java-Kern-API im Allgemeinen geprüft werden, wenn sie immer behandelt werden. Nehmen Sie FileNotFoundException oder (mein Favorit) InterruptedException . Diese Bedingungen sollten fast immer spezifisch behandelt werden (d.h. Ihre Reaktion auf eine InterruptedException ist nicht dasselbe wie Ihre Reaktion auf eine IllegalArgumentException ). Die Tatsache, dass Ihre Ausnahmen geprüft werden, zwingt die Entwickler dazu, darüber nachzudenken, ob eine Bedingung behandelbar ist oder nicht. (Davon abgesehen, habe ich selten gesehen, dass InterruptedException richtig behandelt!)

Und noch etwas - ein RuntimeException ist nicht immer "wo ein Entwickler etwas falsch gemacht hat". Eine illegale Argument-Ausnahme wird ausgelöst, wenn Sie versuchen, ein enum mit valueOf und es gibt keine enum mit diesem Namen. Dies ist nicht unbedingt ein Fehler des Entwicklers!

2voto

ses Punkte 12813

Die guten Beweise dafür, dass Checked Exception nicht benötigt werden, sind:

  1. Eine Menge Frameworks, die einige Arbeit für Java erledigen. Wie Spring, das JDBC-Ausnahmen in ungeprüfte Ausnahmen umwandelt und Meldungen in das Protokoll wirft.
  2. Viele Sprachen, die nach Java kamen, sogar oben auf der Java-Plattform - sie verwenden sie nicht
  3. Geprüfte Ausnahmen, ist es Art Vorhersage darüber, wie der Client den Code, der eine Ausnahme auslöst verwenden würde. Aber ein Entwickler, der diesen Code schreibt, würde nie etwas über das System und das Geschäft wissen, in dem der Client des Codes arbeitet. Ein Beispiel dafür sind Interfcace-Methoden, die eine geprüfte Ausnahme auslösen. Es gibt 100 Implementierungen im System, 50 oder sogar 90 davon lösen diese Ausnahme nicht aus, aber der Client muss diese Ausnahme trotzdem abfangen, wenn er auf diese Schnittstelle verweist. Diese 50 oder 90 Implementierungen neigen dazu, diese Ausnahmen in sich selbst zu behandeln und die Ausnahme in das Protokoll zu schreiben (und das ist ein gutes Verhalten für sie). Was sollten wir damit machen? Es wäre besser, wenn ich eine Hintergrundlogik hätte, die all diese Aufgaben übernimmt und die Nachricht an das Protokoll sendet. Und wenn ich als Kunde des Codes das Gefühl habe, dass ich die Ausnahme behandeln muss, werde ich es tun. Ich kann es vergessen, richtig - aber wenn ich TDD verwende, sind alle meine Schritte abgedeckt und ich weiß, was ich will.
  4. Ein weiteres Beispiel, wenn ich mit I/O in Java arbeite, zwingt es mich, alle Ausnahmen zu überprüfen, wenn die Datei nicht existiert? was soll ich damit machen? Wenn sie nicht existiert, würde das System nicht zum nächsten Schritt übergehen. Der Client dieser Methode würde nicht den erwarteten Inhalt aus dieser Datei erhalten - er kann die Runtime Exception behandeln, andernfalls sollte ich zuerst die Checked Exception prüfen, eine Meldung ins Log schreiben und dann die Exception aus der Methode herauswerfen. Nein...nein - ich würde es besser automatisch mit RuntimeEception machen, das macht es / springt automatisch an. Es macht keinen Sinn, es manuell zu behandeln - ich wäre froh, wenn ich eine Fehlermeldung im Log sehen würde (AOP kann dabei helfen... etwas, das Java behebt). Wenn, schließlich, ich deice, dass das System sollte zeigt Pop-up-Meldung an den Endbenutzer - ich werde es zeigen, nicht ein Problem.

Ich war froh, wenn Java mir eine Auswahl was zu verwenden ist, wenn man mit Core-Libs arbeitet, wie I/O. Wie bietet zwei Kopien der gleichen Klassen - eine mit RuntimeEception gewickelt. Dann können wir vergleichen, was die Leute verwenden würden . Für jetzt, obwohl, viele Leute würden besser für einige Rahmen auf Java, oder andere Sprache gehen. Wie Scala, JRuby was auch immer. Viele glauben einfach, dass SUN richtig war.

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