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ßerRuntimeException
) 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
, IOException
sollte 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 vonException
Unterklassen werfen, woher wissen Sie dann, was Sie fangen sollen?
Wenn die Antwort catch lautet, behandeln Exception
Sie 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 , RuntimeException
weil sie nicht wie die sie von Exception
und / oder warum sie eine Ausnahme fangen und dann erneut auslösen eine RuntimeException
eher als Add Würfe ihre Methode. Ich möchte die Motivation verstehen, geprüfte Ausnahmen nicht zu mögen.
quelle
Antworten:
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.
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:
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
wo
fopen
Fehler 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 mitund 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.
Sehen Sie diesen Fang? Hier ist die Signatur für diese API-Methode:
Beachten Sie, dass dies
FileNotFoundException
eine 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:
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:
(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
getRowData
ist 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
FileNotFoundException
nicht 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. DiesCheckedInvalidRowNumberException
ist 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:
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 .
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
RowData
von verschiedenen Methoden überall angezeigt. Insbesondere werde ich eine Menge Code wie schreibenund es wird schmerzhaft sein, jedes Mal versuchen / fangen zu müssen.
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:
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:
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
FileNotFoundException
denen 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: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.
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
quelle
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]:
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:
Und dann gibt es viele lächerliche Bibliotheksausnahmen wie:
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.
quelle
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:
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:
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.
quelle
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-
List
Oberfläche. Wir haben gemeinsame In-Memory-Implementierungen wieArrayList
undLinkedList
. Wir haben auch die Skelettklasse,AbstractList
die es einfach macht, neue Arten von Listen zu entwerfen. Für eine schreibgeschützte Liste müssen nur zwei Methoden implementiert werden:size()
undget(int index)
.Diese Beispielklasse
WidgetList
liest einige Objekte fester Größe vom TypWidget
(nicht gezeigt) aus einer Datei:Indem Sie die Widgets über die vertraute
List
Oberfläche verfügbar machen , können Sie Elemente abrufen (list.get(123)
) oder eine Liste iterieren (for (Widget w : list) ...
), ohne sichWidgetList
selbst kennen zu müssen . Man kann diese Liste an alle Standardmethoden übergeben, die generische Listen verwenden, oder sie in eine einschließenCollections.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 dieList
Schnittstelle korrekt implementiert ist.Nur dass es nicht so ist. Die obige Klasse wird nicht kompiliert, da die Dateizugriffsmethoden möglicherweise
IOException
eine 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 derList
Schnittstelle verletzen würde . Und es gibt keine nützliche Möglichkeit,WidgetList
die 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:
((Bearbeiten: Java 8 hat eine
UncheckedIOException
Klasse für genau diesen Fall hinzugefügt : zum Abfangen und erneuten Werfen vonIOException
s ü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.Random
die ü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
Runnable
Schnittstelle 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 werdenRuntimeException
. 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):
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
RuntimeException
und ohne die Stapelspur zu überladen! Verwenden Sie das Beispiel "WidgetList" erneut: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:
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 Typt
ü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
‚swrite
alle Methoden angebenthrows IOException
.ByteArrayOutputStream
ist eine Unterklasse, die anstelle einer echten E / A-Quelle in ein In-Memory-Array schreibt. Die überschriebenenwrite
Methoden können keinIOException
s verursachen , daher haben sie keinethrows
Klausel, und Sie können sie aufrufen, ohne sich um die Catch-or-Specify-Anforderung zu kümmern.Außer nicht immer. Angenommen, es
Widget
gibt eine Methode zum Speichern in einem Stream:Es
OutputStream
ist 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:Ü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
IOException
durchwrite
Aufrufe von a ausgelöstenOutputStream
, aber Sie möchten den deklarierten Typ der Variablen in a ändern. DerByteArrayOutputStream
Compiler 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
write
MethodenOutputStream
wird nicht außer Kraft gesetzt durchByteArrayOutputStream
. Insbesonderewrite(byte[] data)
handelt es sich um eine bequeme Methode, mit der das gesamte Array durch Aufrufenwrite(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ünschtethrows
Klausel. Das war vielleicht ein Versehen im Design vonByteArrayOutputStream
, 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
try
und dercatch
Blö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:
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 Next
oder PHPerror_reporting(0);
.Es ist nicht viel besser, eine Art Logger-Klasse aufzurufen:
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.
quelle
RuntimeException
. Beachten Sie, dass eine Routine gleichzeitig als deklariert werden kannthrows IOException
und dennoch angibt, dass jederIOException
aus einem verschachtelten Aufruf ausgelöste Vorgang als unerwartet betrachtet und umbrochen werden soll.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.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 IOException
ist 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
throws
Signaturen 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:
quelle
throws
Deklarationen befassen wollen .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:
Aktualisieren Sie die Benutzeroberfläche von einem Nicht-Benutzeroberflächenthread in C #:
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:
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
Ich habe vorher darüber gebloggt .
quelle
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:
quelle
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:
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.
quelle
RuntimeExceptions
der 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 wieIOException
. Wenn Sie eine erhaltenIOException
, gibt es nichts in Ihrem Code zu beheben; Ihr Programm sollte nicht explodieren, nur weil es einen Netzwerk-Schluckauf gab.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:
(SO erlaubt keine Tabellen, daher möchten Sie vielleicht Folgendes von der Originalseite lesen ...)
quelle
args[]
? Sind sie ein Zufall oder ein Fehler?File#openInputStream
Rückkehr vorstellenEither<InputStream, Problem>
- wenn Sie das meinen, dann sind wir uns vielleicht einig.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.acme
wirft ein Paket jedoch immer zu oft nur eineAcmeException
bestimmte Unterklasse aus. Anrufer müssen dann verarbeiten, deklarieren oder erneut signalisierenAcmeExceptions
, können jedoch immer noch nicht sicher sein, ob einAcmeFileNotFoundError
Ereignis aufgetreten ist oder ein Ereignis aufgetreten istAcmePermissionDeniedError
.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 dokumentierenAcmeException
.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
Error
Klasse anstatt einer zu werfenAcmeException
, aber wenn sie sich überhaupt um ihre API-Qualität kümmern, werden sie lernen, eine einzuführenAcmeFileNotFoundError
.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.
quelle
AcmeException
eher mit Werfen alsAcmeFileNotFoundError
mit 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.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.
quelle
Ausnahmekategorien
Wenn ich über Ausnahmen spreche, verweise ich immer auf Eric Lipperts Blog-Artikel über Vexing-Ausnahmen . Er ordnet Ausnahmen in folgende Kategorien ein:
OutOfMemoryError
oderThreadAbortException
.ArrayIndexOutOfBoundsException
,NullPointerException
oder jederIllegalArgumentException
.NumberFormatException
von,Integer.parseInt
anstatt eineInteger.tryParseInt
Methode bereitzustellen, die bei einem Analysefehler einen booleschen Wert false zurückgibt.FileNotFoundException
.Ein API-Benutzer:
Ü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.
quelle
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?
quelle
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.
quelle
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
Exception
stellt man sich vor? Nun nein, weilException
s Ausnahmen sind und ebenso AusnahmenException
s sind, mit Ausnahme der Ausnahmen, die nichtException
s sind, weil andere Ausnahmen tatsächlichError
s 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 wederException
s nochError
s sind, sondern lediglichThrowable
Ausnahmen.Welche davon sind die "geprüften" Ausnahmen?
Throwable
s sind geprüfte Ausnahmen, außer wenn es sich auch umError
s handelt, bei denen es sich um ungeprüfte Ausnahmen handelt, und dann gibt es dieException
s, die ebenfallsThrowable
s sind und den Haupttyp der geprüften Ausnahme darstellen, außer es gibt auch eine Ausnahme, nämlich die, wenn sie sind auchRuntimeException
s, weil das die andere Art von ungeprüfter Ausnahme ist.Wofür sind
RuntimeException
s? Nun, genau wie der Name schon sagt, sind sie Ausnahmen, wie alleException
s, und sie treten zur Laufzeit auf, wie alle Ausnahmen tatsächlich, außer dassRuntimeException
s im Vergleich zu anderen Laufzeits außergewöhnlich sind,Exception
weil sie nur passieren sollen Wenn Sie einen dummen Fehler machen, obwohlRuntimeException
s nieError
s sind, dann sind sie für Dinge, die außergewöhnlich fehlerhaft sind, aber eigentlich nichtError
s sind. AußerRuntimeErrorException
, was wirklich einRuntimeException
fürError
s ist. Aber sollen nicht alle Ausnahmen ohnehin fehlerhafte Umstände darstellen? Ja, alle. Abgesehen vonThreadDeath
einer 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
Error
s (die für außergewöhnliche Ausführungsausnahmen gelten, also nicht aktiviert sind) undException
s (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 wirIllegalAccessError
undIllegalAccessException
undInstantiationError
undInstantiationException
undNoSuchFieldError
undNoSuchFieldException
undNoSuchMethodError
undNoSuchMethodException
undZipError
und und undZipException
.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 alsUnexpectedException
oder oderUnknownException
(die nichts damit zu tun hatUnknownError
, was nur für "schwerwiegende Ausnahmen" gilt), oderExecutionException
, oderInvocationTargetException
, oder, oderExceptionInInitializerError
.Oh, und wir dürfen nicht vergessen, dass Java 8 ein schickes neues
UncheckedIOException
ist, eineRuntimeException
Ausnahme, mit der Sie das Konzept der Ausnahmeprüfung aus dem Fenster werfen können, indem Sie geprüfteIOException
Ausnahmen einschließen, die durch E / A-Fehler verursacht werden (die keineIOError
Ausnahmen verursachen , obwohl dies vorhanden ist auch), die außergewöhnlich schwer zu handhaben sind und daher nicht überprüft werden müssen.Danke Java!
quelle
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:
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:
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.
quelle
catch
Blö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.Anders spricht in Episode 97 von Software Engineering Radio über die Fallstricke überprüfter Ausnahmen und warum er sie aus C # herausgelassen hat .
quelle
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.
quelle
Um zu versuchen, nur die unbeantwortete Frage zu beantworten:
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.
quelle
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:
Der Autor "Antworten":
Und der Hauptgedanke oder Artikel ist:
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.
quelle
Ü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/
quelle
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.
quelle
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
'sclose()
Methode. Es kann ein werfenSQLException
, 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: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.quelle
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.
quelle
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
FileNotFoundException
oder (mein Favorit)InterruptedException
. Diese Bedingungen sollten fast immer spezifisch behandelt werden (dh Ihre Reaktion auf eineInterruptedException
ist nicht die gleiche wie Ihre Reaktion auf eineIllegalArgumentException
). 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 seltenInterruptedException
richtig gehandhabt gesehen !)Eine weitere Sache - a
RuntimeException
ist nicht immer "wo ein Entwickler etwas falsch gemacht hat". Eine unzulässige Argumentausnahme wird ausgelöst, wenn Sie versuchen, eineenum
Verwendung zu erstellen ,valueOf
und es gibt keinenenum
dieser Namen. Dies ist nicht unbedingt ein Fehler des Entwicklers!quelle
enum
Mitgliedsnamen, einfach weil sieenum
stattdessen 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#valueOf
die IAE anzurufen und zu fangen. Eine andere Möglichkeit besteht darin, eine vorgefüllte zu verwendenMap<String, MyEnum>
. Dies sind jedoch Implementierungsdetails.Hier ist ein Argument gegen geprüfte Ausnahmen (von joelonsoftware.com):
quelle
goto
Schlüsselwort sehen. Mit einer Schleife können Sie die schließende Klammer oder das Schlüsselwortbreak
oder sehencontinue
. Alle springen zu einem Punkt in der aktuellen Methode. Aber Sie können das nicht immer sehenthrow
, weil es oft nicht in der aktuellen Methode ist, sondern in einer anderen Methode, die es aufruft (möglicherweise indirekt.)Die guten Beweise, dass Checked Exception nicht benötigt werden, sind:
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.
quelle
RuntimeException
mit einer entsprechenden inneren Ausnahme). Es ist bedauerlich, dass es prägnanter ist, wenn die äußere Methodethrows
eine Ausnahme von der inneren Methode darstellt, als wenn Ausnahmen von der inneren Methode umbrochen werden, wenn die letztere Vorgehensweise häufiger korrekt wäre.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 MyAppException
was 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.quelle
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
quelle
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 () {
}}
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 ... :)
quelle