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.

8voto

Boann Punkte 46908

Das ist kein Argument gegen das reine Konzept der geprüften Ausnahmen, aber die Klassenhierarchie, die Java für sie verwendet, ist eine Freakshow. Wir nennen die Dinge immer einfach "Ausnahmen" - was korrekt ist, denn die Sprachspezifikation nennt sie auch so - aber wie wird eine Ausnahme benannt und im Typsystem dargestellt?

Durch die Klasse Exception man sich vorstellt? Nun nein, denn Exception s sind Ausnahmen, und ebenso sind Ausnahmen Exception s, mit Ausnahme der Ausnahmen, die pas Exception s, denn andere Ausnahmen sind eigentlich Error s, die die andere Art von Ausnahme sind, eine Art extra-außergewöhnliche Ausnahme, die nie passieren sollte, außer wenn sie es tut, und die Sie nie abfangen sollten, außer wenn Sie es müssen. Das ist aber noch nicht alles, denn Sie können auch andere Ausnahmen definieren, die weder Exception s noch Error s sondern lediglich Throwable Ausnahmen.

Welches sind die "kontrollierten" Ausnahmen? Throwable s sind geprüfte Ausnahmen, es sei denn, sie sind auch Error s, die ungeprüfte Ausnahmen sind, und dann gibt es noch die Exception s, die auch Throwable s und sind die wichtigste Art von geprüften Ausnahmen, außer dass es auch hier eine Ausnahme gibt, und zwar, wenn sie auch RuntimeException s, denn das ist die andere Art der ungeprüften Ausnahme.

Was sind RuntimeException s für? Nun, wie der Name schon sagt, sind sie Ausnahmen, wie alle Exception s, und sie treten zur Laufzeit auf, wie eigentlich alle Ausnahmen, mit der Ausnahme, dass RuntimeException s sind im Vergleich zu anderen Laufzeiten außergewöhnlich Exception s, weil sie nicht vorkommen sollen, außer wenn man einen dummen Fehler macht, obwohl RuntimeException s sind niemals Error s, also für Dinge, die außergewöhnlich fehlerhaft sind, aber eigentlich nicht Error s. Außer bei RuntimeErrorException was eigentlich ein RuntimeException para Error s. Aber sind nicht ohnehin alle Ausnahmen als fehlerhafte Umstände anzusehen? Ja, alle. Außer bei ThreadDeath Die Dokumentation erklärt, dass es sich dabei um ein "normales Vorkommnis" handelt und dass es sich deshalb um einen Typ von Error .

Wie auch immer, da wir alle Ausnahmen in der Mitte unterteilen in Error s (die für außergewöhnliche Ausführungsausnahmen gedacht sind, also nicht angekreuzt) und Exception s (die für weniger außergewöhnliche Ausführungsfehler stehen, also überprüft werden, außer wenn sie es nicht sind), brauchen wir jetzt zwei verschiedene Arten von jeder der mehreren Ausnahmen. Wir brauchen also IllegalAccessError y IllegalAccessException y InstantiationError y InstantiationException y NoSuchFieldError y NoSuchFieldException y NoSuchMethodError y NoSuchMethodException y ZipError y ZipException .

Aber selbst wenn eine Ausnahme geprüft wird, gibt es immer (ziemlich einfache) Möglichkeiten, den Compiler zu betrügen und die Ausnahme zu werfen, ohne dass sie geprüft wird. Wenn Sie das tun, erhalten Sie möglicherweise eine UndeclaredThrowableException außer in anderen Fällen, in denen sie sich als UnexpectedException oder ein UnknownException (was nichts mit der UnknownError die nur für "schwerwiegende Ausnahmen" gilt), oder eine ExecutionException oder ein InvocationTargetException oder ein ExceptionInInitializerError .

Oh, und wir dürfen nicht vergessen, dass Java 8 eine schicke neue UncheckedIOException die ein RuntimeException Ausnahme, mit der Sie das Konzept der Ausnahmeprüfung über den Haufen werfen können, indem Sie die geprüften IOException Ausnahmen, die durch E/A-Fehler verursacht werden (die nicht zu IOError Ausnahmen, obwohl es auch diese gibt), die besonders schwierig zu handhaben sind und deshalb nicht geprüft werden müssen.

Danke Java!

8voto

Newtopian Punkte 7234

Ok... Geprüfte Ausnahmen sind nicht ideal und haben einige Vorbehalte, aber sie erfüllen einen Zweck. Bei der Erstellung einer API gibt es bestimmte Fälle von Fehlern, die für diese API vertraglich festgelegt sind. Wenn man im Kontext einer stark statisch typisierten Sprache wie Java keine geprüften Ausnahmen verwendet, muss man sich auf Ad-hoc-Dokumentation und Konventionen verlassen, um die Möglichkeit eines Fehlers zu vermitteln. Damit entfällt jeglicher Nutzen, den der Compiler bei der Fehlerbehandlung bringen kann, und man ist völlig dem guten Willen der Programmierer ausgeliefert.

Wenn man also die Checked Exception entfernt, so wie es in C# gemacht wurde, wie kann man dann programmatisch und strukturell die Möglichkeit eines Fehlers vermitteln? Wie informiert man den Client-Code, dass solche und solche Fehler auftreten können und behandelt werden müssen?

Ich höre alle Arten von Schrecken, wenn es um geprüfte Ausnahmen geht, sie werden missbraucht, das ist sicher, aber das gilt auch für ungeprüfte Ausnahmen. Ich sage, warten Sie ein paar Jahre, wenn APIs viele Schichten tief gestapelt sind, und Sie werden um die Rückkehr einer Art von strukturiertem Mittel zur Übermittlung von Fehlern betteln.

Nehmen wir den Fall, dass die Ausnahme irgendwo am unteren Ende der API-Schichten ausgelöst wurde und einfach hochgesprudelt ist, weil niemand wusste, dass dieser Fehler überhaupt auftreten kann, und das, obwohl es sich um einen Fehlertyp handelte, der sehr plausibel war, als der aufrufende Code ihn auslöste (FileNotFoundException zum Beispiel im Gegensatz zu VogonsTrashingEarthExcept... in diesem Fall wäre es egal, ob wir ihn behandeln oder nicht, da es nichts mehr gibt, womit man ihn behandeln könnte).

Viele haben argumentiert, dass das Nichtladen der Datei fast immer das Ende des Prozesses bedeutete und er einen schrecklichen und schmerzhaften Tod sterben musste. Also ja... sicher... ok... man baut eine API für etwas und sie lädt irgendwann eine Datei... Ich als Nutzer dieser API kann nur antworten... "Wer zum Teufel bist du, dass du entscheiden kannst, wann mein Programm abstürzen soll!" Sicher, wenn ich die Wahl hätte, dass Ausnahmen verschluckt werden und keine Spuren hinterlassen, oder die EletroFlabbingChunkFluxManifoldChuggingException mit einem Stacktrace tiefer als der Mariannengraben, würde ich ohne zu zögern die letztere wählen, aber bedeutet das, dass dies die wünschenswerte Art ist, mit Ausnahmen umzugehen? Können wir nicht irgendwo in der Mitte sein, wo die Ausnahme jedes Mal, wenn sie eine neue Abstraktionsebene erreicht, umgeschrieben und umhüllt wird, so dass sie tatsächlich etwas bedeutet?

Die meisten Argumente, die ich sehe, lauten: "Ich will mich nicht mit Ausnahmen befassen, viele Leute wollen sich nicht mit Ausnahmen befassen. Geprüfte Ausnahmen zwingen mich dazu, mich mit ihnen zu befassen, daher hasse ich geprüfte Ausnahmen." Einen solchen Mechanismus ganz zu eliminieren und ihn in den Abgrund der goto-Hölle zu verbannen, ist einfach nur dumm und es mangelt ihm an Weitblick und Weitsicht.

Wenn wir die geprüfte Ausnahme eliminieren, könnten wir auch den Rückgabetyp für Funktionen eliminieren und immer eine Variable "beliebigen Typs" zurückgeben... Das würde das Leben so viel einfacher machen, nicht wahr?

6voto

Vlasec Punkte 5291

Eine wichtige Sache, die niemand erwähnt hat, ist, wie sie mit Schnittstellen und Lambda-Ausdrücken interferiert.

Angenommen, Sie definieren eine MyAppException extends Exception . Sie ist die oberste Ausnahme, die an alle von Ihrer Anwendung ausgelösten Ausnahmen vererbt wird. An manchen Stellen wollen Sie nicht auf die einzelnen Ausnahmen reagieren, sondern der Aufrufer soll sie auflösen, also deklarieren Sie throws MyAppException .

Alles sieht gut aus, bis Sie die Schnittstelle eines anderen Unternehmens verwenden möchten. Offensichtlich erklären sie nicht die Absicht, die MyAppException so dass der Compiler Ihnen nicht einmal erlaubt, Ihre Methoden aufzurufen, die Folgendes deklarieren throws MyAppException dort drin. Dies ist besonders schmerzhaft bei java.util.function .

Wenn Ihre Ausnahme jedoch über RuntimeException gibt es keine Probleme mit Schnittstellen. Sie können die Ausnahme in JavaDoc erwähnen, wenn Sie möchten. Aber ansonsten blubbert es einfach still und leise durch alles durch. Das bedeutet natürlich, dass es Ihre Anwendung beenden kann. Aber in vielen Unternehmensprogrammen gibt es eine Schicht zur Behandlung von Ausnahmen, und ungeprüfte Ausnahmen ersparen eine Menge Ärger.

6voto

Piotr Sobczyk Punkte 6175

Dieser Artikel ist der beste Text über Ausnahmebehandlung in Java, den ich je gelesen habe.

Sie gibt ungeprüften Ausnahmen den Vorzug vor geprüften, aber diese Entscheidung wird sehr ausführlich und mit guten Argumenten begründet.

Ich möchte hier nicht zu viel aus dem Inhalt des Artikels zitieren (es ist am besten, ihn als Ganzes zu lesen), aber er deckt die meisten Argumente der Befürworter unkontrollierter Ausnahmen aus diesem Thread ab. Insbesondere dieses Argument (das recht populär zu sein scheint) wird behandelt:

Nehmen wir den Fall, dass die Ausnahme irgendwo am unteren Ende der API-Schichten ausgelöst wurde und einfach hochgesprudelt ist, weil niemand wusste, dass dieser Fehler überhaupt auftreten kann, und das, obwohl es sich um einen Fehlertyp handelte, der sehr plausibel war, als der aufrufende Code ihn auslöste (FileNotFoundException zum Beispiel im Gegensatz zu VogonsTrashingEarthExcept... in diesem Fall wäre es egal, ob wir ihn behandeln oder nicht, da es nichts mehr gibt, womit man ihn behandeln könnte).

Der Autor "Antworten":

Es ist absolut falsch, anzunehmen, dass alle Laufzeitausnahmen nicht abgefangen und bis an die Spitze der Anwendung weitergegeben werden sollten. Anwendung. (...) Für jede Ausnahmebedingung, die die - aufgrund der System-/Geschäftsanforderungen - gesondert behandelt werden muss müssen Programmierer entscheiden, wo sie abgefangen werden sollen und was zu tun ist, sobald die Bedingung aufgefangen wird. Dies muss streng nach den den tatsächlichen Bedürfnissen der Anwendung erfolgen, nicht auf der Grundlage eines Compiler-Alarms. Alle alle anderen Fehler müssen sich frei bis zum obersten Handler ausbreiten können Handler weitergeleitet werden, wo sie protokolliert werden und ein anmutiger (vielleicht, Beendigung) durchgeführt wird.

Und der Hauptgedanke oder Artikel ist:

Wenn es um Fehlerbehandlung in Software geht, ist die einzige sichere und korrekte Annahme, die jemals gemacht werden kann, dass ein Fehler in absolut jedem Unterprogramm oder Modul auftreten kann, das existiert!

Wenn also " niemand wusste, dass dieser Fehler überhaupt auftreten kann ", dann stimmt etwas nicht mit diesem Projekt. Eine solche Ausnahme sollte zumindest vom allgemeinsten Exception-Handler behandelt werden (z.B. derjenige, der alle Ausnahmen behandelt, die nicht von spezifischeren Handlern behandelt werden), wie der Autor vorschlägt.

Es ist so schade, dass nicht viele Leute diesen tollen Artikel entdecken :-(. Ich empfehle von ganzem Herzen jedem, der zögert, welcher Ansatz besser ist, sich etwas Zeit zu nehmen und ihn zu lesen.

6voto

Piotr Sobczyk Punkte 6175

Das Problem

Das schlimmste Problem, das ich beim Mechanismus der Ausnahmebehandlung sehe, ist, dass es führt zu Code-Duplizierung in großem Umfang ! Seien wir ehrlich: In den meisten Projekten ist in 95% der Fälle alles, was die Entwickler mit einer Ausnahme tun müssen, sie irgendwie dem Benutzer mitzuteilen (und in einigen Fällen auch dem Entwicklungsteam, z.B. durch Senden einer E-Mail mit dem Stack-Trace). Daher wird in der Regel überall dort, wo die Ausnahme behandelt wird, dieselbe Zeile/derselbe Codeblock verwendet.

Nehmen wir an, dass wir in jedem Catch-Block für irgendeine Art von geprüfter Ausnahme eine einfache Protokollierung vornehmen:

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

Wenn es sich um eine häufige Ausnahme handelt, kann es in einer größeren Codebasis sogar mehrere hundert solcher try-catch-Blöcke geben. Nehmen wir nun an, dass wir anstelle der Konsolenprotokollierung eine Popup-Dialog-basierte Ausnahmebehandlung einführen oder zusätzlich eine E-Mail an das Entwicklungsteam senden müssen.

Moment mal... wollen wir wirklich alle diese mehreren hundert Stellen im Code bearbeiten?! Sie verstehen, was ich meine :-).

Die Lösung

Um dieses Problem zu lösen, haben wir das Konzept der Ausnahmebehandler (die ich im Folgenden als EH's bezeichne) zu zentralisieren. Behandlung von Ausnahmen. Jeder Klasse, die Ausnahmen behandeln muss, wird eine Instanz des Exception-Handlers von unserem Injektion von Abhängigkeiten Rahmen. Das typische Muster der Ausnahmebehandlung sieht nun wie folgt aus:

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

Um unsere Ausnahmebehandlung anzupassen, müssen wir den Code nur an einer einzigen Stelle ändern (EH-Code).

Für komplexere Fälle können wir natürlich mehrere Unterklassen von EHs implementieren und die Funktionen nutzen, die unser DI-Framework uns bietet. Durch Änderung der Konfiguration unseres DI-Frameworks können wir die EH-Implementierung leicht global umschalten oder Klassen mit besonderen Anforderungen an die Ausnahmebehandlung spezifische EH-Implementierungen zur Verfügung stellen (z. B. mit der Guice @Named-Annotation).

Auf diese Weise können wir das Verhalten bei der Behandlung von Ausnahmen in der Entwicklungs- und der Freigabeversion der Anwendung (z. B. Entwicklung - Protokollierung des Fehlers und Anhalten der Anwendung, prod - Protokollierung des Fehlers mit mehr Details und Fortsetzen der Ausführung der Anwendung) ohne Aufwand unterscheiden.

Letzte Sache

Zu guter Letzt mag es den Anschein haben, dass die gleiche Art von Zentralisierung erreicht werden kann, indem wir unsere Ausnahmen einfach "nach oben" weitergeben, bis sie in einer Klasse zur Behandlung von Ausnahmen auf höchster Ebene ankommen. Aber das führt zu einer Überfrachtung des Codes und der Signaturen unserer Methoden und führt zu Wartungsproblemen, die von anderen in diesem Thread erwähnt wurden.

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