Der Fall gegen geprüfte Ausnahmen


456

Seit einigen Jahren kann ich die folgende Frage nicht mehr richtig beantworten: Warum sind einige Entwickler so gegen geprüfte Ausnahmen? Ich habe zahlreiche Gespräche geführt, Dinge in Blogs gelesen und gelesen, was Bruce Eckel zu sagen hatte (die erste Person, die ich sah, sprach sich gegen sie aus).

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

Jedes Gespräch, das ich habe, endet damit, dass dieselbe Frage unbeantwortet bleibt. Lassen Sie mich sie einrichten:

Im Allgemeinen (wie Java entworfen wurde),

  • Error ist für Dinge, die niemals gefangen werden sollten (VM hat eine Erdnussallergie und jemand hat ein Glas Erdnüsse darauf fallen lassen)
  • RuntimeException ist für Dinge, die der Programmierer falsch gemacht hat (der Programmierer hat das Ende eines Arrays verlassen)
  • Exception(außer RuntimeException) gilt für Dinge, die außerhalb der Kontrolle des Programmierers liegen (die Festplatte füllt sich beim Schreiben in das Dateisystem, das Limit für das Dateihandle für den Prozess wurde erreicht und Sie können keine weiteren Dateien mehr öffnen).
  • Throwable ist einfach das übergeordnete Element aller Ausnahmetypen.

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

Ein weiteres häufiges Argument ist, dass überprüfte Ausnahmen das Refactor von Code erschweren.

Für das Argument "Ich werde nur beenden" sage ich, dass Sie auch beim Beenden eine angemessene Fehlermeldung anzeigen müssen. Wenn Sie sich nur mit der Behandlung von Fehlern beschäftigen, werden Ihre Benutzer nicht übermäßig glücklich sein, wenn das Programm beendet wird, ohne einen klaren Hinweis darauf zu haben, warum.

Für die Menge "es macht es schwierig, umzugestalten" bedeutet dies, dass nicht die richtige Abstraktionsebene gewählt wurde. Anstatt zu deklarieren, dass eine Methode eine auslöst IOException, IOExceptionsollte die in eine Ausnahme umgewandelt werden, die besser für das geeignet ist, was gerade passiert.

Ich habe kein Problem mit dem Umschließen von 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. Dadurch kann ich zumindest eine entsprechende anzeigen Fehlermeldung.

Die Frage, auf die die Leute nie antworten, lautet:

Wenn Sie RuntimeException Unterklassen anstelle von Exception Unterklassen werfen, woher wissen Sie dann, was Sie fangen sollen?

Wenn die Antwort catch lautet, behandeln ExceptionSie Programmiererfehler genauso wie Systemausnahmen. Das scheint mir falsch zu sein.

Wenn Sie abfangen Throwable, behandeln Sie Systemausnahmen und VM-Fehler (und dergleichen) auf die gleiche Weise. Das scheint mir falsch zu sein.

Wenn die Antwort lautet, dass Sie nur die Ausnahmen abfangen, von denen Sie wissen, dass sie ausgelöst werden, woher wissen Sie dann, welche ausgelöst werden? Was passiert, wenn Programmierer X eine neue Ausnahme auslöst und vergisst, sie abzufangen? Das scheint mir sehr gefährlich zu sein.

Ich würde sagen, dass ein Programm, das eine Stapelverfolgung anzeigt, falsch ist. Fühlen sich Menschen, die geprüfte Ausnahmen nicht mögen, nicht so?

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

Edit: Ich bin nicht auf der Suche nach Rat , wenn entweder Modell zu verwenden, was ich suche ist , warum Menschen aus verlängern , RuntimeExceptionweil sie nicht wie die sie von Exceptionund / oder warum sie eine Ausnahme fangen und dann erneut auslösen eine RuntimeExceptioneher als Add Würfe ihre Methode. Ich möchte die Motivation verstehen, geprüfte Ausnahmen nicht zu mögen.


46
Ich denke nicht, dass es völlig subjektiv ist - es ist eine Sprachfunktion, die für eine bestimmte Verwendung entwickelt wurde, anstatt dass jeder selbst entscheiden kann, was es für sich ist. Und es ist nicht besonders argumentativ, es befasst sich im Voraus mit spezifischen Widerlegungen, die sich die Leute leicht hätten einfallen lassen können.
Gareth

6
Komm schon. Dieses als Sprachmerkmal angesehene Thema wurde und kann objektiv angegangen werden.
Kurt Schelfthout

6
@cletus "Beantworte deine eigene Frage" Wenn ich die Antwort hätte, hätte ich die Frage nicht gestellt!
TofuBeer

8
Gute Frage. In C ++ gibt es überhaupt keine geprüften Ausnahmen, und meiner Meinung nach macht es die Ausnahmefunktion unbrauchbar. Sie geraten in eine Situation, in der Sie jeden einzelnen Funktionsaufruf, den Sie ausführen, mit einem Haken versehen müssen, weil Sie einfach nicht wissen, ob er etwas auslösen könnte.
Dimitri C.

4
Das stärkste Argument, das ich für geprüfte Ausnahmen kenne , ist, dass sie ursprünglich nicht in Java vorhanden waren und dass sie bei ihrer Einführung Hunderte von Fehlern im JDK entdeckt haben. Dies ist etwas vor Java 1.0. Ich persönlich wäre nicht ohne sie und würde Bruce Eckel und anderen diesbezüglich heftig widersprechen.
Marquis von Lorne

Antworten:


277

Ich glaube, ich habe das gleiche Interview mit Bruce Eckel gelesen, das Sie geführt haben - und es hat mich immer nervt. Tatsächlich wurde das Argument vom Befragten (wenn dies tatsächlich der Beitrag ist, über den Sie sprechen) Anders Hejlsberg, dem MS-Genie hinter .NET und C #, vorgebracht.

http://www.artima.com/intv/handcuffs.html

Fan, obwohl ich von Hejlsberg und seiner Arbeit bin, hat mich dieses Argument immer als Schwindel empfunden. Es läuft im Grunde auf Folgendes hinaus:

"Überprüfte Ausnahmen sind schlecht, weil Programmierer sie einfach missbrauchen, indem sie sie immer abfangen und entlassen, was dazu führt, dass Probleme ausgeblendet und ignoriert werden, die sonst dem Benutzer angezeigt würden."

Mit "dem Benutzer anderweitig präsentiert" meine ich, wenn Sie eine Laufzeitausnahme verwenden, ignoriert der faule Programmierer diese einfach (im Gegensatz zum Abfangen mit einem leeren Fangblock) und der Benutzer sieht sie.

Die Zusammenfassung der Zusammenfassung des Arguments ist , dass „Programmierer sie nicht richtig verwenden und nicht mit ihnen richtig ist schlimmer , als sie nicht mit“ .

Dieses Argument hat eine gewisse Wahrheit, und ich vermute, dass Goslings Motivation, keine Operatorüberschreibungen in Java zu setzen, auf einem ähnlichen Argument beruht - sie verwirren den Programmierer, weil sie häufig missbraucht werden.

Aber am Ende finde ich es ein falsches Argument von Hejlsberg und möglicherweise ein Post-hoc-Argument, das geschaffen wurde, um den Mangel zu erklären, und keine gut durchdachte Entscheidung.

Ich würde argumentieren, dass die Überbeanspruchung von geprüften Ausnahmen zwar eine schlechte Sache ist und dazu neigt, dass Benutzer schlampig damit umgehen, aber die ordnungsgemäße Verwendung ermöglicht es dem API-Programmierer, dem API-Client-Programmierer große Vorteile zu verschaffen.

Jetzt muss der API-Programmierer darauf achten, nicht überall geprüfte Ausnahmen auszulösen, sonst nervt er den Client-Programmierer einfach. Der sehr faule Client-Programmierer wird auf den Fang zurückgreifen, (Exception) {}wie Hejlsberg warnt, und alle Vorteile gehen verloren und die Hölle wird folgen. Unter bestimmten Umständen gibt es jedoch keinen Ersatz für eine gut geprüfte Ausnahme.

Das klassische Beispiel ist für mich die API zum Öffnen von Dateien. Jede Programmiersprache in der Geschichte der Sprachen (zumindest auf Dateisystemen) verfügt irgendwo über eine API, mit der Sie eine Datei öffnen können. Und jeder Client-Programmierer, der diese API verwendet, weiß, dass er sich mit dem Fall befassen muss, dass die Datei, die er öffnen möchte, nicht vorhanden ist. Lassen Sie mich das umformulieren: Jeder Client-Programmierer, der diese API verwendet, sollte wissen , dass er sich mit diesem Fall befassen muss. Und da ist das Problem: Kann der API-Programmierer ihnen helfen, zu wissen, dass sie damit umgehen sollten, indem sie nur Kommentare abgeben, oder können sie tatsächlich darauf bestehen, dass der Client damit umgeht ?

In C geht die Redewendung so etwas wie

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

wo fopenFehler anzeigt von 0 und C zurückkehrt (dummerweise ) können Sie 0 als boolean behandeln und ... Grundsätzlich Sie dieses Idiom lernen und du bist in Ordnung. Aber was ist, wenn Sie ein Noob sind und die Redewendung nicht gelernt haben? Dann fangen Sie natürlich an mit

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

und auf die harte Tour lernen.

Beachten Sie, dass es sich hier nur um stark typisierte Sprachen handelt: Es gibt eine klare Vorstellung davon, was eine API in einer stark typisierten Sprache ist: Es ist eine Fülle von Funktionen (Methoden), die Sie mit einem klar definierten Protokoll für jede Sprache verwenden können.

Dieses klar definierte Protokoll wird typischerweise durch eine Methodensignatur definiert. Hier erfordert fopen, dass Sie ihm eine Zeichenfolge übergeben (oder ein Zeichen * im Fall von C). Wenn Sie etwas anderes angeben, wird ein Fehler beim Kompilieren angezeigt. Sie haben das Protokoll nicht befolgt - Sie verwenden die API nicht ordnungsgemäß.

In einigen (obskuren) Sprachen ist der Rückgabetyp ebenfalls Teil des Protokolls. Wenn Sie versuchen, das Äquivalent von fopen()in einigen Sprachen aufzurufen, ohne es einer Variablen zuzuweisen, wird auch ein Fehler beim Kompilieren angezeigt (dies ist nur mit void-Funktionen möglich).

Der Punkt, den ich versuche, ist folgender: In einer statisch typisierten Sprache ermutigt der API-Programmierer den Client, die API ordnungsgemäß zu verwenden, indem er verhindert, dass der Client-Code kompiliert wird, wenn offensichtliche Fehler auftreten.

(In einer dynamisch typisierten Sprache wie Ruby können Sie alles, z. B. ein Float, als Dateinamen übergeben - und es wird kompiliert. Warum den Benutzer mit aktivierten Ausnahmen belästigen, wenn Sie nicht einmal die Methodenargumente steuern Die hier vorgebrachten Argumente gelten nur für statisch typisierte Sprachen.)

Was ist also mit geprüften Ausnahmen?

Hier ist eine der Java-APIs, die Sie zum Öffnen einer Datei verwenden können.

try {
  f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
  // deal with it. No really, deal with it!
  ... // this is me dealing with it
}

Sehen Sie diesen Fang? Hier ist die Signatur für diese API-Methode:

public FileInputStream(String name)
                throws FileNotFoundException

Beachten Sie, dass dies FileNotFoundExceptioneine aktivierte Ausnahme ist.

Der API-Programmierer sagt Ihnen Folgendes: "Sie können diesen Konstruktor verwenden, um einen neuen FileInputStream zu erstellen, aber Sie

a) muss den Dateinamen als String übergeben
b) muss die Möglichkeit akzeptieren, dass die Datei zur Laufzeit nicht gefunden wird "

Und das ist für mich der springende Punkt.

Der Schlüssel ist im Grunde das, was in der Frage als "Dinge, die außerhalb der Kontrolle des Programmierers liegen" steht. Mein erster Gedanke war, dass er / sie Dinge meint, die außerhalb der Kontrolle des API- Programmierers liegen. Tatsächlich sollten geprüfte Ausnahmen bei ordnungsgemäßer Verwendung jedoch für Dinge gelten, die außerhalb der Kontrolle des Client-Programmierers und des API-Programmierers liegen. Ich denke, dies ist der Schlüssel, um geprüfte Ausnahmen nicht zu missbrauchen.

Ich denke, das Öffnen der Datei veranschaulicht den Punkt gut. Der API-Programmierer weiß, dass Sie ihm möglicherweise einen Dateinamen geben, der zum Zeitpunkt des Aufrufs der API nicht vorhanden ist, und dass er Ihnen nicht das zurückgeben kann, was Sie wollten, sondern eine Ausnahme auslösen muss. Sie wissen auch, dass dies ziemlich regelmäßig vorkommt und dass der Client-Programmierer möglicherweise erwartet, dass der Dateiname zum Zeitpunkt des Aufrufs des Aufrufs korrekt ist, dies jedoch zur Laufzeit aus Gründen, die außerhalb ihrer Kontrolle liegen, möglicherweise falsch ist.

Die API macht es also explizit: Es wird Fälle geben, in denen diese Datei zum Zeitpunkt Ihres Anrufs nicht vorhanden ist und Sie sich verdammt noch mal besser damit befassen sollten.

Dies wäre bei einem Gegenfall klarer. Stellen Sie sich vor, ich schreibe eine Tabellen-API. Ich habe das Tabellenmodell irgendwo mit einer API, die diese Methode enthält:

public RowData getRowData(int row) 

Als API-Programmierer weiß ich, dass es Fälle geben wird, in denen ein Client einen negativen Wert für die Zeile oder einen Zeilenwert außerhalb der Tabelle übergibt. Ich könnte also versucht sein, eine aktivierte Ausnahme auszulösen und den Client zu zwingen, damit umzugehen:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(Ich würde es natürlich nicht wirklich "Checked" nennen.)

Dies ist eine schlechte Verwendung von geprüften Ausnahmen. Der Client-Code wird voll von Aufrufen zum Abrufen von Zeilendaten sein, von denen jeder einen try / catch verwenden muss, und wofür? Werden sie dem Benutzer melden, dass die falsche Zeile gesucht wurde? Wahrscheinlich nicht - denn unabhängig von der Benutzeroberfläche, die meine Tabellenansicht umgibt, sollte der Benutzer nicht in einen Zustand geraten, in dem eine unzulässige Zeile angefordert wird. Es ist also ein Fehler des Client-Programmierers.

Der API-Programmierer kann weiterhin vorhersagen, dass der Client solche Fehler codiert, und sollte dies mit einer Laufzeitausnahme wie z IllegalArgumentException.

Mit einer aktivierten Ausnahme getRowDataist dies eindeutig ein Fall, der dazu führen wird, dass Hejlsbergs fauler Programmierer einfach leere Fänge hinzufügt. In diesem Fall sind die unzulässigen Zeilenwerte selbst für das Debuggen des Testers oder des Client-Entwicklers nicht offensichtlich, sondern führen zu Folgefehlern, deren Ursache schwer zu bestimmen ist. Arianne-Raketen werden nach dem Start explodieren.

Okay, hier ist das Problem: Ich sage, dass die aktivierte Ausnahme FileNotFoundExceptionnicht nur eine gute Sache ist, sondern ein wesentliches Werkzeug in der Toolbox des API-Programmierers, um die API auf die für den Client-Programmierer nützlichste Weise zu definieren. Dies CheckedInvalidRowNumberExceptionist jedoch eine große Unannehmlichkeit, die zu einer schlechten Programmierung führt und vermieden werden sollte. Aber wie erkennt man den Unterschied?

Ich denke, es ist keine exakte Wissenschaft, und ich denke, das liegt Hejlsbergs Argumentation zugrunde und rechtfertigt es vielleicht bis zu einem gewissen Grad. Aber ich bin nicht glücklich, das Baby hier mit dem Badewasser rauszuwerfen. Erlauben Sie mir daher, hier einige Regeln zu extrahieren, um gut geprüfte Ausnahmen von schlechten zu unterscheiden:

  1. Außerhalb der Kontrolle des Kunden oder geschlossen gegen offen:

    Überprüfte Ausnahmen sollten nur verwendet werden, wenn der Fehlerfall sowohl von der API als auch vom Client-Programmierer nicht kontrolliert wird . Dies hängt damit zusammen, wie offen oder geschlossen das System ist. In einer eingeschränkten Benutzeroberfläche, in der der Client-Programmierer beispielsweise die Kontrolle über alle Schaltflächen, Tastaturbefehle usw. hat, die Zeilen zur Tabellenansicht hinzufügen und daraus löschen (ein geschlossenes System), handelt es sich um einen Client-Programmierfehler, wenn er versucht, Daten abzurufen eine nicht vorhandene Zeile. In einem dateibasierten Betriebssystem, in dem eine beliebige Anzahl von Benutzern / Anwendungen Dateien hinzufügen und löschen kann (ein offenes System), ist es denkbar, dass die vom Client angeforderte Datei ohne deren Wissen gelöscht wurde, sodass von ihnen erwartet werden sollte, dass sie sich damit befassen .

  2. Allgegenwart:

    Überprüfte Ausnahmen sollten nicht für einen API-Aufruf verwendet werden, der häufig vom Client ausgeführt wird. Mit häufig meine ich von vielen Stellen im Client-Code - nicht häufig rechtzeitig. Ein Client-Code versucht also nicht oft, dieselbe Datei zu öffnen, aber meine Tabellenansicht wird RowDatavon verschiedenen Methoden überall angezeigt. Insbesondere werde ich eine Menge Code wie schreiben

    if (model.getRowData().getCell(0).isEmpty())

    und es wird schmerzhaft sein, jedes Mal versuchen / fangen zu müssen.

  3. Informieren des Benutzers:

    Aktivierte Ausnahmen sollten in Fällen verwendet werden, in denen Sie sich vorstellen können, dass dem Endbenutzer eine nützliche Fehlermeldung angezeigt wird. Dies ist das "und was wirst du tun, wenn es passiert?" Frage, die ich oben aufgeworfen habe. Dies bezieht sich auch auf Punkt 1. Da Sie vorhersagen können, dass etwas außerhalb Ihres Client-API-Systems dazu führen kann, dass die Datei nicht vorhanden ist, können Sie dem Benutzer vernünftigerweise davon erzählen:

    "Error: could not find the file 'goodluckfindingthisfile'"

    Da Ihre illegale Zeilennummer durch einen internen Fehler und ohne Verschulden des Benutzers verursacht wurde, gibt es wirklich keine nützlichen Informationen, die Sie ihnen geben können. Wenn Ihre App keine Laufzeitausnahmen auf die Konsole zulässt, werden sie wahrscheinlich eine hässliche Nachricht erhalten wie:

    "Internal error occured: IllegalArgumentException in ...."

    Kurz gesagt, wenn Sie nicht glauben, dass Ihr Client-Programmierer Ihre Ausnahme auf eine Weise erklären kann, die dem Benutzer hilft, sollten Sie wahrscheinlich keine aktivierte Ausnahme verwenden.

Das sind also meine Regeln. Etwas erfunden, und es wird zweifellos Ausnahmen geben (bitte helfen Sie mir, sie zu verfeinern, wenn Sie so wollen). Mein Hauptargument ist jedoch, dass es Fälle gibt, in FileNotFoundExceptiondenen die überprüfte Ausnahme für den API-Vertrag genauso wichtig und nützlich ist wie die Parametertypen. Wir sollten also nicht darauf verzichten, nur weil es missbraucht wird.

Entschuldigung, ich wollte das nicht so lang und waffelig machen. Lassen Sie mich mit zwei Vorschlägen abschließen:

A: API-Programmierer: Verwenden Sie geprüfte Ausnahmen sparsam, um ihre Nützlichkeit zu erhalten. Verwenden Sie im Zweifelsfall eine ungeprüfte Ausnahme.

B: Client-Programmierer: Gewöhnen Sie sich an, frühzeitig in Ihrer Entwicklung eine umschlossene Ausnahme (google it) zu erstellen. JDK 1.4 und höher bieten hierfür einen Konstruktor RuntimeException, aber Sie können auch problemlos einen eigenen erstellen. Hier ist der Konstruktor:

public RuntimeException(Throwable cause)

Gewöhnen Sie sich dann an, wann immer Sie eine aktivierte Ausnahme behandeln müssen und sich faul fühlen (oder Sie denken, der API-Programmierer hat die überprüfte Ausnahme überhaupt übereifrig verwendet), schlucken Sie die Ausnahme nicht einfach, sondern wickeln Sie sie ein und wirf es neu.

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

Fügen Sie dies in eine der kleinen Codevorlagen Ihrer IDE ein und verwenden Sie es, wenn Sie sich faul fühlen. Auf diese Weise müssen Sie, wenn Sie die aktivierte Ausnahme wirklich behandeln müssen, zurückkehren und sie beheben, nachdem Sie das Problem zur Laufzeit gesehen haben. Denn glauben Sie mir (und Anders Hejlsberg), Sie werden nie wieder zu diesem TODO in Ihrem zurückkehren

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}

110
Das Öffnen von Dateien ist eigentlich ein gutes Beispiel für äußerst kontraproduktiv geprüfte Ausnahmen. Da der Code, der die Datei öffnet, die meiste Zeit nichts Nützliches gegen die Ausnahme tun kann, ist dies besser, wenn Sie mehrere Ebenen des Aufrufstapels ausführen. Die aktivierte Ausnahme zwingt Sie dazu, Ihre Methodensignaturen zu überladen, damit Sie das tun können, was Sie sowieso getan hätten - behandeln Sie die Ausnahme an der Stelle, an der dies am sinnvollsten ist.
Michael Borgwardt

116
@ Michael: Vereinbarte Sie in der Regel diese IO Ausnahmen mehrere Ebenen höher behandeln könnte - aber ich höre nicht, dass als eine API - Client zu leugnen , Sie haben diese zu antizipieren. Aus diesem Grund halte ich geprüfte Ausnahmen für angemessen. Ja, Sie müssen auf jedem Aufruf-Stack der Methode "Würfe" deklarieren, aber ich bin nicht der Meinung, dass dies Unordnung ist. Es sind nützliche deklarative Informationen in Ihren Methodensignaturen. Sie sagen: Meine Low-Level-Methoden könnten auf eine fehlende Datei stoßen, aber sie überlassen die Handhabung Ihnen. Ich sehe nichts zu verlieren und nur guten, sauberen Code zu gewinnen
Rhabarber

23
@Rhubarb: +1, sehr interessante Antwort, zeigt Argumente für beide Seiten. Großartig. Zu Ihrem letzten Kommentar: Beachten Sie, dass das Deklarieren von Würfen für jede Methode im Aufrufstapel möglicherweise nicht immer möglich ist, insbesondere wenn Sie unterwegs eine Schnittstelle implementieren.
R. Martinho Fernandes

8
Dies ist ein sehr starkes Argument (und ich stimme dem im Allgemeinen zu), aber es gibt einen Fall, in dem es nicht funktioniert: generische Rückrufe. Wenn eine Bibliothek einen benutzerdefinierten Code aufruft, gibt es (wie ich weiß, in Java) keine Möglichkeit für diese Bibliothek, geprüfte Ausnahmen vom aufgerufenen Benutzercode an den aufrufenden Benutzercode weiterzugeben. Dieses Problem erstreckt sich auch auf andere Teile des Typsystems vieler statisch typisierter Sprachen, die keine geeigneten generischen Typen unterstützen. Diese Antwort würde durch eine Erwähnung (und möglicherweise eine Antwort) verbessert.
Mankarse

7
Falscher @Rhubarb. Überprüfte Ausnahmen waren für "Eventualverbindlichkeiten" gedacht, die behandelt werden konnten und sollten, aber immer nicht mit den Best Practices "früh werfen, spät fangen" vereinbar waren. Das Öffnen einer Datei ist ein ausgewähltes Beispiel, das behandelt werden kann . Die meisten IOException (z. B. Schreiben eines Bytes), SQLException und RemoteException können nicht sinnvoll behandelt werden. Fehler oder Wiederholungsversuche sollten auf der Ebene "Geschäft" oder "Anforderung" erfolgen, und überprüfte Ausnahmen - wie sie in Java-Bibliotheken verwendet werden - sind ein Fehler, der dies schwierig macht. literatejava.com/exceptions/…
Thomas W

184

Die Sache mit geprüften Ausnahmen ist, dass sie nach dem üblichen Verständnis des Konzepts keine wirklichen Ausnahmen sind. Stattdessen handelt es sich um alternative API-Rückgabewerte.

Die ganze Idee von Ausnahmen ist, dass ein Fehler, der irgendwo in der Aufrufkette ausgelöst wird, in die Luft sprudeln und irgendwo weiter oben von Code behandelt werden kann, ohne dass sich der dazwischenliegende Code darum kümmern muss. Überprüfte Ausnahmen erfordern andererseits, dass jede Codeebene zwischen dem Werfer und dem Fänger erklärt, dass sie über alle Arten von Ausnahmen informiert sind, die sie durchlaufen können. Dies unterscheidet sich in der Praxis kaum davon, dass überprüfte Ausnahmen lediglich spezielle Rückgabewerte waren, auf die der Anrufer prüfen musste. zB [Pseudocode]:

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

Da Java keine alternativen Rückgabewerte oder einfache Inline-Tupel als Rückgabewerte ausführen kann, sind geprüfte Ausnahmen eine vernünftige Antwort.

Das Problem ist, dass viele Codes, einschließlich großer Teile der Standardbibliothek, geprüfte Ausnahmen für echte Ausnahmebedingungen missbrauchen, die Sie möglicherweise auf mehreren Ebenen nachholen möchten. Warum ist IOException keine RuntimeException? In jeder anderen Sprache kann ich eine E / A-Ausnahme zulassen. Wenn ich nichts unternehme, wird meine Anwendung gestoppt und ich bekomme einen praktischen Stack-Trace zum Anschauen. Dies ist das Beste, was passieren kann.

Möglicherweise zwei Methoden aus dem Beispiel, mit dem Sie alle IOExceptions aus dem gesamten Schreib-Stream-Prozess abfangen, den Prozess abbrechen und in den Fehlerberichtscode springen möchten. In Java können Sie dies nicht tun, ohne auf jeder Aufrufebene "IOException auslösen" hinzuzufügen, selbst auf Ebenen, die selbst keine E / A ausführen. Solche Methoden sollten nichts über die Ausnahmebehandlung wissen müssen. Ausnahmen zu ihren Signaturen hinzufügen müssen:

  1. erhöht unnötig die Kopplung;
  2. macht Schnittstellensignaturen sehr spröde, um sich zu ändern;
  3. macht den Code weniger lesbar;
  4. ist so ärgerlich, dass die übliche Reaktion des Programmierers darin besteht, das System zu besiegen, indem er etwas Schreckliches tut, wie "Ausnahme auslösen", "Ausnahme (e) {}" auslösen oder alles in eine RuntimeException einschließen (was das Debuggen erschwert).

Und dann gibt es viele lächerliche Bibliotheksausnahmen wie:

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

Wenn Sie Ihren Code mit so einem lächerlichen Grobstoff überladen müssen, ist es kein Wunder, dass geprüfte Ausnahmen eine Menge Hass erhalten, obwohl dies wirklich nur ein einfaches schlechtes API-Design ist.

Ein weiterer besonderer Nachteil ist die Umkehrung der Steuerung, bei der Komponente A einen Rückruf an die generische Komponente B liefert. Komponente A möchte eine Ausnahme von ihrem Rückruf an die Stelle zurückwerfen können, an der sie Komponente B aufgerufen hat, kann dies jedoch nicht weil dies die von B festgelegte Rückrufschnittstelle ändern würde. A kann dies nur tun, indem die reale Ausnahme in eine RuntimeException eingeschlossen wird, die noch mehr Ausnahmebehandlungs-Boilerplate zum Schreiben enthält.

Überprüfte Ausnahmen, wie sie in Java und seiner Standardbibliothek implementiert sind, bedeuten Boilerplate, Boilerplate, Boilerplate. In einer bereits ausführlichen Sprache ist dies kein Gewinn.


14
In Ihrem Codebeispiel ist es am besten, die Ausnahmen so zu verketten, dass beim Lesen der Protokolle die ursprüngliche Ursache gefunden wird: throw CanNeverHappenException (e);
Esko Luontola

5
Ich stimme dir nicht zu. Ausnahmen, geprüft oder nicht, sind außergewöhnliche Bedingungen. Beispiel: Eine Methode, die ein Objekt über HTTP abruft. Der Rückgabewert ist sonst das Objekt oder nichts, alle Dinge, die schlecht werden können, sind außergewöhnlich. Die Behandlung als Rückgabewerte wie in C führt nur zu Verwirrung und schlechtem Design.
Mister Smith

15
@Mister: Ich sage, dass sich geprüfte Ausnahmen, wie sie in Java implementiert sind, in der Praxis eher wie Rückgabewerte wie in C verhalten als die traditionellen 'Ausnahmen', die wir möglicherweise aus C ++ und anderen Sprachen vor Java erkennen. Und dass IMO dies in der Tat zu Verwirrung und schlechtem Design führt.
Bobince

9
Stimmen Sie zu, dass der Missbrauch von geprüften Ausnahmen durch die Standardbibliotheken definitiv zu Verwirrung und schlechtem Fangverhalten beigetragen hat. Und oft ist es nur eine schlechte Dokumentation, z. B. eine Abreißmethode wie "connect" (), die eine IOException auslöst, wenn "ein anderer E / A-Fehler auftritt". Nun, ich habe die Verbindung getrennt! Lecke ich einen Griff oder eine andere Ressource? Muss ich es erneut versuchen? Ohne zu wissen, warum es passiert ist, kann ich nicht die Maßnahmen ableiten, die ich ergreifen sollte, und daher muss ich raten, ob ich es einfach schlucken, erneut versuchen oder auf Kaution gehen soll.
Charstar

14
+1 für "Alternative API-Rückgabewerte". Interessante Sichtweise auf geprüfte Ausnahmen.
Zsolt Török

75

Anstatt alle (vielen) Gründe gegen geprüfte Ausnahmen erneut aufzuwärmen, werde ich nur einen auswählen. Ich habe die Anzahl der Male verloren, die ich diesen Codeblock geschrieben habe:

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

In 99% der Fälle kann ich nichts dagegen tun. Schließlich führen Blöcke alle erforderlichen Aufräumarbeiten durch (oder sollten dies zumindest tun).

Ich habe auch die Anzahl der Male verloren, die ich das gesehen habe:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

Warum? Weil jemand damit umgehen musste und faul war. War es falsch Sicher. Passiert es Absolut. Was wäre, wenn dies stattdessen eine ungeprüfte Ausnahme wäre? Die App wäre gerade gestorben (was dem Schlucken einer Ausnahme vorzuziehen ist).

Und dann haben wir wütenden Code, der Ausnahmen als eine Form der Flusskontrolle verwendet, wie es java.text.Format tut. Bzzzt. Falsch. Ein Benutzer, der "abc" in ein Zahlenfeld eines Formulars einfügt, ist keine Ausnahme.

Ok, ich denke das waren drei Gründe.


4
Wenn eine Ausnahme ordnungsgemäß abgefangen wird, können Sie den Benutzer informieren, andere Aufgaben ausführen (Protokoll?) Und die Anwendung auf kontrollierte Weise beenden. Ich bin damit einverstanden, dass einige API-Teile besser hätten entworfen werden können. Und aus dem Grund des faulen Programmierers denke ich, dass Sie als Programmierer zu 100% für Ihren Code verantwortlich sind.
Herr Smith

3
Beachten Sie, dass Sie mit try-catch-rethrow eine Nachricht angeben können. Normalerweise verwende ich sie, um Informationen über den Inhalt von Statusvariablen hinzuzufügen. Ein häufiges Beispiel ist, dass IOExceptions den absoluten Pfadnamen () der betreffenden Datei hinzufügen.
Thorbjørn Ravn Andersen

15
Ich denke, IDEs wie Eclipse haben eine Menge Schuld daran, wie oft Sie den leeren Fangblock gesehen haben. Wirklich, sie sollten standardmäßig neu werfen.
Artbristol

12
"99% der Zeit kann ich nichts dagegen tun" - falsch, Sie können dem Benutzer die Meldung "Verbindung zum Server konnte nicht hergestellt werden" oder "Das E / A-Gerät ist ausgefallen" anzeigen, anstatt nur die App abstürzen zu lassen aufgrund eines kleinen Netzwerk-Schluckaufs. Beide Beispiele sind Arbeitskünste von schlechten Programmierern. Sie sollten die schlechten Programmierer angreifen und die Ausnahmen nicht selbst überprüfen. Es ist, als würde ich Insulin angreifen, weil ich bei meinem Diabetes nicht helfe, wenn ich es als Salatdressing verwende.
AxiomaticNexus

2
@YasmaniLlanes Sie können diese Dinge nicht immer tun. Manchmal müssen Sie eine Schnittstelle einhalten. Dies gilt insbesondere dann, wenn Sie gut wartbare APIs entwerfen, da Sie nicht einfach überall Nebenwirkungen auslösen können. Sowohl das als auch die damit verbundene Komplexität werden Sie in großem Maßstab schwer beißen. Also ja, 99% der Zeit gibt es nichts zu tun.
MasterMastic

50

Ich weiß, dass dies eine alte Frage ist, aber ich habe eine Weile mit geprüften Ausnahmen gerungen und ich muss etwas hinzufügen. Bitte vergib mir die Länge!

Mein Hauptrindfleisch mit geprüften Ausnahmen ist, dass sie den Polymorphismus ruinieren. Es ist unmöglich, sie mit polymorphen Schnittstellen gut spielen zu lassen.

Nehmen Sie die gute alte Java- ListOberfläche. Wir haben gemeinsame In-Memory-Implementierungen wie ArrayListund LinkedList. Wir haben auch die Skelettklasse, AbstractListdie es einfach macht, neue Arten von Listen zu entwerfen. Für eine schreibgeschützte Liste müssen nur zwei Methoden implementiert werden: size()und get(int index).

Diese Beispielklasse WidgetListliest einige Objekte fester Größe vom Typ Widget(nicht gezeigt) aus einer Datei:

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

Indem Sie die Widgets über die vertraute ListOberfläche verfügbar machen , können Sie Elemente abrufen ( list.get(123)) oder eine Liste iterieren ( for (Widget w : list) ...), ohne sich WidgetListselbst kennen zu müssen . Man kann diese Liste an alle Standardmethoden übergeben, die generische Listen verwenden, oder sie in eine einschließen Collections.synchronizedList. Code, der ihn verwendet, muss weder wissen noch sich darum kümmern, ob die "Widgets" vor Ort erstellt werden, aus einem Array stammen oder aus einer Datei oder Datenbank oder aus dem gesamten Netzwerk oder aus einem zukünftigen Subraum-Relay gelesen werden. Es wird weiterhin korrekt funktionieren, da die ListSchnittstelle korrekt implementiert ist.

Nur dass es nicht so ist. Die obige Klasse wird nicht kompiliert, da die Dateizugriffsmethoden möglicherweise IOExceptioneine aktivierte Ausnahme auslösen, die Sie "abfangen oder angeben" müssen. Sie können es nicht als ausgelöst angeben - der Compiler lässt Sie nicht zu, da dies den Vertrag der ListSchnittstelle verletzen würde . Und es gibt keine nützliche Möglichkeit, WidgetListdie Ausnahme selbst zu behandeln (wie ich später erläutern werde).

Anscheinend ist das einzige, was zu tun ist, geprüfte Ausnahmen zu fangen und erneut zu werfen, als eine ungeprüfte Ausnahme:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((Bearbeiten: Java 8 hat eine UncheckedIOExceptionKlasse für genau diesen Fall hinzugefügt : zum Abfangen und erneuten Werfen von IOExceptions über polymorphe Methodengrenzen hinweg. Das beweist irgendwie meinen Standpunkt!))

Daher funktionieren geprüfte Ausnahmen in solchen Fällen einfach nicht . Du kannst sie nicht werfen. Das Gleiche gilt für eine clevere Map, von einer Datenbank unterstützte Implementierung oder eine Implementierung, java.util.Randomdie über einen COM-Port mit einer Quantenentropiequelle verbunden ist. Sobald Sie versuchen, mit der Implementierung einer polymorphen Schnittstelle etwas Neues zu tun, schlägt das Konzept der überprüften Ausnahmen fehl. Überprüfte Ausnahmen sind jedoch so heimtückisch, dass sie Sie immer noch nicht in Ruhe lassen, da Sie immer noch Methoden auf niedrigerer Ebene abfangen und erneut werfen müssen, den Code überladen und die Stapelverfolgung überladen müssen.

Ich finde, dass die allgegenwärtige RunnableSchnittstelle oft in diese Ecke zurückgesetzt wird, wenn sie etwas aufruft, das geprüfte Ausnahmen auslöst. Die Ausnahme kann nicht so wie sie ist ausgelöst werden RuntimeException. Alles, was sie tun kann, ist, den Code durch Abfangen und erneutes Werfen als zu überladen .

Eigentlich Sie können nicht angemeldete werfen Ausnahmen geprüft , ob Sie Hacks greifen. Die JVM kümmert sich zur Laufzeit nicht um geprüfte Ausnahmeregeln, daher müssen wir nur den Compiler zum Narren halten. Der einfachste Weg, dies zu tun, besteht darin, Generika zu missbrauchen. Dies ist meine Methode dafür (Klassenname wird angezeigt, weil er (vor Java 8) in der aufrufenden Syntax für die generische Methode erforderlich ist):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

Hurra! Auf diese Weise können wir eine aktivierte Ausnahme in jeder Tiefe des Stapels auslösen, ohne sie zu deklarieren, ohne sie in eine zu wickeln RuntimeExceptionund ohne die Stapelspur zu überladen! Verwenden Sie das Beispiel "WidgetList" erneut:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

Leider ist die letzte Beleidigung der geprüfte Ausnahmen , dass der Compiler , damit Sie weigert sich fangen eine geprüfte Ausnahme , wenn sie der Meinung fehlerhaft, ist es nicht geworfen worden sein könnte. (Nicht aktivierte Ausnahmen haben diese Regel nicht.) Um die hinterhältig ausgelöste Ausnahme abzufangen, müssen wir Folgendes tun:

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

Das ist etwas umständlich, aber auf der positiven Seite ist es immer noch etwas einfacher als der Code zum Extrahieren einer geprüften Ausnahme, die in a eingeschlossen wurde RuntimeException.

Glücklicherweise ist die throw t;Aussage hier legal, obwohl der Typ tüberprüft wird, dank einer in Java 7 hinzugefügten Regel zum erneuten Auslösen abgefangener Ausnahmen.


Wenn geprüfte Ausnahmen auf Polymorphismus treffen, ist auch der umgekehrte Fall ein Problem: Wenn eine Methode als potenziell ausgelöste Ausnahme ausgelöst wird, eine überschriebene Implementierung jedoch nicht. Zum Beispiel ist die abstrakte Klasse OutputStream‚s writealle Methoden angeben throws IOException. ByteArrayOutputStreamist eine Unterklasse, die anstelle einer echten E / A-Quelle in ein In-Memory-Array schreibt. Die überschriebenen writeMethoden können kein IOExceptions verursachen , daher haben sie keine throwsKlausel, und Sie können sie aufrufen, ohne sich um die Catch-or-Specify-Anforderung zu kümmern.

Außer nicht immer. Angenommen, es Widgetgibt eine Methode zum Speichern in einem Stream:

public void writeTo(OutputStream out) throws IOException;

Es OutputStreamist richtig, diese Methode so zu deklarieren, dass sie eine Ebene akzeptiert. Daher kann sie polymorph für alle Arten von Ausgaben verwendet werden: Dateien, Datenbanken, das Netzwerk usw. Und In-Memory-Arrays. Bei einem In-Memory-Array besteht jedoch eine falsche Anforderung, um eine Ausnahme zu behandeln, die tatsächlich nicht auftreten kann:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

Überprüfte Ausnahmen stören wie immer. Wenn Ihre Variablen als Basistyp deklariert sind, für den offenere Ausnahmeanforderungen gelten, müssen Sie Handler für diese Ausnahmen hinzufügen, auch wenn Sie wissen, dass sie in Ihrer Anwendung nicht vorkommen.

Aber warten Sie, überprüfte Ausnahmen sind tatsächlich so ärgerlich, dass Sie nicht einmal das Gegenteil tun können! Stellen Sie sich vor, Sie fangen derzeit alle IOExceptiondurch writeAufrufe von a ausgelösten OutputStream, aber Sie möchten den deklarierten Typ der Variablen in a ändern. Der ByteArrayOutputStreamCompiler beschimpft Sie, wenn Sie versuchen, eine aktivierte Ausnahme abzufangen, von der er sagt, dass sie nicht ausgelöst werden kann.

Diese Regel verursacht einige absurde Probleme. Zum Beispiel, eine der drei writeMethoden OutputStreamwird nicht außer Kraft gesetzt durch ByteArrayOutputStream. Insbesondere write(byte[] data)handelt es sich um eine bequeme Methode, mit der das gesamte Array durch Aufrufen write(byte[] data, int offset, int length)mit einem Offset von 0 und der Länge des Arrays geschrieben wird. ByteArrayOutputStreamüberschreibt die Drei-Argument-Methode, erbt jedoch die Ein-Argument-Convenience-Methode unverändert. Die geerbte Methode macht genau das Richtige, enthält jedoch eine unerwünschte throwsKlausel. Das war vielleicht ein Versehen im Design von ByteArrayOutputStream, aber sie können es nie beheben, weil es die Quellkompatibilität mit jedem Code unterbrechen würde, der die Ausnahme abfängt - die Ausnahme, die niemals, niemals und niemals ausgelöst wird!

Diese Regel ist auch beim Bearbeiten und Debuggen ärgerlich. Zum Beispiel werde ich manchmal einen Methodenaufruf vorübergehend auskommentieren, und wenn er eine aktivierte Ausnahme hätte auslösen können, beschwert sich der Compiler jetzt über die Existenz des lokalen Blocks tryund der catchBlöcke. Also muss ich diese auch auskommentieren, und jetzt, wenn der Code darin bearbeitet wird, wird die IDE auf die falsche Ebene eingerückt, weil die {und }auskommentiert sind. Gah! Es ist eine kleine Beschwerde, aber es scheint, dass das einzige, was geprüfte Ausnahmen jemals tun, Ärger verursacht.


Ich bin fast fertig. Meine letzte Enttäuschung über geprüfte Ausnahmen ist, dass Sie an den meisten Anrufseiten nichts Nützliches damit anfangen können. Wenn etwas schief geht, haben wir im Idealfall einen kompetenten anwendungsspezifischen Handler, der den Benutzer über das Problem informieren und / oder den Vorgang entsprechend beenden oder wiederholen kann. Dies kann nur ein Handler hoch oben im Stack tun, da dies der einzige ist, der das Gesamtziel kennt.

Stattdessen erhalten wir die folgende Redewendung, die weit verbreitet ist, um den Compiler zum Schweigen zu bringen:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

In einer GUI oder einem automatisierten Programm wird die gedruckte Nachricht nicht angezeigt. Schlimmer noch, es wird nach der Ausnahme mit dem Rest des Codes fortgesetzt. Ist die Ausnahme nicht wirklich ein Fehler? Dann drucke es nicht aus. Andernfalls wird in einem Moment etwas anderes explodieren. Zu diesem Zeitpunkt ist das ursprüngliche Ausnahmeobjekt verschwunden. Diese Redewendung ist nicht besser als die von BASIC On Error Resume Nextoder PHP error_reporting(0);.

Es ist nicht viel besser, eine Art Logger-Klasse aufzurufen:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

Das ist genauso faul wie e.printStackTrace();und pflügt immer noch mit Code in einem unbestimmten Zustand weiter. Außerdem ist die Auswahl eines bestimmten Protokollierungssystems oder eines anderen Handlers anwendungsspezifisch, sodass die Wiederverwendung von Code beeinträchtigt wird.

Aber warte! Es gibt eine einfache und universelle Möglichkeit, den anwendungsspezifischen Handler zu finden. Es befindet sich weiter oben im Aufrufstapel (oder es ist als nicht erfasster Ausnahmebehandler des Threads festgelegt ). In den meisten Fällen müssen Sie also nur die Ausnahme höher auf den Stapel werfen . ZB , throw e;. Überprüfte Ausnahmen stören nur.

Ich bin mir sicher, dass geprüfte Ausnahmen bei der Entwicklung der Sprache nach einer guten Idee klangen, aber in der Praxis habe ich festgestellt, dass sie alle störend sind und keinen Nutzen bringen.


Für Ihre Größenmethode mit der WidgetList würde ich die Größe in einer Variablen zwischenspeichern und im Konstruktor festlegen. Dem Konstruktor steht es frei, eine Ausnahme auszulösen. Dies funktioniert jedoch nicht, wenn sich die Datei während der Verwendung der WidgetList ändert, was wahrscheinlich schlecht wäre, wenn dies der Fall wäre.
TofuBeer

2
SomeStupidExceptionOmgWhoCares gut jemand kümmerte sich genug, um es zu werfen. Entweder sollte es nie geworfen worden sein (schlechtes Design) oder Sie sollten wirklich damit umgehen. Gleiches gilt für die schlechte Implementierung einer Klasse vor 1.0 (des Byte-Array-Ausgabestreams), bei der das Design leider schlecht war.
TofuBeer

2
Die richtige Redewendung wäre eine Direktive gewesen, die alle von verschachtelten Unterprogrammaufrufen ausgelösten Ausnahmen abfängt und sie in a umwickelt RuntimeException. Beachten Sie, dass eine Routine gleichzeitig als deklariert werden kann throws IOExceptionund dennoch angibt, dass jeder IOExceptionaus einem verschachtelten Aufruf ausgelöste Vorgang als unerwartet betrachtet und umbrochen werden soll.
Supercat

15
Ich bin ein professioneller C # -Entwickler mit Java-Erfahrung, der über diesen Beitrag gestolpert ist. Ich bin verblüfft, warum jemand dieses bizarre Verhalten unterstützen würde. Wenn ich in .NET eine bestimmte Art von Ausnahme abfangen möchte, kann ich diese abfangen. Wenn ich es einfach auf den Stapel werfen lassen möchte, gibt es nichts zu tun. Ich wünschte, Java wäre nicht so schrullig. :)
Aikeru

In Bezug auf "Manchmal werde ich einen Methodenaufruf vorübergehend auskommentieren" habe ich gelernt, if (false)dies zu verwenden. Es vermeidet das Problem mit der Wurfklausel und die Warnung hilft mir, schneller zurück zu navigieren. +++ Trotzdem stimme ich allem zu, was du geschrieben hast. Überprüfte Ausnahmen haben einen gewissen Wert, aber dieser Wert ist im Vergleich zu ihren Kosten vernachlässigbar. Fast immer stören sie nur.
Maaartinus

45

Nun, es geht nicht darum, einen Stacktrace anzuzeigen oder lautlos abzustürzen. Es geht darum, Fehler zwischen Ebenen kommunizieren zu können.

Das Problem bei geprüften Ausnahmen besteht darin, dass sie Personen dazu ermutigen, wichtige Details (nämlich die Ausnahmeklasse) zu schlucken. Wenn Sie dieses Detail nicht verschlucken möchten, müssen Sie weiterhin Wurfdeklarationen für Ihre gesamte App hinzufügen. Dies bedeutet 1) dass ein neuer Ausnahmetyp viele Funktionssignaturen beeinflusst und 2) Sie eine bestimmte Instanz der Ausnahme übersehen können, die Sie tatsächlich abfangen möchten (sagen wir, Sie öffnen eine sekundäre Datei für eine Funktion, die Daten in a schreibt Die sekundäre Datei ist optional, sodass Sie ihre Fehler ignorieren können. Aufgrund der Signatur throws IOExceptionist dies jedoch leicht zu übersehen.

Ich beschäftige mich jetzt tatsächlich mit dieser Situation in einer Anwendung. Wir haben fast Ausnahmen als AppSpecificException neu verpackt. Dies machte die Signaturen wirklich sauber und wir mussten uns keine Sorgen machen, dass die throwsSignaturen explodieren .

Natürlich müssen wir uns jetzt auf die Fehlerbehandlung auf den höheren Ebenen spezialisieren und Wiederholungslogik und dergleichen implementieren. Alles ist jedoch AppSpecificException, daher können wir nicht sagen: "Wenn eine IOException ausgelöst wird, wiederholen Sie den Vorgang" oder "Wenn ClassNotFound ausgelöst wird, brechen Sie den Vorgang vollständig ab". Wir haben keine zuverlässige Möglichkeit, zur eigentlichen Ausnahme zu gelangen, da die Dinge immer wieder neu verpackt werden, wenn sie zwischen unserem Code und dem Code von Drittanbietern ausgetauscht werden.

Aus diesem Grund bin ich ein großer Fan der Ausnahmebehandlung in Python. Sie können nur die Dinge fangen, die Sie wollen und / oder handhaben können. Alles andere sprudelt, als ob Sie es selbst zurückschreiben würden (was Sie sowieso getan haben).

Ich habe immer wieder und während des gesamten von mir erwähnten Projekts festgestellt, dass die Ausnahmebehandlung in drei Kategorien unterteilt ist:

  1. Eine bestimmte Ausnahme abfangen und behandeln . Dies dient zum Beispiel zur Implementierung einer Wiederholungslogik.
  2. Andere Ausnahmen abfangen und erneut auslösen . Alles, was hier passiert, ist normalerweise die Protokollierung, und es ist normalerweise eine banale Nachricht wie "$ filename kann nicht geöffnet werden". Dies sind Fehler, gegen die Sie nichts unternehmen können. Nur ein höheres Level weiß genug, um damit umzugehen.
  3. Fangen Sie alles ab und zeigen Sie eine Fehlermeldung an. Dies ist normalerweise die Wurzel eines Dispatchers und stellt lediglich sicher, dass er den Fehler über einen Nicht-Ausnahmemechanismus (Popup-Dialog, Marshalling eines RPC-Fehlerobjekts usw.) an den Anrufer weiterleiten kann.

5
Sie hätten bestimmte Unterklassen von AppSpecificException erstellen können, um eine Trennung unter Beibehaltung der einfachen Methodensignaturen zu ermöglichen.
Thorbjørn Ravn Andersen

1
Eine sehr wichtige Ergänzung zu Punkt 2 ist auch, dass Sie der abgefangenen Ausnahme INFORMATIONEN HINZUFÜGEN können (z. B. durch Verschachteln in einer RuntimeException). Es ist viel, viel besser, den Namen der Datei nicht im Stack-Trace zu finden, als ihn tief in einer Protokolldatei zu verbergen.
Thorbjørn Ravn Andersen

1
Grundsätzlich lautet Ihr Argument: "Das Verwalten von Ausnahmen ist anstrengend, daher möchte ich mich lieber nicht damit befassen." Wenn die Ausnahme sprudelt, verliert sie an Bedeutung und das Erstellen von Kontexten ist praktisch nutzlos. Als Designer einer API, die Sie festlegen sollten, ist vertraglich klar, was zu erwarten ist, wenn etwas schief geht. Wenn mein Programm abstürzt, weil ich nicht darüber informiert wurde, dass diese oder jene Ausnahme "sprudeln" kann, sind Sie als Designer gescheitert und als Aufgrund Ihres Ausfalls ist mein System nicht so stabil wie es sein kann.
Newtopian

4
Das sage ich überhaupt nicht. Dein letzter Satz stimmt tatsächlich mit mir überein. Wenn alles in AppSpecificException eingeschlossen ist, sprudelt es nicht (und Bedeutung / Kontext geht verloren), und ja, der API-Client wird nicht informiert - genau das passiert mit aktivierten Ausnahmen (wie in Java). , weil die Leute sich nicht mit Funktionen mit vielen throwsDeklarationen befassen wollen .
Richard Levasseur

6
@Newtopian - Ausnahmen können größtenteils nur auf der Ebene "Geschäft" oder "Anfrage" behandelt werden. Es ist sinnvoll, bei großer Granularität zu scheitern oder es erneut zu versuchen, nicht bei jedem winzigen potenziellen Fehler. Aus diesem Grund wird die Best Practice für die Ausnahmebehandlung als "früh werfen, spät fangen" zusammengefasst. Checked Ausnahmen machen es schwieriger , die Zuverlässigkeit auf der richtigen Ebene zu verwalten und zu ermutigen , eine große Zahl von Fehl codierten catch - Blöcke. literatejava.com/exceptions/…
Thomas W

24

SNR

Erstens verringern geprüfte Ausnahmen das "Signal-Rausch-Verhältnis" für den Code. Anders Hejlsberg spricht auch über imperative vs. deklarative Programmierung, was ein ähnliches Konzept ist. Beachten Sie auf jeden Fall die folgenden Codefragmente:

Aktualisieren Sie die Benutzeroberfläche von einem Nicht-Benutzeroberflächenthread in Java:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

Aktualisieren Sie die Benutzeroberfläche von einem Nicht-Benutzeroberflächenthread in C #:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

Was mir viel klarer erscheint. Wenn Sie anfangen, mehr und mehr UI-Arbeiten in Swing durchzuführen, werden geprüfte Ausnahmen wirklich nervig und nutzlos.

Ausbruch aus dem Gefängnis

Um selbst die grundlegendsten Implementierungen wie die List-Oberfläche von Java zu implementieren, fallen geprüfte Ausnahmen als Werkzeug für das vertragliche Design aus. Stellen Sie sich eine Liste vor, die von einer Datenbank, einem Dateisystem oder einer anderen Implementierung unterstützt wird, die eine aktivierte Ausnahme auslöst. Die einzig mögliche Implementierung besteht darin, die aktivierte Ausnahme abzufangen und als nicht aktivierte Ausnahme erneut auszulösen:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

Und jetzt müssen Sie sich fragen, worum es bei all dem Code geht. Die geprüften Ausnahmen fügen nur Rauschen hinzu, die Ausnahme wurde abgefangen, aber nicht behandelt, und das vertragliche Design (in Bezug auf geprüfte Ausnahmen) ist zusammengebrochen.

Fazit

  • Das Abfangen von Ausnahmen unterscheidet sich vom Behandeln.
  • Aktivierte Ausnahmen fügen dem Code Rauschen hinzu.
  • Die Ausnahmebehandlung funktioniert in C # ohne sie gut.

Ich habe vorher darüber gebloggt .


3
Im Beispiel lassen sowohl Java als auch C # die Ausnahmen nur weitergeben, ohne sie zu behandeln (Java über IllegalStateException). Der Unterschied besteht darin, dass Sie möglicherweise eine FileNotFoundException behandeln möchten, es jedoch unwahrscheinlich ist, dass die Behandlung von InvocationTargetException oder InterruptedException hilfreich ist.
Luke Quinane

3
Und woher weiß ich in C #, dass die E / A-Ausnahme auftreten kann? Außerdem würde ich niemals eine Ausnahme vom Ausführen auslösen ... Ich halte das für eine missbräuchliche Ausnahmebehandlung. Entschuldigung, aber für diesen Teil Ihres Codes kann ich Ihre Seite noch sehen.
TofuBeer

7
Wir kommen dorthin :-) Also müssen Sie bei jeder neuen Version einer API alle Ihre Aufrufe durchkämmen und nach neuen Ausnahmen suchen, die auftreten könnten? Dies kann bei unternehmensinternen APIs leicht passieren, da sie sich nicht um die Abwärtskompatibilität kümmern müssen.
TofuBeer

3
Meinten Sie das Signal-Rausch-Verhältnis verringern ?
neo2862

3
@TofuBeer Ist es nicht gut, Ihren Code zu aktualisieren, nachdem sich die Oberfläche einer zugrunde liegenden API geändert hat? Wenn Sie dort nur ungeprüfte Ausnahmen gehabt hätten, hätten Sie ein kaputtes / unvollständiges Programm erhalten, ohne es zu wissen.
Francois Bourgeois

23

Artima veröffentlichte ein Interview mit einem der Architekten von .NET, Anders Hejlsberg, in dem die Argumente gegen geprüfte Ausnahmen genau behandelt werden. Ein kurzer Vorgeschmack:

Die Throws-Klausel, zumindest die Art und Weise, wie sie in Java implementiert ist, zwingt Sie nicht unbedingt dazu, die Ausnahmen zu behandeln. Wenn Sie sie jedoch nicht behandeln, müssen Sie genau erkennen, welche Ausnahmen möglicherweise durchlaufen werden. Sie müssen entweder deklarierte Ausnahmen abfangen oder in Ihre eigene Throws-Klausel einfügen. Um diese Anforderung zu umgehen, machen die Leute lächerliche Dinge. Zum Beispiel dekorieren sie jede Methode mit "löst eine Ausnahme aus". Das macht die Funktion einfach komplett zunichte, und Sie haben den Programmierer dazu gebracht, mehr verschlungenes Zeug zu schreiben. Das hilft niemandem.


2
In der Tat der Chefarchitekt.
Falle

18
Ich habe das gelesen, für mich läuft sein Argument darauf hinaus, "es gibt schlechte Programmierer da draußen".
TofuBeer

6
TofuBeer, überhaupt nicht. Der springende Punkt ist, dass Sie oft nicht wissen, was Sie mit der Ausnahme tun sollen, die die aufgerufene Methode auslöst, und dass der Fall, an dem Sie wirklich interessiert sind, nicht einmal erwähnt wird. Sie öffnen eine Datei, Sie erhalten zum Beispiel eine E / A-Ausnahme ... das ist nicht mein Problem, also werfe ich sie auf. Die aufrufende Methode der obersten Ebene möchte jedoch nur die Verarbeitung beenden und den Benutzer darüber informieren, dass ein unbekanntes Problem vorliegt. Die aktivierte Ausnahme hat überhaupt nicht geholfen. Es war eines von einer Million seltsamen Dingen, die passieren können.
Dan Rosenstark

4
@yar, wenn Ihnen die aktivierte Ausnahme nicht gefällt, führen Sie eine "Neue RuntimeException auslösen" ("Wir haben dies bei Foo.bar () nicht erwartet", e) "aus und machen Sie damit fertig.
Thorbjørn Ravn Andersen

4
@ ThorbjørnRavnAndersen: Eine grundlegende Designschwäche in Java, die .net leider kopiert hat, besteht darin, dass der Typ einer Ausnahme sowohl als primäres Mittel zur Entscheidung, ob darauf reagiert werden soll, als auch als primäres Mittel zur Angabe des allgemeinen Typs der Sache verwendet wird das ging schief, obwohl die beiden Probleme tatsächlich weitgehend orthogonal sind. Was zählt, ist nicht, was schief gelaufen ist, sondern welche Statusobjekte sich befinden. Außerdem gehen sowohl .net als auch Java standardmäßig davon aus, dass das Behandeln und Lösen einer Ausnahme im Allgemeinen dasselbe ist, obwohl sie häufig unterschiedlich sind.
Supercat

20

Ich stimmte Ihnen zunächst zu, da ich immer für geprüfte Ausnahmen war, und begann darüber nachzudenken, warum ich es nicht mag, Ausnahmen in .Net nicht geprüft zu haben. Aber dann wurde mir klar, dass ich nicht wie geprüfte Ausnahmen infektiere.

Um Ihre Frage zu beantworten: Ja, ich möchte, dass meine Programme Stapelspuren anzeigen, vorzugsweise wirklich hässliche. Ich möchte, dass die Anwendung in einen schrecklichen Haufen der hässlichsten Fehlermeldungen explodiert, die Sie jemals sehen möchten.

Und der Grund ist, dass ich es reparieren muss, wenn es das tut, und ich muss es sofort reparieren. Ich möchte sofort wissen, dass es ein Problem gibt.

Wie oft behandeln Sie tatsächlich Ausnahmen? Ich spreche nicht davon, Ausnahmen zu fangen - ich spreche davon, sie zu behandeln? Es ist zu einfach, Folgendes zu schreiben:

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

Und ich weiß, dass man sagen kann, dass es eine schlechte Praxis ist und dass die Antwort darin besteht, etwas mit der Ausnahme zu tun (lassen Sie mich raten, protokollieren?), Aber in der realen Welt (tm) tun die meisten Programmierer dies einfach nicht es.

Also ja, ich möchte keine Ausnahmen abfangen, wenn ich das nicht muss, und ich möchte, dass mein Programm spektakulär explodiert, wenn ich es vermassle. Stilles Scheitern ist das schlechteste Ergebnis.


Java empfiehlt Ihnen, dies zu tun, damit Sie nicht jeder Methodensignatur jede Art von Ausnahme hinzufügen müssen.
Yfeldblum

17
Witzig ... seit ich geprüfte Ausnahmen richtig angenommen und angemessen verwendet habe, haben meine Programme aufgehört, in einem riesigen, dampfenden Haufen von Kundenunzufriedenheit in die Luft zu jagen. Wenn Sie während der Entwicklung eine große, hässliche, schlechte Stapelverfolgung haben, muss der Kunde diese ebenfalls erhalten. Ich mag es, sein Gesicht zu sehen, wenn er ArrayIndexOutOfBoundsException mit einem meilenhohen Stack-Trace auf seinem abgestürzten System sieht, anstatt einer kleinen Tray-Benachrichtigung, die besagt, dass die Farbkonfiguration für die Schaltfläche XYZ nicht analysiert werden konnte, sodass die Standardeinstellung verwendet wurde, während die Software glücklich summte entlang
Newtopian

1
Möglicherweise benötigt Java eine "cantHandle" -Deklaration, die angibt, dass eine Methode oder ein Try / Catch-Codeblock nicht darauf vorbereitet ist, eine bestimmte darin auftretende Ausnahme zu behandeln, und dass eine solche Ausnahme über andere Mittel als eine explizite auftritt throw innerhalb dieser Methode (im Gegensatz zu einer aufgerufenen Methode) sollte automatisch in eine RuntimeException eingeschlossen und erneut geworfen werden. IMHO sollten geprüfte Ausnahmen den Aufrufstapel selten weitergeben , ohne umbrochen zu werden.
Supercat

3
@Newtopian - Ich schreibe Server- und hochzuverlässige Software und mache das seit 25 Jahren. Meine Programme sind nie in die Luft gesprengt worden, und ich arbeite mit hochverfügbaren, auf Integration und Wiederverbindung basierenden, integrationsbasierten Finanz- und Militärsystemen. Ich habe eine absolut objektive Basis, um Laufzeitausnahmen zu bevorzugen. Überprüfte Ausnahmen erschweren das Befolgen der korrekten Best Practice "Früh werfen, spät fangen". Die korrekte Zuverlässigkeit und Fehlerbehandlung erfolgt auf der Ebene "Geschäft", "Verbindung" oder "Anforderung". (Oder gelegentlich beim Parsen von Daten). Überprüfte Ausnahmen behindern es, es richtig zu machen.
Thomas W

1
Die Ausnahmen, über die Sie hier sprechen, sind in RuntimeExceptionsder Tat, die Sie nicht fangen müssen, und ich stimme zu, dass Sie das Programm in die Luft jagen lassen sollten. Die Ausnahmen, die Sie immer abfangen und behandeln sollten, sind die aktivierten Ausnahmen wie IOException. Wenn Sie eine erhalten IOException, gibt es nichts in Ihrem Code zu beheben; Ihr Programm sollte nicht explodieren, nur weil es einen Netzwerk-Schluckauf gab.
AxiomaticNexus

20

Der Artikel Effektive Java-Ausnahmen erklärt ausführlich, wann nicht aktivierte und wann aktivierte Ausnahmen verwendet werden sollen. Hier sind einige Zitate aus diesem Artikel, um die wichtigsten Punkte hervorzuheben:

Kontingenz: Eine erwartete Bedingung, die eine alternative Antwort von einer Methode erfordert, die sich in Bezug auf den beabsichtigten Zweck der Methode ausdrücken lässt. Der Aufrufer der Methode erwartet diese Art von Bedingungen und hat eine Strategie, um mit ihnen umzugehen.

Fehler: Ein ungeplanter Zustand, der verhindert, dass eine Methode ihren beabsichtigten Zweck erreicht, der nicht ohne Bezugnahme auf die interne Implementierung der Methode beschrieben werden kann.

(SO erlaubt keine Tabellen, daher möchten Sie vielleicht Folgendes von der Originalseite lesen ...)

Kontingenz

  • Wird betrachtet als: Ein Teil des Designs
  • Wird voraussichtlich passieren: Regelmäßig aber selten
  • Wen interessiert das? Der Upstream-Code, der die Methode aufruft
  • Beispiele: Alternative Rückgabemodi
  • Beste Zuordnung: Eine aktivierte Ausnahme

Fehler

  • Wird als: eine böse Überraschung angesehen
  • Wird voraussichtlich passieren: Niemals
  • Wen interessiert das? Die Leute, die das Problem beheben müssen
  • Beispiele: Programmierfehler, Hardwarefehler, Konfigurationsfehler, fehlende Dateien, nicht verfügbare Server
  • Beste Zuordnung: Eine nicht aktivierte Ausnahme

Ich weiß, wann ich sie verwenden soll, ich möchte wissen, warum Leute, die diesen Rat nicht befolgen ... diesen Rat nicht befolgen :-)
TofuBeer

Was sind Programmierfehler und wie können sie von Verwendungsfehlern unterschieden werden ? Ist es ein Programmierfehler, wenn der Benutzer die falschen Argumente an das Programm übergibt? Aus Java-Sicht ist es möglicherweise kein Programmierfehler, aber aus Sicht des Shell-Skripts ist es ein Programmierfehler. Worin bestehen also ungültige Argumente args[]? Sind sie ein Zufall oder ein Fehler?
Ceving

3
@TofuBeer - Da die Java-Bibliotheksdesigner alle Arten von nicht behebbaren Fehlern auf niedriger Ebene als geprüfte Ausnahmen festgelegt haben, sollten sie eindeutig deaktiviert sein . FileNotFound ist die einzige IOException, die beispielsweise überprüft werden sollte. In Bezug auf JDBC - nur eine Verbindung zur Datenbank - kann vernünftigerweise als Eventualfall angesehen werden . Alle anderen SQLExceptions hätte sein sollen Ausfälle und unkontrolliert. Die Fehlerbehandlung sollte korrekt auf der Ebene "Geschäft" oder "Anforderung" erfolgen. Weitere Informationen finden Sie in der Best Practice "Früh werfen, spät fangen". Überprüfte Ausnahmen sind ein Hindernis dafür.
Thomas W

1
Es gibt einen einzigen großen Fehler in Ihrer Argumentation. "Kontingenz" darf NICHT durch Ausnahmen behandelt werden, sondern durch Rückgabewerte für Geschäftscode und Methoden. Ausnahmen sind, wie das Wort sagt, AUSSERGEWÖHNLICHE Situationen, also Fehler.
Matteo Mosca

@MatteoMosca Fehlerrückgabecodes werden in der Regel ignoriert und reichen aus, um sie zu disqualifizieren. Tatsächlich kann alles Ungewöhnliche oft nur irgendwo oben im Stapel behandelt werden, und das ist ein Anwendungsfall für Ausnahmen. Ich könnte mir so etwas wie eine File#openInputStreamRückkehr vorstellen Either<InputStream, Problem>- wenn Sie das meinen, dann sind wir uns vielleicht einig.
Maaartinus

19

Zusamenfassend:

Ausnahmen sind eine API-Entwurfsfrage. -- Nicht mehr und nicht weniger.

Das Argument für geprüfte Ausnahmen:

Um zu verstehen, warum geprüfte Ausnahmen möglicherweise nicht gut sind, drehen wir die Frage um und fragen: Wann oder warum sind geprüfte Ausnahmen attraktiv, dh warum soll der Compiler die Deklaration von Ausnahmen erzwingen?

Die Antwort ist klar: Manchmal Sie brauchen , um eine Ausnahme zu fangen, und das ist nur möglich , wenn der Code bietet eine spezielle Ausnahmeklasse für den Fehler aufgerufen wird , dass Sie interessiert sind.

Daher ist das Argument für geprüfte Ausnahmen, dass der Compiler Programmierer zwingt, zu deklarieren, welche Ausnahmen ausgelöst werden, und der Programmierer dann hoffentlich auch bestimmte Ausnahmeklassen und die Fehler, die sie verursachen, dokumentiert.

In der Realität com.acmewirft ein Paket jedoch immer zu oft nur eine AcmeExceptionbestimmte Unterklasse aus. Anrufer müssen dann verarbeiten, deklarieren oder erneut signalisieren AcmeExceptions, können jedoch immer noch nicht sicher sein, ob ein AcmeFileNotFoundErrorEreignis aufgetreten ist oder ein Ereignis aufgetreten ist AcmePermissionDeniedError.

Wenn Sie also nur an einem interessiert sind AcmeFileNotFoundError, besteht die Lösung darin, eine Funktionsanforderung bei den ACME-Programmierern einzureichen und diese anzuweisen, diese Unterklasse von zu implementieren, zu deklarieren und zu dokumentieren AcmeException.

Wieso sich die Mühe machen?

Daher kann der Compiler selbst bei aktivierten Ausnahmen Programmierer nicht zwingen, nützliche Ausnahmen auszulösen . Es ist immer noch nur eine Frage der Qualität der API.

Infolgedessen schneiden Sprachen ohne geprüfte Ausnahmen normalerweise nicht viel schlechter ab. Programmierer könnten versucht sein, unspezifische Instanzen einer allgemeinen ErrorKlasse anstatt einer zu werfen AcmeException, aber wenn sie sich überhaupt um ihre API-Qualität kümmern, werden sie lernen, eine einzuführen AcmeFileNotFoundError.

Insgesamt unterscheidet sich die Spezifikation und Dokumentation von Ausnahmen nicht wesentlich von der Spezifikation und Dokumentation beispielsweise gewöhnlicher Methoden. Auch dies ist eine API-Entwurfsfrage. Wenn ein Programmierer vergessen hat, eine nützliche Funktion zu implementieren oder zu exportieren, muss die API verbessert werden, damit Sie sinnvoll damit arbeiten können.

Wenn Sie dieser Argumentation folgen, sollte es offensichtlich sein, dass der in Sprachen wie Java so übliche "Aufwand", Ausnahmen zu deklarieren, abzufangen und erneut auszulösen, oft wenig Wert hinzufügt.

Es ist auch erwähnenswert, dass der Java - VM hat nicht Ausnahmen überprüft hat - nur die Java - Compiler prüft sie und Klassendateien mit geändertener Ausnahme Erklärungen sind zur Laufzeit kompatibel. Die Java VM-Sicherheit wird nicht durch aktivierte Ausnahmen verbessert, sondern nur durch den Codierungsstil.


4
Ihr Argument spricht gegen sich selbst. Wenn "manchmal müssen Sie eine Ausnahme abfangen" und die API-Qualität häufig schlecht ist, wissen Sie ohne aktivierte Ausnahmen nicht, ob der Designer es versäumt hat zu dokumentieren, dass eine bestimmte Methode eine Ausnahme auslöst, die abgefangen werden muss. Verbinden Sie das AcmeExceptioneher mit Werfen als AcmeFileNotFoundErrormit viel Glück, um herauszufinden, was Sie falsch gemacht haben und wo Sie es fangen müssen. Aktivierte Ausnahmen bieten Programmierern einen Mindestschutz gegen schlechtes API-Design.
Eva

1
Das Design der Java-Bibliothek hat schwerwiegende Fehler gemacht. "Überprüfte Ausnahmen" betrafen vorhersehbare und wiederherstellbare Eventualitäten - z. B. Datei nicht gefunden, Verbindungsfehler. Sie waren niemals für ein Systemversagen auf niedriger Ebene gedacht oder geeignet. Es wäre in Ordnung gewesen, das Öffnen einer zu überprüfenden Datei zu erzwingen, aber es gibt keine sinnvolle Wiederholung oder Wiederherstellung, wenn kein einzelnes Byte geschrieben / eine SQL-Abfrage usw. nicht ausgeführt werden kann. Wiederholung oder Wiederherstellung werden bei "business" oder "request" korrekt behandelt "Ebene, die Ausnahmen überprüft sinnlos schwierig machen. literatejava.com/exceptions/…
Thomas W

17

Ich habe in den letzten drei Jahren mit mehreren Entwicklern in relativ komplexen Anwendungen gearbeitet. Wir haben eine Codebasis, die häufig überprüfte Ausnahmen mit korrekter Fehlerbehandlung verwendet, und eine andere, die dies nicht tut.

Bisher habe ich es einfacher gefunden, mit der Codebasis mit Checked Exceptions zu arbeiten. Wenn ich die API einer anderen Person verwende, ist es schön, dass ich genau sehen kann, welche Art von Fehlerbedingungen ich erwarten kann, wenn ich den Code aufrufe und sie richtig behandle, entweder durch Protokollieren, Anzeigen oder Ignorieren (Ja, es gibt gültige Fälle zum Ignorieren Ausnahmen (z. B. eine ClassLoader-Implementierung). Das gibt dem Code, den ich schreibe, die Möglichkeit, ihn wiederherzustellen. Alle Laufzeitausnahmen, die ich weitergebe, bis sie zwischengespeichert und mit einem generischen Fehlerbehandlungscode behandelt werden. Wenn ich eine aktivierte Ausnahme finde, die ich auf einer bestimmten Ebene nicht wirklich behandeln möchte oder die einen Programmierlogikfehler berücksichtigt, verpacke ich sie in eine RuntimeException und lasse sie aufblasen. Schlucken Sie niemals eine Ausnahme ohne guten Grund (und gute Gründe dafür sind eher selten).

Wenn ich mit der Codebasis arbeite, die keine Ausnahmen überprüft hat, ist es für mich etwas schwieriger, vorher zu wissen, was ich beim Aufrufen der Funktion erwarten kann, was einige Dinge schrecklich beschädigen kann.

Dies alles ist natürlich eine Frage der Präferenz und der Entwicklerfähigkeit. Beide Arten der Programmierung und Fehlerbehandlung können gleichermaßen effektiv (oder nicht effektiv) sein, daher würde ich nicht sagen, dass es The One Way gibt.

Alles in allem fällt es mir leichter, mit Checked Exceptions zu arbeiten, insbesondere bei großen Projekten mit vielen Entwicklern.


6
Ich mache zu. Für mich sind sie ein wesentlicher Bestandteil eines Vertrages. Ohne auf die API-Dokumentation näher eingehen zu müssen, kann ich schnell die wahrscheinlichsten Fehlerszenarien erkennen.
Wayne Hartman

2
Zustimmen. Ich habe einmal die Notwendigkeit von geprüften Ausnahmen in .Net erlebt, als ich versucht habe, Netzwerkanrufe zu tätigen. Da ich wusste, dass jederzeit ein Netzwerkproblem auftreten kann, musste ich die gesamte Dokumentation der APIs durchlesen, um herauszufinden, welche Ausnahme ich speziell für dieses Szenario abfangen musste. Wenn C # Ausnahmen überprüft hätte, hätte ich es sofort gewusst. Andere C # -Entwickler würden die App wahrscheinlich nur aufgrund eines einfachen Netzwerkfehlers abstürzen lassen.
AxiomaticNexus

13

Ausnahmekategorien

Wenn ich über Ausnahmen spreche, verweise ich immer auf Eric Lipperts Blog-Artikel über Vexing-Ausnahmen . Er ordnet Ausnahmen in folgende Kategorien ein:

  • Tödlich - Diese Ausnahmen sind nicht Ihre Schuld : Sie können dies nicht verhindern und Sie können nicht vernünftig damit umgehen. Zum Beispiel OutOfMemoryErroroder ThreadAbortException.
  • Boneheaded - Diese Ausnahmen sind Ihre Schuld : Sie hätten sie verhindern sollen, und sie stellen Fehler in Ihrem Code dar. Zum Beispiel ArrayIndexOutOfBoundsException, NullPointerExceptionoder jeder IllegalArgumentException.
  • Ärgerlich - Diese Ausnahmen sind keine Ausnahme , nicht Ihre Schuld, Sie können sie nicht verhindern, aber Sie müssen sich mit ihnen befassen. Sie sind häufig das Ergebnis einer unglücklichen Entwurfsentscheidung, z. B. das Werfen NumberFormatExceptionvon, Integer.parseIntanstatt eine Integer.tryParseIntMethode bereitzustellen, die bei einem Analysefehler einen booleschen Wert false zurückgibt.
  • Exogen - Diese Ausnahmen sind normalerweise außergewöhnlich , nicht Ihre Schuld. Sie können sie nicht (vernünftigerweise) verhindern, aber Sie müssen damit umgehen . Zum Beispiel FileNotFoundException.

Ein API-Benutzer:

  • darf nicht handhaben tödlich oder boneheaded Ausnahmen.
  • sollte behandeln ärgerliche Ausnahmen, aber sie sollten nicht in einer idealen API auftreten.
  • muss mit exogenen Ausnahmen umgehen .

Überprüfte Ausnahmen

Die Tatsache , dass die API Benutzer muss eine bestimmte Ausnahme behandeln ist Teil des Vertrages des Verfahrens zwischen dem Anrufer und dem Angerufenen. Der Vertrag legt unter anderem fest: Anzahl und Art der Argumente, die der Angerufene erwartet, die Art des Rückgabewerts, den der Anrufer erwarten kann, und die Ausnahmen, die der Anrufer voraussichtlich behandeln wird .

Da in einer API keine störenden Ausnahmen vorhanden sein sollten, müssen nur diese exogenen Ausnahmen überprüft werden , um Teil des Vertrags der Methode zu sein. Relativ wenige Ausnahmen sind exogen , daher sollte jede API relativ wenige geprüfte Ausnahmen haben.

Eine aktivierte Ausnahme ist eine Ausnahme, die behandelt werden muss . Die Behandlung einer Ausnahme kann so einfach sein wie das Verschlucken. Dort! Die Ausnahme wird behandelt. Zeitraum. Wenn der Entwickler so damit umgehen möchte, ist das in Ordnung. Aber er kann die Ausnahme nicht ignorieren und wurde gewarnt.

API-Probleme

Jede API, die ärgerliche und schwerwiegende Ausnahmen überprüft hat (z. B. die JCL), belastet die API-Benutzer jedoch unnötig. Solche Ausnahmen müssen behandelt werden, aber entweder ist die Ausnahme so häufig, dass sie überhaupt keine Ausnahme gewesen sein sollte, oder es kann nichts unternommen werden, wenn sie behandelt wird. Und dies führt dazu, dass Java-Entwickler geprüfte Ausnahmen hassen.

Außerdem haben viele APIs keine ordnungsgemäße Ausnahmeklassenhierarchie, wodurch alle Arten von nicht exogenen Ausnahmeursachen durch eine einzelne geprüfte Ausnahmeklasse dargestellt werden (z IOException. B. ). Dies führt auch dazu, dass Java-Entwickler geprüfte Ausnahmen hassen.

Fazit

Exogene Ausnahmen sind solche, die nicht Ihre Schuld sind, nicht hätten verhindert werden können und die behandelt werden sollten. Diese bilden eine kleine Teilmenge aller Ausnahmen, die ausgelöst werden können. APIs sollten nur exogene Ausnahmen überprüft und alle anderen Ausnahmen deaktiviert haben. Dies führt zu besseren APIs, entlastet den API-Benutzer weniger und verringert daher die Notwendigkeit, alle zu fangen, ungeprüfte Ausnahmen zu verschlucken oder erneut zu werfen.

Hassen Sie also Java und seine überprüften Ausnahmen nicht. Hassen Sie stattdessen die APIs, die geprüfte Ausnahmen überbeanspruchen.


Und missbrauchen Sie sie, indem Sie keine Hierarchie haben.
TofuBeer

1
FileNotFound und das Herstellen einer JDBC / Netzwerkverbindung sind Eventualitäten und korrekte Ausnahmen, da diese vorhersehbar und (möglicherweise) wiederherstellbar sind. Die meisten anderen IOExceptions, SQLExceptions, RemoteException usw. sind unvorhersehbare und nicht behebbare Fehler und sollten Laufzeitausnahmen gewesen sein. Aufgrund des fehlerhaften Designs der Java-Bibliothek wurden wir alle mit diesem Fehler konfrontiert und verwenden jetzt hauptsächlich Spring & Hibernate (die das Design richtig gemacht haben).
Thomas W

Normalerweise sollten Sie Ausnahmen ohne Kopf behandeln, obwohl Sie es möglicherweise nicht als "Behandlung" bezeichnen möchten. Zum Beispiel protokolliere ich sie auf einem Webserver und zeige dem Benutzer 500 an. Da die Ausnahme unerwartet ist, kann ich vor der Fehlerbehebung fast alles tun.
Maaartinus

6

Ok ... Überprüfte Ausnahmen sind nicht ideal und haben einige Einschränkungen, aber sie erfüllen einen Zweck. Beim Erstellen einer API gibt es bestimmte Fälle von Fehlern, die vertraglich für diese API gelten. Wenn im Kontext einer stark statisch typisierten Sprache wie Java keine geprüften Ausnahmen verwendet werden, muss man sich auf Ad-hoc-Dokumentation und Konventionen verlassen, um die Möglichkeit von Fehlern zu vermitteln. Dadurch werden alle Vorteile beseitigt, die der Compiler bei der Behandlung von Fehlern mit sich bringen kann, und Sie sind vollständig dem guten Willen der Programmierer überlassen.

Man entfernt also die geprüfte Ausnahme, wie sie in C # gemacht wurde. Wie kann man dann programmgesteuert und strukturell die Möglichkeit eines Fehlers vermitteln? Wie kann der Client-Code darüber informiert werden, dass solche und solche Fehler auftreten können und behandelt werden müssen?

Ich höre alle möglichen Schrecken, wenn es um geprüfte Ausnahmen geht. Sie werden missbraucht. Dies ist sicher, aber auch ungeprüfte Ausnahmen. Ich sage, warten Sie ein paar Jahre, wenn APIs viele Schichten tief gestapelt sind und Sie um die Rückkehr eines strukturierten Mittels bitten, um Fehler zu übermitteln.

Nehmen wir den Fall, als die Ausnahme irgendwo unten in den API-Ebenen ausgelöst wurde und nur in die Luft sprudelte, weil niemand wusste, dass dieser Fehler überhaupt auftreten konnte, obwohl es sich um eine Art Fehler handelte, der beim Aufrufen des Codes sehr plausibel war warf es (FileNotFoundException zum Beispiel im Gegensatz zu VogonsTrashingEarthExcept ... in diesem Fall wäre es egal, ob wir damit umgehen oder nicht, da nichts mehr übrig ist, um damit umzugehen).

Viele haben argumentiert, dass das Nichtladen der Datei fast immer das Ende der Welt für den Prozess war und einen schrecklichen und schmerzhaften Tod bedeuten muss. Also ja ... sicher ... ok ... Sie erstellen eine API für etwas und sie lädt irgendwann eine Datei ... Ich als Benutzer dieser API kann nur antworten ... "Wer zum Teufel bist du, um zu entscheiden, wann meine Programm sollte abstürzen! " Sicher Angesichts der Wahl, wo Ausnahmen verschlungen werden und keine Spuren hinterlassen, oder der EletroFlabbingChunkFluxManifoldChuggingException mit einer Stapelspur, die tiefer als der Marianna-Graben liegt, würde ich letztere ohne zu zögern nehmen, aber bedeutet dies, dass dies der wünschenswerte Weg ist, mit Ausnahmen umzugehen ? Können wir uns nicht irgendwo in der Mitte befinden, wo die Ausnahme jedes Mal neu gefasst und verpackt wird, wenn sie in eine neue Abstraktionsebene übergeht, sodass sie tatsächlich etwas bedeutet?

Schließlich lautet das meiste Argument, das ich sehe, "Ich möchte mich nicht mit Ausnahmen befassen, viele Menschen möchten sich nicht mit Ausnahmen befassen. Überprüfte Ausnahmen zwingen mich, mit ihnen umzugehen, daher hasse ich überprüfte Ausnahmen." Um einen solchen Mechanismus insgesamt zu beseitigen und es in den Abgrund der Hölle zu verbannen ist einfach albern und es fehlt ihm an Jugement und Vision.

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


2
Überprüfte Ausnahmen wären nützlich, wenn es ein deklaratives Mittel gäbe, um zu sagen, dass von keinem der Methodenaufrufe innerhalb eines Blocks erwartet wird, dass sie einige (oder mehrere) geprüfte Ausnahmen auslösen, und solche Ausnahmen sollten automatisch umbrochen und erneut ausgelöst werden. Sie könnten sogar noch nützlicher sein, wenn Aufrufe von Methoden, die als ausgelöste geprüfte Ausnahmen deklariert wurden, die Anrufgeschwindigkeit / -rückgabe gegen die Geschwindigkeit der Ausnahmebehandlung austauschen (sodass erwartete Ausnahmen fast so schnell wie der normale Programmablauf behandelt werden könnten). Derzeit gilt jedoch keine der beiden Situationen.
Supercat

6

In der Tat erhöhen geprüfte Ausnahmen einerseits die Robustheit und Korrektheit Ihres Programms (Sie müssen korrekte Deklarationen Ihrer Schnittstellen abgeben - die Ausnahmen, die eine Methode auslöst, sind im Grunde genommen ein spezieller Rückgabetyp). Auf der anderen Seite haben Sie das Problem, dass Sie, da Ausnahmen "aufblasen", sehr oft eine ganze Reihe von Methoden ändern müssen (alle Anrufer und die Anrufer der Anrufer usw.), wenn Sie die Ausnahmen ändern Methode wirft.

Überprüfte Ausnahmen in Java lösen das letztere Problem nicht. C # und VB.NET werfen das Baby mit dem Badewasser weg.

Ein guter Ansatz, der den Mittelweg nimmt, wird in diesem OOPSLA 2005-Papier (oder dem zugehörigen technischen Bericht ) beschrieben.

Kurz gesagt, Sie können sagen : method g(x) throws like f(x), was bedeutet, dass g alle Ausnahmen auslöst, die f auslöst. Voila, überprüfte Ausnahmen ohne das Problem der kaskadierenden Änderungen.

Obwohl es sich um eine akademische Arbeit handelt, möchte ich Sie ermutigen, (Teile davon) zu lesen, da sie die Vor- und Nachteile geprüfter Ausnahmen gut erklärt.


5

Dies 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 richtig ist, weil die Sprachspezifikation sie auch so nennt - aber wie wird eine Ausnahme im Typensystem benannt und dargestellt?

Durch die Klasse Exceptionstellt man sich vor? Nun nein, weil Exceptions Ausnahmen sind und ebenso Ausnahmen Exceptions sind, mit Ausnahme der Ausnahmen, die nicht Exception s sind, weil andere Ausnahmen tatsächlich Errors sind, die die andere Art von Ausnahme sind, eine Art von außergewöhnlicher Ausnahme, die niemals auftreten sollte, außer wenn es das tut und was du niemals fangen solltest, außer manchmal musst du es. Das ist aber nicht alles, weil Sie auch andere Ausnahmen definieren können, die weder Exceptions noch Errors sind, sondern lediglich ThrowableAusnahmen.

Welche davon sind die "geprüften" Ausnahmen? Throwables sind geprüfte Ausnahmen, außer wenn es sich auch um Errors handelt, bei denen es sich um ungeprüfte Ausnahmen handelt, und dann gibt es die Exceptions, die ebenfalls Throwables sind und den Haupttyp der geprüften Ausnahme darstellen, außer es gibt auch eine Ausnahme, nämlich die, wenn sie sind auch RuntimeExceptions, weil das die andere Art von ungeprüfter Ausnahme ist.

Wofür sind RuntimeExceptions? Nun, genau wie der Name schon sagt, sind sie Ausnahmen, wie alle Exceptions, und sie treten zur Laufzeit auf, wie alle Ausnahmen tatsächlich, außer dass RuntimeExceptions im Vergleich zu anderen Laufzeits außergewöhnlich sind, Exceptionweil sie nur passieren sollen Wenn Sie einen dummen Fehler machen, obwohl RuntimeExceptions nie Errors sind, dann sind sie für Dinge, die außergewöhnlich fehlerhaft sind, aber eigentlich nicht Errors sind. Außer RuntimeErrorException, was wirklich ein RuntimeExceptionfür Errors ist. Aber sollen nicht alle Ausnahmen ohnehin fehlerhafte Umstände darstellen? Ja, alle. Abgesehen von ThreadDeatheiner außergewöhnlich außergewöhnlichen Ausnahme, da in der Dokumentation erklärt wird, dass es sich um ein "normales Ereignis" handelt und dassError

Wie auch immer, da wir alle Ausnahmen in der Mitte in Errors (die für außergewöhnliche Ausführungsausnahmen gelten, also nicht aktiviert sind) und Exceptions (die für weniger außergewöhnliche Ausführungsfehler gelten, also überprüft, außer wenn sie nicht sind), benötigen wir jetzt zwei verschiedene Arten von jeweils mehreren Ausnahmen. Also brauchen wir IllegalAccessErrorund IllegalAccessExceptionund InstantiationErrorund InstantiationExceptionund NoSuchFieldErrorund NoSuchFieldExceptionund NoSuchMethodErrorund NoSuchMethodExceptionund ZipErrorund und und ZipException.

Selbst wenn eine Ausnahme aktiviert ist, gibt es immer (ziemlich einfache) Möglichkeiten, den Compiler zu betrügen und zu werfen, ohne dass er überprüft wird. Wenn Sie dies tun, erhalten Sie möglicherweise eine UndeclaredThrowableException, außer in anderen Fällen, in denen sie sich als UnexpectedExceptionoder oder UnknownException(die nichts damit zu tun hat UnknownError, was nur für "schwerwiegende Ausnahmen" gilt), oder ExecutionException, oder InvocationTargetException, oder, oder ExceptionInInitializerError.

Oh, und wir dürfen nicht vergessen, dass Java 8 ein schickes neues UncheckedIOExceptionist, eine RuntimeExceptionAusnahme, mit der Sie das Konzept der Ausnahmeprüfung aus dem Fenster werfen können, indem Sie geprüfte IOExceptionAusnahmen einschließen, die durch E / A-Fehler verursacht werden (die keine IOErrorAusnahmen verursachen , obwohl dies vorhanden ist auch), die außergewöhnlich schwer zu handhaben sind und daher nicht überprüft werden müssen.

Danke Java!


2
Soweit ich sagen kann, sagt diese Antwort nur "Javas Ausnahmen sind ein Chaos" auf eine ironische, wohl witzige Weise. Was es nicht zu tun scheint, ist zu erklären, warum Programmierer es vermeiden, zu verstehen, wie diese Dinge funktionieren sollen. Auch in realen Fällen (zumindest in Fällen, mit denen ich mich befassen konnte) sind die Ausnahmen bei weitem nicht so kompliziert, wie Sie es beschrieben haben, wenn die Programmierer nicht absichtlich versuchen, ihr Leben zu erschweren.
CptBartender

5

Das Problem

Das schlimmste Problem, das ich beim Ausnahmebehandlungsmechanismus sehe, ist, dass er die Codeduplizierung in großem Maßstab einführt ! Seien wir ehrlich: In den meisten Projekten müssen Entwickler in 95% der Fälle mit Ausnahme nur dem Benutzer (und in einigen Fällen auch dem Entwicklungsteam, z. B. durch Senden eines e) mitteilen -mail mit dem Stack-Trace). Daher wird normalerweise an jeder Stelle, an der die Ausnahme behandelt wird, dieselbe Codezeile / jeder Codeblock verwendet.

Nehmen wir an, wir protokollieren einfach jeden catch-Block für eine Art von geprüfter Ausnahme:

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

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

Warten Sie einen Moment ... werden wir wirklich all diese mehreren hundert Stellen im Code bearbeiten?! Du verstehst, was ich meine :-).

Die Lösung

Um dieses Problem zu beheben, haben wir das Konzept der Ausnahmebehandlungsroutinen (auf die ich weiter als EHs verweisen werde) eingeführt, um die Ausnahmebehandlung zu zentralisieren . Für jede Klasse, die Ausnahmen ausführen muss, wird eine Instanz des Ausnahmebehandlers durch unser Dependency Injection- Framework injiziert . Das typische Muster der Ausnahmebehandlung sieht nun folgendermaßen 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).

Natürlich können wir für komplexere Fälle mehrere Unterklassen von EHs implementieren und Funktionen nutzen, die uns unser DI-Framework bietet. Durch Ändern unserer DI-Framework-Konfiguration können wir die EH-Implementierung problemlos global wechseln oder bestimmte Implementierungen von EH für Klassen mit besonderen Anforderungen an die Ausnahmebehandlung bereitstellen (z. B. mithilfe der Guice @ Named-Annotation).

Auf diese Weise können wir das Verhalten bei der Ausnahmebehandlung in der Entwicklungs- und Release-Version der Anwendung (z. B. Entwicklung - Protokollierung des Fehlers und Anhalten der Anwendung, Protokollierung des Fehlers mit weiteren Details und Fortsetzung der Ausführung durch die Anwendung) ohne Aufwand unterscheiden.

Letzte eine Sache

Last but not least scheint es, dass die gleiche Art der Zentralisierung erreicht werden kann, indem unsere Ausnahmen nur "hoch" übergeben werden, bis sie zu einer Ausnahmebehandlungsklasse der obersten Ebene gelangen. Dies führt jedoch zu einer Unordnung von Code und Signaturen unserer Methoden und führt zu Wartungsproblemen, die von anderen in diesem Thread erwähnt werden.


6
Ausnahmen werden erfunden, um etwas Nützliches daraus zu machen. Das Schreiben in eine Protokolldatei oder das Rendern eines hübschen Fensters ist nicht sinnvoll, da das ursprüngliche Problem dadurch nicht gelöst wird. Um etwas Nützliches zu tun, müssen Sie eine andere Lösungsstrategie ausprobieren. Beispiele: Wenn ich meine Daten nicht von Server AI abrufen kann, versuchen Sie es auf Server B. Oder wenn Algorithmus A einen Heap-Überlauf erzeugt, versuche ich Algorithmus B, der viel langsamer ist, aber möglicherweise erfolgreich ist.
Ceving

2
@ceving Ja, theoretisch ist alles gut und wahr. Aber jetzt kommen wir zurück, um das Wort zu üben. Bitte antworten Sie wirklich ehrlich, wie oft Sie dies in Ihrem Real-Word-Projekt tun. Welcher Teil der catchBlöcke in diesem realen Projekt macht mit Exceptins etwas wirklich "Nützliches"? 10% wären gut. Übliche Probleme, die Ausnahmen generieren, sind der Versuch, die Konfiguration aus einer nicht vorhandenen Datei zu lesen, OutOfMemoryErrors, NullPointerExceptions, Integritätsfehler bei Datenbankbeschränkungen usw. usw. Versuchen Sie wirklich, alle ordnungsgemäß wiederherzustellen? Ich glaube dir nicht :). Oft gibt es einfach keine Möglichkeit, sich zu erholen.
Piotr Sobczyk

3
@PiotrSobczyk: Wenn ein Programm aufgrund einer suer-Anforderung eine Aktion ausführt und der Vorgang auf eine Weise fehlschlägt, die im Systemstatus nichts beschädigt hat, ist es äußerst nützlich, den Benutzer darüber zu informieren, dass der Vorgang nicht abgeschlossen werden konnte Art und Weise, mit der Situation umzugehen. Das größte Versagen von Ausnahmen in C # und .net besteht darin, dass es keine einheitliche Methode gibt, um festzustellen, ob etwas im Systemstatus möglicherweise beschädigt wurde.
Supercat

4
Richtig, @PiotrSobczyk. In den meisten Fällen besteht die einzig richtige Aktion als Antwort auf eine Ausnahme darin, die Transaktion zurückzusetzen und eine Fehlerantwort zurückzugeben. Ideen zum "Lösen von Ausnahmen" implizieren Wissen und Autorität, die wir nicht haben (und sollten), und verletzen die Kapselung. Wenn unsere Anwendung keine Datenbank ist, sollten wir nicht versuchen, die Datenbank zu reparieren . Es ist nützlich genug, sauber zu versagen und zu vermeiden, dass fehlerhafte Daten geschrieben werden .
Thomas W

@PiotrSobczyk Gestern habe ich mich mit einer Ausnahme "a konnte kein Objekt lesen" befasst (die nur auftritt, weil die zugrunde liegende Datenbank vor der Software aktualisiert wurde - was niemals passieren sollte, aber aufgrund menschlicher Fehler möglich ist) Die historische Version der Datenbank verweist garantiert auf eine alte Version des Objekts.
Gleichnamig

4

Anders spricht in Episode 97 von Software Engineering Radio über die Fallstricke überprüfter Ausnahmen und warum er sie aus C # herausgelassen hat .


4

Mein Artikel auf c2.com ist immer noch weitgehend unverändert von seiner ursprünglichen Form: CheckedExceptionsAreIncompatibleWithVisitorPattern

Zusammenfassend:

Das Besuchermuster und seine Verwandten sind eine Klasse von Schnittstellen, bei denen sowohl der indirekte Aufrufer als auch die Schnittstellenimplementierung eine Ausnahme kennen, die Schnittstelle und der direkte Anrufer jedoch eine Bibliothek bilden, die dies nicht wissen kann.

Die Grundannahme von CheckedExceptions ist, dass alle deklarierten Ausnahmen von jedem Punkt aus ausgelöst werden können, der eine Methode mit dieser Deklaration aufruft. Das VisitorPattern zeigt, dass diese Annahme fehlerhaft ist.

Das Endergebnis von geprüften Ausnahmen in solchen Fällen ist eine Menge ansonsten nutzloser Code, der die geprüfte Ausnahmebedingung des Compilers zur Laufzeit im Wesentlichen aufhebt.

Was das zugrunde liegende Problem betrifft:

Meine allgemeine Idee ist, dass der Handler der obersten Ebene die Ausnahme interpretieren und eine entsprechende Fehlermeldung anzeigen muss. Ich sehe fast immer entweder E / A-Ausnahmen, Kommunikationsausnahmen (aus irgendeinem Grund unterscheiden sich APIs) oder schwerwiegende Fehler (Programmfehler oder schwerwiegende Probleme auf dem Sicherungsserver). Dies sollte also nicht zu schwierig sein, wenn wir eine Stapelverfolgung für schwerwiegende Fehler zulassen Serverproblem.


1
Sie sollten so etwas wie DAGNodeException in der Schnittstelle haben, dann die IOException abfangen und in eine DAGNodeException konvertieren: public void call (DAGNode arg) löst DAGNodeException aus;
TofuBeer

2
@TofuBeer, das ist genau mein Punkt. Ich finde, dass das ständige Ein- und Auspacken von Ausnahmen schlimmer ist als das Entfernen geprüfter Ausnahmen.
Joshua

2
Nun, wir sind uns dann überhaupt nicht einig ... aber Ihr Artikel beantwortet immer noch nicht die eigentliche zugrunde liegende Frage, wie Sie verhindern, dass Ihre Anwendung dem Benutzer eine Stapelverfolgung anzeigt, wenn eine Laufzeitausnahme ausgelöst wird.
TofuBeer

1
@TofuBeer - Wenn es fehlschlägt, ist es richtig, dem Benutzer mitzuteilen, dass es fehlgeschlagen ist! Was ist Ihre Alternative, außer den Fehler mit "null" oder unvollständigen / falschen Daten zu "überarbeiten"? Zu behaupten, es sei gelungen, ist eine Lüge, die die Sache nur noch schlimmer macht. Mit 25 Jahren Erfahrung in hochzuverlässigen Systemen sollte die Wiederholungslogik nur sorgfältig und gegebenenfalls verwendet werden. Ich würde auch erwarten, dass ein Besucher wahrscheinlich wieder versagt, egal wie oft Sie es erneut versuchen. Wenn Sie nicht mit einem Flugzeug fliegen, ist das Wechseln zu einer zweiten Version desselben Algorithmus unpraktisch und unplausibel (und kann trotzdem fehlschlagen).
Thomas W

4

Um zu versuchen, nur die unbeantwortete Frage zu beantworten:

Wenn Sie RuntimeException-Unterklassen anstelle von Exception-Unterklassen auslösen, woher wissen Sie dann, was Sie abfangen sollen?

Die Frage enthält meiner Meinung nach eine fadenscheinige Begründung. Nur weil die API Ihnen sagt, was sie auslöst, heißt das nicht, dass Sie in allen Fällen gleich damit umgehen. Anders ausgedrückt, die Ausnahmen, die Sie abfangen müssen, hängen vom Kontext ab, in dem Sie die Komponente verwenden, die die Ausnahme auslöst.

Zum Beispiel:

Wenn ich einen Verbindungstester für eine Datenbank schreibe oder etwas, um die Gültigkeit eines von XPath eingegebenen Benutzers zu überprüfen, möchte ich wahrscheinlich alle aktivierten und nicht aktivierten Ausnahmen abfangen und melden, die von der Operation ausgelöst werden.

Wenn ich jedoch eine Verarbeitungs-Engine schreibe, werde ich eine XPathException (aktiviert) wahrscheinlich genauso behandeln wie eine NPE: Ich würde sie bis zum oberen Rand des Worker-Threads laufen lassen, den Rest dieses Stapels überspringen und protokollieren das Problem (oder senden Sie es zur Diagnose an eine Supportabteilung) und hinterlassen Sie dem Benutzer ein Feedback, um den Support zu kontaktieren.


2
Genau. Einfach und unkompliziert, wie die Ausnahmebehandlung sein soll. Wie Dave sagt, erfolgt die korrekte Ausnahmebehandlung normalerweise auf hohem Niveau . "Früh werfen, spät fangen" ist das Prinzip. Überprüfte Ausnahmen machen das schwierig.
Thomas W

4

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

Es bevorzugt ungeprüfte Ausnahmen, aber diese Auswahl wird sehr gründlich erklärt und basiert auf starken Argumenten.

Ich möchte hier nicht zu viel vom Artikelinhalt zitieren (es ist am besten, ihn als Ganzes zu lesen), aber er behandelt die meisten Argumente von ungeprüften Ausnahmen, die Befürworter dieses Threads befürworten. Insbesondere dieses Argument (das sehr beliebt zu sein scheint) wird behandelt:

Nehmen wir den Fall, als die Ausnahme irgendwo unten in den API-Ebenen ausgelöst wurde und nur in die Luft sprudelte, weil niemand wusste, dass dieser Fehler überhaupt auftreten konnte, obwohl es sich um eine Art Fehler handelte, der beim Aufrufen des Codes sehr plausibel war warf es (FileNotFoundException zum Beispiel im Gegensatz zu VogonsTrashingEarthExcept ... in diesem Fall wäre es egal, ob wir damit umgehen oder nicht, da nichts mehr übrig ist, um damit umzugehen).

Der Autor "Antworten":

Es ist absolut falsch anzunehmen, dass nicht alle Laufzeitausnahmen abgefangen und an die "Spitze" der Anwendung weitergegeben werden dürfen. (...) Für jede außergewöhnliche Bedingung, die - gemäß den System- / Geschäftsanforderungen - eindeutig behandelt werden muss, müssen Programmierer entscheiden, wo sie gefangen werden sollen und was zu tun ist, sobald die Bedingung erfasst wird. Dies muss streng nach den tatsächlichen Anforderungen der Anwendung erfolgen und darf nicht auf einer Compiler-Warnung basieren. Alle anderen Fehler müssen frei an den obersten Handler weitergegeben werden können, wo sie protokolliert werden, und es wird eine ordnungsgemäße (möglicherweise Beendigung) Aktion ausgeführt.

Und der Hauptgedanke oder Artikel ist:

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

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

So traurig, dass nicht viele Menschen diesen großartigen Artikel zu entdecken scheinen :-(. Ich empfehle von ganzem Herzen jedem, der zögert, welcher Ansatz besser ist, sich etwas Zeit zu nehmen und ihn zu lesen.


4

Überprüfte Ausnahmen waren in ihrer ursprünglichen Form eher ein Versuch, Eventualitäten als Fehler zu behandeln. Das lobenswerte Ziel war es, bestimmte vorhersehbare Punkte hervorzuheben (keine Verbindung möglich, Datei nicht gefunden usw.) und sicherzustellen, dass Entwickler damit umgehen.

Was im ursprünglichen Konzept nie enthalten war, war, die Erklärung einer Vielzahl von systemischen und nicht behebbaren Fehlern zu erzwingen. Diese Fehler waren nie korrekt, um als geprüfte Ausnahmen deklariert zu werden.

Fehler sind im Code im Allgemeinen möglich, und EJB-, Web- und Swing / AWT-Container berücksichtigen dies bereits, indem sie einen äußersten Ausnahmebehandler für fehlgeschlagene Anforderungen bereitstellen. Die grundlegendste richtige Strategie besteht darin, die Transaktion zurückzusetzen und einen Fehler zurückzugeben.

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

Das größte Argument gegen "geprüfte" Ausnahmen ist, dass die meisten Ausnahmen nicht behoben werden können. Die einfache Tatsache ist, dass wir nicht den Code / das Subsystem besitzen, der / das kaputt gegangen ist. Wir können die Implementierung nicht sehen, wir sind nicht dafür verantwortlich und können sie nicht reparieren.

Wenn unsere Anwendung keine Datenbank ist, sollten wir nicht versuchen, die Datenbank zu reparieren. Das würde das Prinzip der Einkapselung verletzen .

Besonders problematisch waren die Bereiche JDBC (SQLException) und RMI für EJB (RemoteException). Anstatt behebbare Eventualitäten gemäß dem ursprünglichen Konzept der „geprüften Ausnahme“ zu identifizieren, mussten diese allgegenwärtigen systemischen Zuverlässigkeitsprobleme, die nicht behebbar sind, allgemein deklariert werden.

Der andere schwerwiegende Fehler im Java-Design bestand darin, dass die Ausnahmebehandlung korrekt auf der höchstmöglichen Ebene "Geschäft" oder "Anforderung" platziert werden sollte. Das Prinzip hier ist "früh werfen, spät fangen". Überprüfte Ausnahmen tun wenig, behindern dies jedoch.

In Java besteht ein offensichtliches Problem darin, dass Tausende von Try-Catch-Blöcken erforderlich sind, wobei ein erheblicher Anteil (40% +) falsch codiert ist. Fast keine von diesen implementiert eine echte Handhabung oder Zuverlässigkeit, verursacht jedoch einen erheblichen Codierungsaufwand.

Schließlich sind "geprüfte Ausnahmen" mit der funktionalen FP-Programmierung so gut wie nicht kompatibel.

Ihr Beharren auf "Sofort behandeln" steht im Widerspruch zu den Best Practices für die Ausnahmebehandlung bei "Catch Late" und jeder FP-Struktur, die Schleifen / oder Kontrollflüsse abstrahiert.

Viele Leute reden über den "Umgang" mit geprüften Ausnahmen, reden aber durch ihre Hüte. Wenn Sie nach einem Fehler mit null, unvollständigen oder falschen Daten fortfahren, um den Erfolg vorzutäuschen, wird nichts behandelt. Es ist ein Engineering- / Zuverlässigkeitsfehler der niedrigsten Form.

Sauberes Versagen ist die grundlegendste richtige Strategie zur Behandlung einer Ausnahme. Das Zurücksetzen der Transaktion, das Protokollieren des Fehlers und das Melden einer "Fehler" -Reaktion an den Benutzer sind eine gute Praxis - und vor allem zu verhindern, dass falsche Geschäftsdaten in die Datenbank übernommen werden.

Andere Strategien für die Ausnahmebehandlung sind "Wiederholen", "Wiederverbinden" oder "Überspringen" auf Geschäfts-, Subsystem- oder Anforderungsebene. All dies sind allgemeine Zuverlässigkeitsstrategien und funktionieren mit Laufzeitausnahmen gut / besser.

Schließlich ist es weitaus besser, zu scheitern, als mit falschen Daten zu laufen. Das Fortfahren führt entweder zu sekundären Fehlern, die von der ursprünglichen Ursache entfernt und schwerer zu debuggen sind. oder führt schließlich dazu, dass fehlerhafte Daten festgeschrieben werden. Die Leute werden dafür gefeuert.

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


1
Mein Punkt ist es , als allgemeine Strategie richtig zu scheitern . Nicht aktivierte Ausnahmen helfen dabei, da sie das Einfügen von Fangblöcken nicht erzwingen. Das Abfangen und die Fehlerprotokollierung können dann einigen äußersten Handlern überlassen werden, anstatt tausende Male in der gesamten Codebasis falsch codiert zu werden (was tatsächlich Fehler verbirgt) . Bei willkürlichen Fehlern sind ungeprüfte Ausnahmen absolut korrekt. Eventualverbindlichkeiten - vorhersehbare Ergebnisse wie unzureichende Mittel - sind die einzigen Ausnahmen, die es wert sind, überprüft zu werden.
Thomas W

1
Meine Antwort oben spricht dies an. In erster Linie 1) Der äußerste Fehlerbehandler sollte alles abfangen. Darüber hinaus können nur für bestimmte identifizierte Standorte 2) bestimmte erwartete Eventualitäten erfasst und behandelt werden - an dem unmittelbaren Standort, an dem sie ausgelöst werden. Das bedeutet, dass die Datei nicht gefunden wurde, zu wenig Geld usw. an dem Punkt, an dem diese wiederhergestellt werden konnten - nicht höher. Das Prinzip der Einkapselung bedeutet, dass die äußeren Schichten nicht dafür verantwortlich sein können / sollten, Fehler tief im Inneren zu verstehen / zu beheben. Drittens 3) Alles andere sollte nach außen geworfen werden - wenn möglich nicht markiert.
Thomas W

1
Der äußerste Handler fängt die Ausnahme ab, protokolliert sie und gibt eine "fehlgeschlagene" Antwort zurück oder zeigt einen Fehlerdialog an. Sehr einfach, überhaupt nicht schwer zu definieren. Der Punkt ist, dass jede Ausnahme, die nicht sofort und lokal wiederherstellbar ist, aufgrund des Kapselungsprinzips ein nicht behebbarer Fehler ist . Wenn der Code, über den Sie Bescheid wissen sollen, ihn nicht wiederherstellen kann, schlägt die Anforderung insgesamt sauber und ordnungsgemäß fehl. Dies ist der richtige Weg, um es richtig zu machen.
Thomas W

1
Falsch. Die Aufgabe des äußersten Handlers besteht darin , fehlerfrei zu versagen und Fehler an der Anforderungsgrenze zu protokollieren . Eine fehlerhafte Anforderung schlägt ordnungsgemäß fehl, eine Ausnahme wird gemeldet, der Thread kann die nächste Anforderung weiter bearbeiten. Ein solcher äußerster Handler ist eine Standardfunktion in Tomcat-, AWT-, Spring-, EJB-Containern und im Java-Hauptthread.
Thomas W

1
Warum ist es gefährlich, "echte Fehler" an der Anforderungsgrenze oder am äußersten Handler zu melden? Ich arbeite häufig in den Bereichen Systemintegration und Zuverlässigkeit, wo korrektes Zuverlässigkeitstechnik tatsächlich wichtig ist, und verwende dazu Ansätze mit "ungeprüften Ausnahmen". Ich bin mir nicht sicher, worüber Sie tatsächlich debattieren - anscheinend möchten Sie vielleicht 3 Monate auf die ungeprüfte Ausnahmemethode verbringen, ein Gefühl dafür bekommen, und dann können wir vielleicht weiter darüber diskutieren. Vielen Dank.
Thomas W

4

Wie bereits erwähnt, gibt es im Java-Bytecode keine geprüften Ausnahmen. Sie sind einfach ein Compiler-Mechanismus, ähnlich wie andere Syntaxprüfungen. Ich sehe geprüfte Ausnahmen, so wie ich sehe, dass sich der Compiler über eine redundante Bedingung beschwert : if(true) { a; } b;. Das ist hilfreich, aber ich hätte dies möglicherweise absichtlich getan. Lassen Sie mich Ihre Warnungen ignorieren.

Tatsache ist, dass Sie nicht jeden Programmierer dazu zwingen können, "das Richtige zu tun", wenn Sie geprüfte Ausnahmen durchsetzen und alle anderen jetzt Kollateralschäden erleiden, die Sie nur für die von Ihnen festgelegte Regel hassen.

Beheben Sie die schlechten Programme da draußen! Versuchen Sie nicht, die Sprache zu korrigieren, um sie nicht zuzulassen! Für die meisten Leute bedeutet "etwas gegen eine Ausnahme tun" dem Benutzer nur davon zu erzählen. Ich kann den Benutzer auch über eine nicht aktivierte Ausnahme informieren. Halten Sie daher Ihre aktivierten Ausnahmeklassen von meiner API fern.


Richtig, ich wollte nur den Unterschied zwischen nicht erreichbarem Code (der einen Fehler erzeugt) und Bedingungen mit einem vorhersehbaren Ergebnis hervorheben. Ich werde diesen Kommentar später entfernen.
Holger

3

Ein Problem bei aktivierten Ausnahmen besteht darin, dass Ausnahmen häufig an Methoden einer Schnittstelle angehängt werden, wenn auch nur eine Implementierung dieser Schnittstelle diese verwendet.

Ein weiteres Problem bei geprüften Ausnahmen besteht darin, dass sie häufig missbraucht werden. Das perfekte Beispiel dafür ist in java.sql.Connection's close()Methode. Es kann ein werfen SQLException, obwohl Sie bereits ausdrücklich angegeben haben, dass Sie mit der Verbindung fertig sind. Welche Informationen könnten close () möglicherweise vermitteln, die Sie interessieren würden?

Wenn ich eine Verbindung schließe () *, sieht sie normalerweise ungefähr so ​​aus:

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

Lassen Sie mich auch nicht mit den verschiedenen Analysemethoden und NumberFormatException beginnen ... TryParse von .NET, das keine Ausnahmen auslöst, ist so viel einfacher zu verwenden, dass es schmerzhaft ist, zu Java zurückkehren zu müssen (wir verwenden sowohl Java als auch C # wo ich arbeite).

*Als zusätzlicher Kommentar, ein PooledConnection der Connection.close () nicht einmal nahe , eine Verbindung, aber Sie immer noch die SQLException fangen müssen aufgrund es eine geprüfte Ausnahme.


2
Richtig, jeder der Fahrer kann ... die Frage ist eher "warum sollte sich der Programmierer darum kümmern?" wie er sowieso auf die Datenbank zugegriffen hat. Die Dokumente warnen Sie sogar, dass Sie die aktuelle Transaktion immer festschreiben () oder zurücksetzen () sollten, bevor Sie close () aufrufen.
Powerlord

Viele Leute denken, dass das Schließen einer Datei keine Ausnahme auslösen kann ... stackoverflow.com/questions/588546/… Sind Sie zu 100% sicher, dass es keine Fälle gibt, in denen dies von Bedeutung wäre?
TofuBeer

Ich bin zu 100% sicher, dass es keine Fälle gibt, in denen es wichtig wäre und der Anrufer keinen Versuch / Fang machen würde.
Martin

1
Exzellentes Beispiel mit schließenden Verbindungen, Martin! Ich kann Sie nur umformulieren: Wenn wir nur ausdrücklich angegeben haben, dass wir mit einer Verbindung fertig sind, warum sollte es uns dann stören, was passiert, wenn wir es schließen? Es gibt weitere Fälle wie diesen, in denen es dem Programmierer egal ist, ob eine Ausnahme auftritt, und er hat absolut Recht damit.
Piotr Sobczyk

1
@PiotrSobczyk: Einige SQL-Treiber kreischen, wenn eine Verbindung nach dem Starten einer Transaktion geschlossen, aber weder bestätigt noch zurückgesetzt wird. IMHO ist das Kreischen besser, als das Problem stillschweigend zu ignorieren, zumindest in Fällen, in denen das Kreischen nicht dazu führt, dass andere Ausnahmen verloren gehen.
Supercat

3

Der Programmierer muss alle Ausnahmen kennen, die eine Methode auslösen kann, um sie korrekt verwenden zu können. Wenn Sie ihn also mit nur wenigen Ausnahmen über den Kopf schlagen, hilft dies einem unachtsamen Programmierer nicht unbedingt, Fehler zu vermeiden.

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

Statische Analyse kann nett sein, aber eine wirklich zuverlässige statische Analyse erfordert oft unflexibel strenge Arbeit vom Programmierer. Es gibt eine Kosten-Nutzen-Berechnung, und die Messlatte muss für eine Prüfung, die zu einem Fehler bei der Kompilierungszeit führt, hoch gelegt werden. Es wäre hilfreicher, wenn die IDE die Aufgabe übernehmen würde, mitzuteilen, welche Ausnahmen eine Methode auslösen kann (einschließlich welcher unvermeidbar ist). Obwohl es ohne erzwungene Ausnahmedeklarationen möglicherweise nicht so zuverlässig wäre, würden die meisten Ausnahmen dennoch in der Dokumentation deklariert, und die Zuverlässigkeit einer IDE-Warnung ist nicht so entscheidend.


2

Ich denke, dass dies eine ausgezeichnete Frage ist und überhaupt nicht argumentativ. Ich denke, dass Bibliotheken von Drittanbietern (im Allgemeinen) ungeprüfte Ausnahmen auslösen sollten . Dies bedeutet, dass Sie Ihre Abhängigkeiten von der Bibliothek isolieren können (dh Sie müssen ihre Ausnahmen weder erneut auslösen noch auslösen Exception- normalerweise schlechte Praxis). Die DAO-Schicht von Spring ist ein hervorragendes Beispiel dafür.

Andererseits sollten Ausnahmen von der Java-Kern-API im Allgemeinen überprüft werden, ob sie jemals behandelt werden könnten . Nimm FileNotFoundExceptionoder (mein Favorit) InterruptedException. Diese Bedingungen sollten fast immer spezifisch behandelt werden (dh Ihre Reaktion auf eine InterruptedExceptionist nicht die gleiche wie Ihre Reaktion auf eine IllegalArgumentException). Die Tatsache, dass Ihre Ausnahmen überprüft werden, zwingt Entwickler dazu, darüber nachzudenken, ob eine Bedingung behandelt werden kann oder nicht. (Das heißt, ich habe selten InterruptedExceptionrichtig gehandhabt gesehen !)

Eine weitere Sache - a RuntimeExceptionist nicht immer "wo ein Entwickler etwas falsch gemacht hat". Eine unzulässige Argumentausnahme wird ausgelöst, wenn Sie versuchen, eine enumVerwendung zu erstellen , valueOfund es gibt keinen enumdieser Namen. Dies ist nicht unbedingt ein Fehler des Entwicklers!


1
Ja, es ist ein Fehler des Entwicklers. Sie haben eindeutig nicht den richtigen Namen verwendet, also müssen sie zurückgehen und ihren Code korrigieren.
AxiomaticNexus

@AxiomaticNexus Kein vernünftiger Entwickler verwendet enumMitgliedsnamen, einfach weil sie enumstattdessen Objekte verwenden. Ein falscher Name kann also nur von außen kommen, sei es eine Importdatei oder was auch immer. Eine Möglichkeit, mit solchen Namen umzugehen, besteht darin, MyEnum#valueOfdie IAE anzurufen und zu fangen. Eine andere Möglichkeit besteht darin, eine vorgefüllte zu verwenden Map<String, MyEnum>. Dies sind jedoch Implementierungsdetails.
Maaartinus

@maaartinus Es gibt Fälle, in denen Enum-Mitgliedsnamen verwendet werden, ohne dass die Zeichenfolge von außen kommt. Zum Beispiel, wenn Sie alle Mitglieder dynamisch durchlaufen möchten, um mit jedem etwas zu tun. Darüber hinaus spielt es keine Rolle, ob die Zeichenfolge von außen kommt oder nicht. Der Entwickler verfügt über alle erforderlichen Informationen, um zu wissen, ob die Übergabe der x-Zeichenfolge an "MyEnum # valueOf" zu einem Fehler führt, bevor er übergeben wird. Die Übergabe der x-Zeichenfolge an "MyEnum # valueOf", wenn dies zu einem Fehler geführt hätte, wäre eindeutig ein Fehler des Entwicklers.
AxiomaticNexus

2

Hier ist ein Argument gegen geprüfte Ausnahmen (von joelonsoftware.com):

Der Grund dafür ist, dass ich Ausnahmen als nicht besser als "goto's" betrachte, die seit den 1960er Jahren als schädlich angesehen werden, da sie einen abrupten Sprung von einem Codepunkt zum anderen bewirken. In der Tat sind sie deutlich schlechter als gotos:

  • Sie sind im Quellcode unsichtbar. Wenn Sie sich einen Codeblock ansehen, einschließlich Funktionen, die Ausnahmen auslösen können oder nicht, können Sie nicht erkennen, welche Ausnahmen von wo ausgelöst werden können. Dies bedeutet, dass selbst eine sorgfältige Codeüberprüfung keine potenziellen Fehler aufdeckt.
  • Sie erstellen zu viele mögliche Austrittspunkte für eine Funktion. Um korrekten Code zu schreiben, müssen Sie wirklich über jeden möglichen Codepfad durch Ihre Funktion nachdenken. Jedes Mal, wenn Sie eine Funktion aufrufen, die eine Ausnahme auslösen kann und diese nicht sofort abfängt, können Überraschungsfehler auftreten, die durch Funktionen verursacht werden, die abrupt beendet wurden und Daten in einem inkonsistenten Zustand belassen, oder durch andere Codepfade, die Sie nicht ausgeführt haben nachdenken über.

3
+1 Vielleicht möchten Sie das Argument in Ihrer Antwort zusammenfassen? Sie sind wie unsichtbare Gotos und frühe Exits für Ihre Routinen, die über das gesamte Programm verteilt sind.
MarkJ

13
Das ist eher ein Argument gegen Ausnahmen im Allgemeinen.
Ionuț G. Stan

10
Hast du den Artikel tatsächlich gelesen? Erstens spricht er über Ausnahmen im Allgemeinen, zweitens gilt der Abschnitt "Sie sind im Quellcode unsichtbar" speziell für UNCHECKED-Ausnahmen. Dies ist der springende Punkt der überprüften Ausnahme ... damit Sie wissen, welcher Code was wohin wirft
Newtopian

2
@Eva Sie sind nicht gleich. Mit einer goto-Anweisung können Sie das gotoSchlüsselwort sehen. Mit einer Schleife können Sie die schließende Klammer oder das Schlüsselwort breakoder sehen continue. Alle springen zu einem Punkt in der aktuellen Methode. Aber Sie können das nicht immer sehen throw, weil es oft nicht in der aktuellen Methode ist, sondern in einer anderen Methode, die es aufruft (möglicherweise indirekt.)
finnw

5
@ finnw Funktionen sind selbst eine Form von goto. Normalerweise wissen Sie nicht, welche Funktionen die von Ihnen aufgerufenen Funktionen aufrufen. Wenn Sie ohne Funktionen programmiert hätten, hätten Sie kein Problem mit unsichtbaren Ausnahmen. Dies bedeutet, dass das Problem nicht speziell an Ausnahmen gebunden ist und kein gültiges Argument gegen Ausnahmen im Allgemeinen ist. Man könnte sagen, Fehlercodes sind schneller, man könnte sagen, Monaden sind sauberer, aber das Argument goto ist albern.
Eva

2

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

  1. Viele Frameworks, die für Java funktionieren. Wie Spring, das JDBC-Ausnahmen in ungeprüfte Ausnahmen umschließt und Nachrichten in das Protokoll wirft
  2. Viele Sprachen, die nach Java kamen, sogar oben auf der Java-Plattform - sie verwenden sie nicht
  3. Überprüfte Ausnahmen, es ist eine freundliche Vorhersage darüber, wie der Client den Code verwenden würde, der eine Ausnahme auslöst. Ein Entwickler, der diesen Code schreibt, würde jedoch niemals über das System und das Geschäft Bescheid wissen, in dem der Code-Client arbeitet. Als Beispiel Interfcace-Methoden, die das Auslösen einer aktivierten Ausnahme erzwingen. Es gibt 100 Implementierungen über das System, 50 oder sogar 90 Implementierungen lösen diese Ausnahme nicht aus, aber der Client muss diese Ausnahme noch abfangen, wenn er auf diese Schnittstelle verweist. Diese 50 oder 90 Implementierungen neigen dazu, diese Ausnahmen in sich selbst zu behandeln und Ausnahmen in das Protokoll aufzunehmen (und dies ist ein gutes Verhalten für sie). Was sollen wir damit machen? Ich hätte besser eine Hintergrundlogik, die all diesen Job erledigt - das Senden einer Nachricht an das Protokoll. Und wenn ich als Code-Client das Gefühl hätte, die Ausnahme behandeln zu müssen, werde ich es tun.
  4. Ein weiteres Beispiel, wenn ich mit E / A in Java arbeite, zwingt es mich, alle Ausnahmen zu überprüfen, wenn die Datei nicht vorhanden ist? was soll ich damit machen Wenn es nicht existiert, würde das System nicht mit dem nächsten Schritt fortfahren. Der Client dieser Methode würde keinen erwarteten Inhalt aus dieser Datei erhalten - er kann die Laufzeitausnahme verarbeiten. Andernfalls sollte ich zuerst die Option "Überprüfte Ausnahme" aktivieren, eine Nachricht in das Protokoll einfügen und dann eine Ausnahme aus der Methode auslösen. Nein ... nein - ich mache es besser automatisch mit RuntimeEception, das macht es / leuchtet automatisch auf. Es macht keinen Sinn, manuell damit umzugehen - ich würde mich freuen, wenn ich eine Fehlermeldung im Protokoll sehen würde (AOP kann dabei helfen ... etwas, das Java behebt). Wenn ich irgendwann feststelle, dass das System dem Endbenutzer eine Popup-Nachricht anzeigen soll, werde ich sie anzeigen, kein Problem.

Ich war froh, wenn Java mir die Wahl geben würde, was ich verwenden soll, wenn ich mit Kernbibliotheken wie E / A arbeite. Like bietet zwei Kopien derselben Klassen - eine mit RuntimeEception. Dann können wir vergleichen, was die Leute benutzen würden . Im Moment sollten sich viele Leute für Java oder eine andere Sprache entscheiden. Wie Scala, JRuby was auch immer. Viele glauben einfach, dass SUN richtig war.


1
Anstatt zwei Versionen von Klassen zu haben, sollte es eine präzise Möglichkeit geben, anzugeben, dass keiner der von einem Codeblock ausgeführten Methodenaufrufe Ausnahmen bestimmter Typen auslösen soll, und dass solche Ausnahmen mit bestimmten Mitteln und umschlossen werden sollten neu geworfen (standardmäßig erstellen Sie eine neue RuntimeExceptionmit einer entsprechenden inneren Ausnahme). Es ist bedauerlich, dass es prägnanter ist, wenn die äußere Methode throwseine Ausnahme von der inneren Methode darstellt, als wenn Ausnahmen von der inneren Methode umbrochen werden, wenn die letztere Vorgehensweise häufiger korrekt wäre.
Supercat

2

Eine wichtige Sache, die niemand erwähnt hat, ist, wie sie Schnittstellen und Lambda-Ausdrücke stört.

Angenommen, Sie definieren a MyAppException extends Exception. Dies ist die Ausnahme der obersten Ebene, die von allen von Ihrer Anwendung ausgelösten Ausnahmen geerbt wird. Jede Methode erklärt, throws MyAppExceptionwas ein bisschen nuanciert, aber überschaubar ist. Ein Ausnahmebehandler protokolliert eine Ausnahme und benachrichtigt den Benutzer irgendwie.

Alles sieht in Ordnung aus, bis Sie eine Schnittstelle implementieren möchten, die nicht Ihre ist. Offensichtlich erklärt es nicht die Absicht zu werfen MyApException, so dass der Compiler Ihnen nicht erlaubt, die Ausnahme von dort aus zu werfen.

Wenn sich Ihre Ausnahme jedoch verlängert RuntimeException, gibt es kein Problem mit den Schnittstellen. Sie können die Ausnahme freiwillig in JavaDoc erwähnen, wenn Sie dies wünschen. Abgesehen davon sprudelt es nur lautlos durch irgendetwas, um in Ihrer Ausnahmebehandlungsschicht gefangen zu werden.


1

Wir haben einige Hinweise auf C # 's Chefarchitekt gesehen.

Hier ist eine alternative Sichtweise eines Java-Benutzers, wann geprüfte Ausnahmen verwendet werden sollen. Er erkennt viele der Negative an, die andere erwähnt haben: Effektive Ausnahmen


2
Das Problem mit geprüften Ausnahmen in Java beruht auf einem tieferen Problem, bei dem zu viele Informationen im TYP der Ausnahme und nicht in den Eigenschaften einer Instanz eingekapselt sind. Es wäre nützlich, Ausnahmen geprüft zu haben, wenn "geprüft" ein Attribut von Wurf- / Fangstellen wäre und wenn deklarativ angegeben werden könnte, ob eine geprüfte Ausnahme, die einem Codeblock entgeht, als geprüfte Ausnahme verbleiben oder angezeigt werden soll durch einen umschließenden Block als ungeprüfte Ausnahme; Ebenso sollten catch-Blöcke angeben können, dass sie nur geprüfte Ausnahmen wünschen.
Supercat

1
Angenommen, eine Routine zur Suche nach Wörterbüchern wird angegeben, um einen bestimmten Ausnahmetyp auszulösen, wenn versucht wird, auf einen nicht vorhandenen Schlüssel zuzugreifen. Es kann sinnvoll sein, dass Client-Code eine solche Ausnahme abfängt. Wenn jedoch eine von der Suchroutine verwendete Methode denselben Ausnahmetyp auf eine Weise auslöst, die die Suchroutine nicht erwartet, sollte der Clientcode sie wahrscheinlich nicht abfangen. Nachdem geprüft Heit eine Eigenschaft Ausnahme sein Instanzen , throw - Sites und fangen Websites würden solche Probleme vermeiden. Der Client würde "geprüfte" Ausnahmen dieses Typs abfangen und unerwarteten ausweichen.
Supercat

0

Ich habe viel über die Ausnahmebehandlung gelesen, auch wenn ich (meistens) nicht wirklich sagen kann, dass ich über das Vorhandensein geprüfter Ausnahmen glücklich oder traurig bin. Dies ist meine Einstellung: Geprüfte Ausnahmen in Code auf niedriger Ebene (E / A, Netzwerk) , Betriebssystem usw.) und nicht aktivierte Ausnahmen in APIs / Anwendungsebenen auf hoher Ebene.

Auch wenn es nicht so einfach ist, eine Grenze zwischen ihnen zu ziehen, finde ich es wirklich ärgerlich / schwierig, mehrere APIs / Bibliotheken unter einem Dach zu integrieren, ohne ständig viele geprüfte Ausnahmen zu verpacken, aber manchmal auch Es ist nützlich / besser, gezwungen zu sein, eine Ausnahme abzufangen und eine andere bereitzustellen, die im aktuellen Kontext sinnvoller ist.

Das Projekt, an dem ich arbeite, verwendet viele Bibliotheken und integriert sie unter derselben API, die vollständig auf ungeprüften Ausnahmen basiert. Dieses Framework bietet eine API auf hoher Ebene, die anfangs voller geprüfter Ausnahmen war und nur einige nicht aktivierte hatte Ausnahmen (Initialisierungsausnahme, ConfigurationException usw.) und ich muss sagen, war nicht sehr freundlich . Die meiste Zeit mussten Sie Ausnahmen abfangen oder erneut auslösen, mit denen Sie nicht umgehen können, oder Sie kümmern sich nicht einmal darum (nicht zu verwechseln, Sie sollten Ausnahmen ignorieren), insbesondere auf der Clientseite, wo eine einzelne auftritt Ein Klick könnte 10 mögliche (markierte) Ausnahmen auslösen.

Die aktuelle Version (3. Version) verwendet nur ungeprüfte Ausnahmen und verfügt über einen globalen Ausnahmebehandler, der für die Behandlung aller nicht erfassten Ausnahmen verantwortlich ist. Die API bietet eine Möglichkeit, Ausnahmebehandlungsroutinen zu registrieren, die entscheiden, ob eine Ausnahme als Fehler angesehen wird (meistens ist dies der Fall). Dies bedeutet, dass jemand protokolliert und benachrichtigt wird, oder es kann etwas anderes bedeuten - wie diese Ausnahme, AbortException, Dies bedeutet, dass der aktuelle Ausführungsthread unterbrochen und kein Fehler protokolliert wird, da dies nicht gewünscht wird. Um alle benutzerdefinierten Threads zu ermitteln, muss die run () -Methode natürlich mit einem try {...} catch (all) behandelt werden.

public void run () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}}

Dies ist nicht erforderlich, wenn Sie den WorkerService zum Planen von Jobs (ausführbar, aufrufbar, Worker) verwenden, die alles für Sie erledigen.

Natürlich ist dies nur meine Meinung, und es ist vielleicht nicht die richtige, aber es sieht nach einem guten Ansatz für mich aus. Ich werde sehen, nachdem ich das Projekt veröffentlicht habe, ob das, was ich für gut halte, auch für andere gut ist ... :)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.