Wir verwenden SonarQube, um unseren Java-Code zu analysieren. Dabei gilt folgende Regel (kritisch):
Öffentliche Methoden sollten höchstens eine aktivierte Ausnahme auslösen
Durch die Verwendung von aktivierten Ausnahmen werden Methodenaufrufer gezwungen, Fehler zu behandeln, indem sie entweder weitergegeben oder behandelt werden. Dies macht diese Ausnahmen vollständig Teil der API der Methode.
Um die Komplexität für die Aufrufer angemessen zu halten, sollten Methoden nicht mehr als eine Art von aktivierter Ausnahme auslösen. "
Ein weiteres Stück in Sonar hat Folgendes :
Öffentliche Methoden sollten höchstens eine aktivierte Ausnahme auslösen
Durch die Verwendung von aktivierten Ausnahmen werden Methodenaufrufer gezwungen, Fehler zu behandeln, indem sie entweder weitergegeben oder behandelt werden. Dies macht diese Ausnahmen vollständig Teil der API der Methode.
Um die Komplexität für Aufrufer angemessen zu halten, sollten Methoden nicht mehr als eine Art von aktivierter Ausnahme auslösen.
Der folgende Code:
public void delete() throws IOException, SQLException { // Non-Compliant /* ... */ }
sollte überarbeitet werden in:
public void delete() throws SomeApplicationLevelException { // Compliant /* ... */ }
Überschreibende Methoden werden von dieser Regel nicht geprüft und dürfen mehrere geprüfte Ausnahmen auslösen.
Ich bin in meinen Lesungen zur Ausnahmebehandlung nie auf diese Regel / Empfehlung gestoßen und habe versucht, einige Standards, Diskussionen usw. zum Thema zu finden. Das einzige, was ich gefunden habe, ist das von CodeRach: Wie viele Ausnahmen sollte eine Methode höchstens auslösen ?
Ist das ein akzeptierter Standard?
quelle
Antworten:
Betrachten wir die Situation, in der Sie den bereitgestellten Code haben:
Hier besteht die Gefahr, dass der Code, den Sie aufrufen
delete()
, wie folgt aussieht:Das ist auch schlimm. Und es wird mit einer anderen Regel abgefangen, die das Abfangen der Basis-Exception-Klasse kennzeichnet.
Der Schlüssel ist, keinen Code zu schreiben, der Sie dazu bringt, schlechten Code anderswo zu schreiben.
Die Regel, auf die Sie stoßen, ist ziemlich verbreitet. Checkstyle hat es in seinen Designregeln:
Dies beschreibt genau das Problem und was das Problem ist und warum Sie es nicht tun sollten. Es ist ein anerkannter Standard, den viele statische Analysewerkzeuge identifizieren und kennzeichnen.
Und während Sie können es nach Sprache Design tun, und es kann vorkommen, dass es das Richtige ist, zu tun , ist es etwas , das Sie sehen sollen und sofort gehen „um, warum mache ich das?“ Es mag für internen Code akzeptabel sein, bei dem jeder diszipliniert genug ist, um nie
catch (Exception e) {}
etwas zu tun , aber in den meisten Fällen habe ich Leute gesehen, die vor allem in internen Situationen Abstriche gemacht haben.Lassen Sie die Teilnehmer Ihrer Klasse keinen schlechten Code schreiben.
Ich möchte darauf hinweisen, dass dies mit Java SE 7 und höher weniger wichtig ist, da mit einer einzelnen catch-Anweisung mehrere Ausnahmen abgefangen werden können (Abfangen mehrerer Ausnahmetypen und erneutes Auslösen von Ausnahmen mit verbesserter Typprüfung von Oracle).
Mit Java 6 und früher hatten Sie Code, der so aussah:
und
oder
Keine dieser Optionen mit Java 6 ist ideal. Der erste Ansatz verletzt DRY . Mehrere Blöcke tun immer wieder dasselbe - einmal für jede Ausnahme. Sie möchten die Ausnahme protokollieren und erneut auslösen? Okay. Dieselben Codezeilen für jede Ausnahme.
Die zweite Option ist aus mehreren Gründen schlechter. Erstens bedeutet dies, dass Sie alle Ausnahmen abfangen. Der Nullzeiger wird dort abgefangen (und sollte es auch nicht). Darüber hinaus werfen Sie einen erneut aus,
Exception
was bedeutet, dass die MethodensignaturdeleteSomething() throws Exception
eine weitere Verwirrung verursacht, da Benutzer Ihres Codes nun gezwungen sind, diesen zu verwendencatch(Exception e)
.In Java 7 ist dies nicht so wichtig, da Sie stattdessen Folgendes tun können:
Darüber hinaus ist die Art zu überprüfen , ob man nicht die Typen der Ausnahmen fängt geworfen werden:
Die Typprüfung erkennt, dass es sich
e
möglicherweise nur um TypenIOException
oder handeltSQLException
. Ich bin immer noch nicht sonderlich begeistert von der Verwendung dieses Stils, aber es verursacht nicht so schlechten Code wie unter Java 6 (wo es Sie zwingen würde, die Methodensignatur als die Superklasse zu haben, die die Ausnahmen erweitern).Trotz all dieser Änderungen erzwingen viele statische Analysetools (Sonar, PMD, Checkstyle) immer noch Java 6-Stilrichtlinien. Es ist keine schlechte Sache. Ich bin mit einer Warnung einverstanden, dass diese noch durchgesetzt werden sollen, aber Sie können die Priorität für sie in Dur oder Moll ändern, je nachdem, wie Ihr Team sie priorisiert.
Wenn Ausnahmen aktiviert oder deaktiviert werden sollen ... das ist eine Frage der g r e eine t Debatte , dass man leicht unzählige Blog - Beiträge Aufnahme jede Seite des Arguments finden. Wenn Sie jedoch mit aktivierten Ausnahmen arbeiten, sollten Sie wahrscheinlich vermeiden, mehrere Typen zu werfen, zumindest unter Java 6.
quelle
foo.delete()
? Wird es noch gefangen und erneut geworfen?delete
in diesem Beispiel eine andere aktivierte Ausnahme als IOException oder SQLException ausgelöst wird. Der entscheidende Punkt, den ich machen wollte, ist, dass eine Methode, die rethrowException aufruft, immer noch den Typ der Exception in Java 7 erhält. In Java 6 wird all dies auf den allgemeinenException
Typ zusammengeführt, was statische Analysen und andere Codierer traurig macht.catch (Exception e)
und es entwedercatch (IOException e)
odercatch (SQLException e)
stattdessen zu erzwingen .catch(IOException|SQLException ex)
stattdessen tun kann . Wenn Sie jedoch nur die Ausnahme erneut auslösen möchten, ist es keine schlechte Sache, der Typprüfung zu erlauben, den tatsächlichen Typ der Ausnahme zu verbreiten, während der Code vereinfacht wird.Der Grund, warum Sie im Idealfall nur einen Ausnahmetyp auslösen möchten, liegt darin, dass dies ansonsten wahrscheinlich gegen die Prinzipien der Einzelverantwortung und der Abhängigkeitsumkehr verstößt . Lassen Sie uns anhand eines Beispiels demonstrieren.
Angenommen, wir haben eine Methode, die Daten aus der Persistenz abruft, und diese Persistenz besteht aus einer Reihe von Dateien. Da es sich um Dateien handelt, können wir Folgendes haben
FileNotFoundException
:Jetzt haben wir eine Änderung der Anforderungen und unsere Daten stammen aus einer Datenbank. Anstelle von a
FileNotFoundException
(da es sich nicht um Dateien handelt) werfen wir jetzt einSQLException
:Wir müssten jetzt den gesamten Code durchgehen, der unsere Methode verwendet, und die Ausnahme ändern, auf die wir prüfen müssen, sonst wird der Code nicht kompiliert. Wenn unsere Methode weit und breit aufgerufen wird, kann das viel bewirken, dass sich andere ändern. Es braucht viel Zeit und die Leute werden nicht glücklich sein.
Die Abhängigkeitsinversion besagt, dass wir keine dieser Ausnahmen auslösen sollten, da sie interne Implementierungsdetails enthüllen, an deren Verkapselung wir arbeiten. Das Aufrufen von Code muss wissen, welche Art von Persistenz wir verwenden, wenn es wirklich nur darum gehen sollte, ob der Datensatz abgerufen werden kann. Stattdessen sollten wir eine Ausnahme auslösen, die den Fehler auf derselben Abstraktionsebene übermittelt, die wir über unsere API verfügbar machen:
Wenn wir nun die interne Implementierung ändern, können wir diese Ausnahme einfach in eine einbinden
InvalidRecordException
und weitergeben (oder sie nicht einbinden und einfach eine neue auslösenInvalidRecordException
). Externer Code weiß oder kümmert sich nicht darum, welche Art von Persistenz verwendet wird. Es ist alles eingekapselt.In Bezug auf die Einzelverantwortung müssen wir uns mit Code befassen, der mehrere, nicht zusammenhängende Ausnahmen auslöst. Angenommen, wir haben die folgende Methode:
Was können wir über diese Methode sagen? Wir können nur an der Signatur erkennen, dass sie eine Datei öffnet und analysiert. Wenn wir eine Konjunktion wie "und" oder "oder" in der Beschreibung einer Methode sehen, wissen wir, dass sie mehr als eine Sache tut; es hat mehr als eine verantwortung . Methoden mit mehr als einer Verantwortung sind schwer zu verwalten, da sie sich ändern können, wenn sich eine der Verantwortlichkeiten ändert. Stattdessen sollten wir die Methoden aufteilen, damit sie eine einzige Verantwortung haben:
Wir haben die Verantwortung für das Lesen der Datei aus der Verantwortung für das Parsen der Daten herausgenommen. Ein Nebeneffekt davon ist, dass wir jetzt beliebige Zeichenfolgendaten an die Analysedaten aus beliebigen Quellen übergeben können: In-Memory, Datei, Netzwerk usw. Wir können
parse
jetzt auch einfacher testen, da wir keine Datei auf der Festplatte benötigen Tests durchführen gegen.Manchmal gibt es tatsächlich zwei (oder mehr) Ausnahmen, die wir von einer Methode auslösen können, aber wenn wir uns an SRP und DIP halten, werden die Zeiten, in denen wir auf diese Situation stoßen, seltener.
quelle
update
Methode so atomar ist, wie Sie es machen können, und die Ausnahmen auf der richtigen Abstraktionsebene sind, dann klingt es so, als müssten Sie zwei verschiedene Arten von Ausnahmen auslösen, da es zwei Ausnahmefälle gibt. Das sollte das Maß dafür sein, wie viele Ausnahmen geworfen werden können, und nicht diese (manchmal willkürlichen) Interferenzregeln.catch (IOException | SQLException ex)
) zu verwenden, da das eigentliche Problem im Programmmodell / -design liegt.Ich erinnere mich, dass ich ein bisschen damit herumgespielt habe, als ich vor einiger Zeit mit Java gespielt habe, aber ich war mir der Unterscheidung zwischen aktiviert und deaktiviert nicht wirklich bewusst, bis ich Ihre Frage gelesen habe. Ich habe diesen Artikel ziemlich schnell bei Google gefunden und er geht auf einige der offensichtlichen Kontroversen ein:
http://tutorials.jenkov.com/java-exception-handling/checked-or-unchecked-exceptions.html
Abgesehen davon ist eines der Probleme, die dieser Typ mit geprüften Ausnahmen angesprochen hat, dass (und ich persönlich bin von Anfang an auf dieses Problem mit Java gestoßen), wenn Sie den
throws
Klauseln in Ihren Methodendeklarationen nicht nur eine Reihe von geprüften Ausnahmen hinzufügen Müssen Sie viel mehr Boilerplate-Code eingeben, um dies zu unterstützen, wenn Sie zu Methoden auf höherer Ebene wechseln? Es führt jedoch auch zu einem größeren Durcheinander und bricht die Kompatibilität, wenn Sie versuchen, den Methoden auf niedrigerer Ebene mehr Ausnahmetypen hinzuzufügen. Wenn Sie einer untergeordneten Methode einen aktivierten Ausnahmetyp hinzufügen, müssen Sie Ihren Code erneut durchgehen und mehrere andere Methodendeklarationen anpassen.Ein in dem Artikel erwähnter Milderungspunkt - und der Autor mochte das persönlich nicht - war, eine Basisklassenausnahme zu erstellen, Ihre
throws
Klauseln darauf zu beschränken , sie nur zu verwenden und dann nur Unterklassen davon intern zu erhöhen. Auf diese Weise können Sie neue geprüfte Ausnahmetypen erstellen, ohne den gesamten Code erneut durchlaufen zu müssen.Dem Autor dieses Artikels hat es vielleicht nicht so gut gefallen, aber es ist meiner persönlichen Erfahrung nach absolut sinnvoll (vor allem, wenn Sie nachsehen können, was alle Unterklassen sind), und ich wette, aus diesem Grund erhalten Sie den folgenden Rat begrenzen Sie alles auf jeweils einen aktivierten Ausnahmetyp. Was mehr ist, dass der von Ihnen erwähnte Rat mehrere geprüfte Ausnahmetypen in nicht öffentlichen Methoden erlaubt, was absolut sinnvoll ist, wenn dies ihr Motiv ist (oder auch auf andere Weise). Wenn es ohnehin nur eine private Methode oder ähnliches ist, haben Sie nicht die Hälfte Ihrer Codebasis durchlaufen, wenn Sie eine Kleinigkeit ändern.
Sie haben größtenteils gefragt, ob dies ein akzeptierter Standard ist, aber zwischen Ihrer Recherche, die Sie erwähnt haben, diesem vernünftig durchdachten Artikel und der Erfahrung mit persönlicher Programmierung scheint dies in keiner Weise aufzufallen.
quelle
Throwable
und damit fertig werden, anstatt eine eigene Hierarchie zu erfinden?Das Auslösen mehrerer aktivierter Ausnahmen ist sinnvoll, wenn mehrere sinnvolle Dinge zu tun sind.
Nehmen wir zum Beispiel an, Sie haben eine Methode
Dies verstößt gegen eine Ausnahmeregel, ist aber sinnvoll.
Leider passieren in der Regel Methoden wie
Hier besteht für den Anrufer nur eine geringe Chance, basierend auf dem Ausnahmetyp etwas Bestimmtes zu tun. Also, wenn wir ihn dazu bringen wollen, zu erkennen, dass diese Methoden KÖNNEN und manchmal WOLLEN, dann ist es genug und besser, nur SomethingMightGoWrongException auszulösen.
Daher gilt höchstens eine geprüfte Ausnahme.
Wenn Ihr Projekt jedoch ein Design verwendet, bei dem mehrere wichtige überprüfte Ausnahmen vorliegen, sollte diese Regel nicht gelten.
Nebenbemerkung: Irgendetwas kann eigentlich fast überall schief gehen, man kann also überlegen, mit? erweitert RuntimeException, aber es gibt einen Unterschied zwischen "wir alle machen Fehler" und "dies spricht externes System und es wird manchmal nicht funktionieren, damit umgehen".
quelle