Ich wurde beauftragt, eine Anwendung zu warten, die vor einiger Zeit von erfahreneren Entwicklern geschrieben wurde. Ich bin auf diesen Code gestoßen:
public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
try {
return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
rethrow(e);
}
throw new RuntimeException("cannot reach here");
}
Ich bin gespannt, ob das Werfen RuntimeException("cannot reach here")
gerechtfertigt ist. Mir fehlt wahrscheinlich etwas Offensichtliches, da ich weiß, dass dieser Code von einem erfahreneren Kollegen stammt.
EDIT: Hier ist ein Rethrow-Body, auf den sich einige Antworten beziehen. Ich hielt es für unwichtig in dieser Frage.
private void rethrow(Exception e) throws MailException {
if (e instanceof InvalidDataException) {
InvalidDataException ex = (InvalidDataException) e;
rethrow(ex);
}
if (e instanceof EntityAlreadyExistsException) {
EntityAlreadyExistsException ex = (EntityAlreadyExistsException) e;
rethrow(ex);
}
if (e instanceof EntityNotFoundException) {
EntityNotFoundException ex = (EntityNotFoundException) e;
rethrow(ex);
}
if (e instanceof NoPermissionException) {
NoPermissionException ex = (NoPermissionException) e;
rethrow(ex);
}
if (e instanceof ServiceUnavailableException) {
ServiceUnavailableException ex = (ServiceUnavailableException) e;
rethrow(ex);
}
LOG.error("internal error, original exception", e);
throw new MailUnexpectedException();
}
private void rethrow(ServiceUnavailableException e) throws
MailServiceUnavailableException {
throw new MailServiceUnavailableException();
}
private void rethrow(NoPermissionException e) throws PersonNotAuthorizedException {
throw new PersonNotAuthorizedException();
}
private void rethrow(InvalidDataException e) throws
MailInvalidIdException, MailLoginNotAvailableException,
MailInvalidLoginException, MailInvalidPasswordException,
MailInvalidEmailException {
switch (e.getDetail()) {
case ID_INVALID:
throw new MailInvalidIdException();
case LOGIN_INVALID:
throw new MailInvalidLoginException();
case LOGIN_NOT_ALLOWED:
throw new MailLoginNotAvailableException();
case PASSWORD_INVALID:
throw new MailInvalidPasswordException();
case EMAIL_INVALID:
throw new MailInvalidEmailException();
}
}
private void rethrow(EntityAlreadyExistsException e)
throws MailLoginNotAvailableException, MailEmailAddressAlreadyForwardedToException {
switch (e.getDetail()) {
case LOGIN_ALREADY_TAKEN:
throw new MailLoginNotAvailableException();
case EMAIL_ADDRESS_ALREADY_FORWARDED_TO:
throw new MailEmailAddressAlreadyForwardedToException();
}
}
private void rethrow(EntityNotFoundException e) throws
MailAccountNotCreatedException,
MailAliasNotCreatedException {
switch (e.getDetail()) {
case ACCOUNT_NOT_FOUND:
throw new MailAccountNotCreatedException();
case ALIAS_NOT_FOUND:
throw new MailAliasNotCreatedException();
}
}
java
programming-practices
Sok Pomaranczowy
quelle
quelle
rethrow
eigentlichthrow
eine ausnahme ausfällt . (was eines Tages passieren kann, wenn sich die Implementierung ändert)Antworten:
Zunächst einmal vielen Dank, dass Sie Ihre Frage beantwortet und uns gezeigt haben, was zu tun
rethrow
ist. Tatsächlich konvertiert es also Ausnahmen mit Eigenschaften in feinkörnigere Klassen von Ausnahmen. Dazu später mehr.Da ich die Hauptfrage ursprünglich nicht wirklich beantwortet habe, heißt es hier: Ja, es ist im Allgemeinen ein schlechter Stil, Laufzeitausnahmen in nicht erreichbarem Code auszulösen. Verwenden Sie besser Behauptungen oder vermeiden Sie das Problem. Wie bereits erwähnt, kann der Compiler hier nicht sicher sein, dass der Code niemals den
try/catch
Block verlässt. Sie können Ihren Code umgestalten, indem Sie den Vorteil nutzen, dass ...Fehler sind Werte
( Es ist nicht überraschend, dass es in go bekannt ist )
Nehmen wir ein einfacheres Beispiel, das ich vor Ihrer Bearbeitung verwendet habe: Stellen Sie sich vor, Sie protokollieren etwas und erstellen eine Wrapper-Ausnahme wie in Konrads Antwort . Nennen wir es
logAndWrap
.Anstatt die Ausnahme als Nebeneffekt von
logAndWrap
auszulösen, können Sie sie als Nebeneffekt wirken lassen und eine Ausnahme zurückgeben (zumindest die in der Eingabe angegebene). Sie müssen keine Generika verwenden, sondern nur Grundfunktionen:Dann
throw
explizit Sie und Ihr Compiler freut sich:Was ist, wenn Sie es vergessen
throw
?Wie in Joe23s Kommentar erläutert , besteht eine Möglichkeit zur defensiven Programmierung , um sicherzustellen, dass die Ausnahme immer ausgelöst wird, darin, explizit
throw new CustomWrapperException(exception)
am Ende von ein zu machenlogAndWrap
, wie es von Guava.Throwables getan wird . Auf diese Weise wissen Sie, dass die Ausnahme ausgelöst wird, und Ihr Typanalysator ist glücklich. Ihre benutzerdefinierten Ausnahmen müssen jedoch deaktiviert sein, was nicht immer möglich ist. Außerdem würde ich das Risiko, dass einthrow
Entwickler zum Schreiben fehlt, als sehr gering einstufen : Der Entwickler muss es vergessen und die umgebende Methode sollte nichts zurückgeben, da der Compiler sonst einen fehlenden Return feststellen würde. Dies ist eine interessante Möglichkeit, das Typensystem zu bekämpfen, und sie funktioniert trotzdem.Erneut werfen
Das eigentliche
rethrow
kann auch als Funktion geschrieben werden, aber ich habe Probleme mit der aktuellen Implementierung:Es gibt viele nutzlose Darsteller wieCasts sind in der Tat erforderlich (siehe Kommentare):Wenn neue Ausnahmen geworfen / zurückgegeben werden, wird die alte verworfen. Im folgenden Code kann
MailLoginNotAvailableException
ich mit a nicht erkennen, welche Anmeldung nicht verfügbar ist, was unpraktisch ist. Darüber hinaus sind Stacktraces unvollständig:Warum löst der Ursprungscode diese speziellen Ausnahmen überhaupt nicht aus? Ich vermute, dass dies
rethrow
als Kompatibilitätsebene zwischen einem (Mailing-) Subsystem und der Geschäftslogik verwendet wird (möglicherweise besteht die Absicht darin, Implementierungsdetails wie ausgelöste Ausnahmen zu verbergen, indem sie durch benutzerdefinierte Ausnahmen ersetzt werden). Auch wenn ich zustimme, dass es besser wäre, fangfreien Code zu haben, wie in Pete Beckers Antwort vorgeschlagen , glaube ich nicht, dass Sie die Möglichkeit haben werden, den Codecatch
undrethrow
hier ohne größere Umgestaltungen zu entfernen .quelle
logAndWrap
Methode können Sie die Ausnahme auslösen, anstatt sie zurückzugeben, aber den Rückgabetyp beibehalten(Runtime)Exception
. Auf diese Weise kann der catch-Block so bleiben, wie Sie ihn geschrieben haben (ohne den nicht erreichbaren Code einzufügen). Aber es hat den zusätzlichen Vorteil, dass Sie das nicht vergessen könnenthrow
. Wenn Sie die Ausnahme zurückgeben und den Wurf vergessen, wird die Ausnahme unbemerkt verschluckt. Wenn Sie RuntimeException mit einem Rückgabetyp auslösen, wird der Compiler glücklich, und diese Fehler werden vermieden. So wird GuavaThrowables.propagate
umgesetzt.logAndWrap
, aber mit der zurückgegebenen Ausnahme nichts tut? Das ist interessant. In einer Funktion, die einen Wert zurückgeben muss, würde der Compiler feststellen, dass es einen Pfad gibt, für den kein Wert zurückgegeben wird. Dies ist jedoch nützlich für Methoden, die nichts zurückgeben. Danke noch einmal.Diese
rethrow(e);
Funktion verstößt gegen das Prinzip, dass unter normalen Umständen eine Funktion zurückgegeben wird, während unter außergewöhnlichen Umständen eine Funktion eine Ausnahme auslöst. Diese Funktion verletzt dieses Prinzip, indem sie unter normalen Umständen eine Ausnahme auslöst. Das ist die Quelle aller Verwirrung.Der Compiler geht davon aus, dass diese Funktion unter normalen Umständen zurückgegeben wird, so weit der Compiler erkennen kann, dass die Ausführung möglicherweise das Ende der
retrieveUserMailConfiguration
Funktion erreicht. An diesem Punkt ist es ein Fehler, keinereturn
Anweisung zu haben . Das dortRuntimeException
geworfene soll diese Sorge des Compilers lindern, aber es ist ein ziemlich klobiger Weg, es zu tun. Eine andere Möglichkeit, denfunction must return a value
Fehler zu verhindern , besteht darin, einereturn null; //to keep the compiler happy
Erklärung hinzuzufügen , die meiner Meinung nach jedoch ebenso umständlich ist.Also persönlich würde ich das ersetzen:
mit diesem:
oder, noch besser, (wie Coredump angedeutet hat) mit diesem:
Auf diese Weise würde der Steuerungsfluss für den Compiler offensichtlich, sodass Ihr Final
throw new RuntimeException("cannot reach here");
nicht nur redundant, sondern auch nicht kompilierbar wird, da es vom Compiler als nicht erreichbarer Code gekennzeichnet wird.Das ist der eleganteste und eigentlich auch einfachste Weg, aus dieser hässlichen Situation herauszukommen.
quelle
throw reportAndTransform(e)
ein paar Minuten nach dem Absenden meiner eigenen Antwort bearbeitet haben. Vielleicht haben Sie Ihre Frage unabhängig davon geändert, und ich entschuldige mich im Voraus, wenn dies der Fall ist, aber ansonsten tun die Leute normalerweise etwas Anerkennung.throw reportAndTransform(e)
. Bei vielen Designmustern fehlen Patches und Sprachfunktionen. Das ist einer von ihnen.Das
throw
wurde wahrscheinlich hinzugefügt, um den Fehler "Methode muss einen Wert zurückgeben" zu umgehen, der andernfalls auftreten würde - der Java-Datenflussanalysator ist intelligent genug, um zu verstehen, dassreturn
nach einemthrow
, aber nicht nach Ihrer benutzerdefiniertenrethrow()
Methode, keine@NoReturn
Annotation erforderlich ist dass Sie dies beheben können.Das Erstellen einer neuen Ausnahme in nicht erreichbarem Code erscheint jedoch überflüssig. Ich würde einfach schreiben
return null
, wissend, dass es in der Tat nie passiert.quelle
throw new...
Zeile auskommentiere und mich über keine Rückgabebescheinigung beschwert. Ist es schlecht programmiert? Gibt es eine Konvention zum Ersetzen einer nicht erreichbaren Rückgabeerklärung? Ich werde diese Frage eine Weile unbeantwortet lassen, um mehr Input zu ermutigen. Trotzdem danke für deine Antwort.rethrow()
Methode verbirgt die Tatsache, dasscatch
die Ausnahme erneut ausgelöst wird. Ich würde so etwas vermeiden. Außerdem kann es natürlichrethrow()
passieren, dass die Ausnahme nicht erneut ausgelöst wird. In diesem Fall passiert etwas anderes.return null
. Mit der Ausnahme, wenn jemand in der Zukunft den Code bricht, damit die letzte Zeile irgendwie erreichbar wird, wird es sofort klar, wenn die Ausnahme ausgelöst wird. Wenn Sie stattdessenreturn null
jetzt einennull
Wert in Ihrer Anwendung haben, der nicht erwartet wurde. Wer weiß, wo in der Bewerbung derNullPointerException
Wille entsteht? Es sei denn, Sie haben Glück und es ist unmittelbar nach dem Aufruf dieser Funktion, wird es sehr schwierig sein, das eigentliche Problem aufzuspüren.return null
ist sehr wahrscheinlich, dass der Fehler ausgeblendet wird, da Sie die anderen Fälle, in denen ernull
von diesem Fehler zurückkehrt, nicht unterscheiden können .Das
Die Anweisung macht einem PERSONEN , der den Code liest, klar, was gerade passiert. Es ist also viel besser, als beispielsweise null zurückzugeben. Dies erleichtert auch das Debuggen, wenn der Code auf unerwartete Weise geändert wird.
Scheint aber
rethrow(e)
einfach falsch! In Ihrem Fall halte ich das Refactoring des Codes für eine bessere Option. In den anderen Antworten (ich mag die besten von coredump) erfahren Sie, wie Sie Ihren Code sortieren können.quelle
Ich weiß nicht, ob es eine Konvention gibt.
Jedenfalls wäre ein anderer Trick, so zu tun:
Dies zulassen:
Und keine künstliche
RuntimeException
wird mehr benötigt. Obwohl esrethrow
eigentlich nie einen Wert zurückgibt, ist es jetzt gut genug für den Compiler.Es gibt theoretisch einen Wert zurück (Methodensignatur) und ist dann davon ausgenommen, da es stattdessen eine Ausnahme auslöst.
Ja, es mag seltsam aussehen, aber
RuntimeException
andererseits - ein Phantom zu werfen oder Nullen zurückzugeben, die in dieser Welt niemals zu sehen sind - ist auch nicht gerade etwas Schönes.Wenn Sie die Lesbarkeit anstreben, können Sie Folgendes umbenennen
rethrow
und haben:quelle
Wenn Sie den
try
Block komplett entfernen, brauchen Sie dasrethrow
oder das nichtthrow
. Dieser Code macht genau das Gleiche wie das Original:Lassen Sie sich nicht von der Tatsache täuschen, dass es von erfahreneren Entwicklern stammt. Das ist Code Rot, und das passiert die ganze Zeit. Repariere es einfach.
EDIT: Ich habe falsch gelesen,
rethrow(e)
als würde ich die Ausnahme einfach neu auslösene
. Wenn dieserethrow
Methode tatsächlich etwas anderes ausführt als die Ausnahme erneut auszulösen, ändert das Entfernen der Ausnahme die Semantik dieser Methode.quelle
catch(Exception)
einem abscheulichen Code-Geruch erkennen ließen.rethrow
wird, nutzlos ist (und das ist hier nicht der Punkt), aber Sie können nicht einfach Code loswerden, den Sie nicht mögen, und sagen, es ist gleichbedeutend mit dem Original, wenn es eindeutig nicht ist. Es gibt Nebenwirkungen in der benutzerdefiniertenrethrow
Funktion.