J'ai été chargé de maintenir une application écrite il y a quelque temps par des développeurs plus qualifiés. Je suis tombé sur ce morceau de code:
public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
try {
return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
rethrow(e);
}
throw new RuntimeException("cannot reach here");
}
Je suis curieux de savoir si le lancer RuntimeException("cannot reach here")
est justifié. Il me manque probablement quelque chose d'évident sachant que ce morceau de code vient d'un collègue plus aguerri.
EDIT: Voici le corps de retour auquel certaines réponses ont fait référence. J'ai jugé que ce n'était pas important dans cette question.
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
la source
la source
rethrow
ne fait pas réellementthrow
exception. (ce qui peut arriver un jour si l'implémentation change)Réponses:
Tout d'abord, merci d'avoir mis à jour votre question et de nous avoir montré ce qui se
rethrow
passe. Donc, en fait, il convertit les exceptions avec des propriétés en classes d'exceptions plus fines. Plus d'informations à ce sujet plus tard.Comme je n'ai pas vraiment répondu à la question principale à l'origine, c'est parti: oui, c'est généralement un mauvais style de lancer des exceptions d'exécution dans du code inaccessible; vous feriez mieux d'utiliser des assertions, ou mieux encore, d'éviter le problème. Comme déjà souligné, le compilateur ici ne peut pas être sûr que le code ne sort jamais du
try/catch
bloc. Vous pouvez refactoriser votre code en profitant du fait que ...Les erreurs sont des valeurs
(Sans surprise, il est bien connu dans go )
Prenons un exemple plus simple, celui que j'ai utilisé avant votre édition: alors imaginez que vous enregistrez quelque chose et que vous créez une exception de wrapper comme dans la réponse de Konrad . Appelons ça
logAndWrap
.Au lieu de lancer l'exception comme effet secondaire de
logAndWrap
, vous pouvez la laisser faire son travail comme effet secondaire et lui faire renvoyer une exception (au moins, celle donnée en entrée). Vous n'avez pas besoin d'utiliser des génériques, juste des fonctions de base:Ensuite, vous
throw
explicite, et votre compilateur est heureux:Et si tu oublies
throw
?Comme expliqué dans le commentaire de Joe23 , une manière de programmation défensive pour s'assurer que l'exception est toujours levée consisterait à faire explicitement un
throw new CustomWrapperException(exception)
à la fin delogAndWrap
, comme cela est fait par Guava.Throwables . De cette façon, vous savez que l'exception sera levée et votre analyseur de type est content. Cependant, vos exceptions personnalisées doivent être des exceptions non vérifiées, ce qui n'est pas toujours possible. De plus, je noterais que le risque qu'un développeur manque d'écrirethrow
soit très faible: le développeur doit l'oublier et la méthode environnante ne doit rien retourner, sinon le compilateur détecterait un retour manquant. C'est une façon intéressante de combattre le système de type, et cela fonctionne, cependant.Renouveler
Le réel
rethrow
peut également être écrit en fonction, mais j'ai des problèmes avec son implémentation actuelle:Il y a beaucoup de moulages inutiles commeDes moulages sont en effet nécessaires (voir commentaires):Lors du lancement / retour de nouvelles exceptions, l'ancienne est supprimée; dans le code suivant, a
MailLoginNotAvailableException
ne me permet pas de savoir quelle connexion n'est pas disponible, ce qui n'est pas pratique; de plus, les traces de pile seront incomplètes:Pourquoi le code d'origine ne lance-t-il pas ces exceptions spécialisées en premier lieu? Je soupçonne qu'il
rethrow
est utilisé comme couche de compatibilité entre un sous-système (de publipostage) et une logique commerciale (peut-être que l'intention est de masquer les détails d'implémentation comme les exceptions levées en les remplaçant par des exceptions personnalisées). Même si je conviens qu'il serait préférable d'avoir du code sans capture, comme suggéré dans la réponse de Pete Becker , je ne pense pas que vous aurez l'occasion de supprimer le codecatch
etrethrow
ici sans refactorisations majeures.la source
logAndWrap
méthode, vous pouvez lever l'exception au lieu de la renvoyer, mais conservez le type de retour(Runtime)Exception
. De cette façon, le bloc catch peut rester comme vous l'avez écrit (sans le jeter dans du code inaccessible). Mais il a l'avantage supplémentaire que vous ne pouvez pas oublierthrow
. Si vous retournez l'exception et oubliez le lancer, cela entraînera l'ingestion silencieuse de l'exception. Lancer tout en ayant un type de retour RuntimeException rendra le compilateur heureux tout en évitant ces erreurs. C'est ainsi que la goyaveThrowables.propagate
est mise en œuvre.logAndWrap
mais ne fait rien avec l'exception renvoyée? C'est intéressant. Dans une fonction qui doit renvoyer une valeur, le compilateur remarquera qu'il existe un chemin sans retour de valeur, mais cela est utile pour les méthodes qui ne renvoient rien. Merci encore.Cette
rethrow(e);
fonction viole le principe qui dit que dans des circonstances normales, une fonction reviendra, tandis que dans des circonstances exceptionnelles, une fonction lèvera une exception. Cette fonction viole ce principe en lançant une exception dans des circonstances normales. C'est la source de toute la confusion.Le compilateur suppose que cette fonction sera renvoyée dans des circonstances normales, pour autant que le compilateur puisse le dire, l'exécution peut atteindre la fin de la
retrieveUserMailConfiguration
fonction, auquel cas c'est une erreur de ne pas avoir d'return
instruction. LeRuntimeException
projeté là-bas est censé atténuer cette préoccupation du compilateur, mais c'est une façon plutôt maladroite de le faire. Une autre façon de prévenir l'function must return a value
erreur consiste à ajouter unereturn null; //to keep the compiler happy
déclaration, mais c'est tout aussi maladroit à mon avis.Donc, personnellement, je remplacerais ceci:
avec ça:
ou, mieux encore, (comme l' a suggéré coredump ) avec ceci:
Ainsi, le flux de contrôle serait rendu évident pour le compilateur, de sorte que votre version finale
throw new RuntimeException("cannot reach here");
deviendrait non seulement redondante, mais en fait même pas compilable, car elle serait signalée par le compilateur comme du code inaccessible.C'est le moyen le plus élégant et le plus simple de sortir de cette horrible situation.
la source
throw reportAndTransform(e)
un ou deux minutes après que je posté ma propre réponse. Peut-être que vous avez modifié votre question indépendamment de cela, et je m'excuse à l'avance si c'est le cas, mais sinon, donner un peu de crédit est quelque chose que les gens font habituellement.throw reportAndTransform(e)
. De nombreux modèles de conception sont des correctifs qui manquent de fonctionnalités linguistiques. C'est l'un d'eux.L'
throw
erreur a probablement été ajoutée pour contourner l'erreur "la méthode doit renvoyer une valeur" qui se produirait autrement - l'analyseur de flux de données Java est suffisamment intelligent pour comprendre qu'il n'est pasreturn
nécessaire après athrow
, mais pas après votrerethrow()
méthode personnalisée , et qu'il n'y a pas d'@NoReturn
annotation que vous pourriez utiliser pour résoudre ce problème.Néanmoins, la création d'une nouvelle exception dans du code inaccessible semble superflue. J'écrirais simplement
return null
, sachant que cela ne se produit jamais.la source
throw new...
ligne et il se plaignait de l'absence de déclaration de retour. Est-ce une mauvaise programmation? Existe-t-il une convention concernant la substitution d'une déclaration de retour inaccessible? Je vais laisser cette question sans réponse pendant un certain temps pour encourager plus de commentaires. Merci néanmoins pour votre réponse.rethrow()
méthode masque le fait que lecatch
retire l'exception. J'éviterais de faire quelque chose comme ça. De plus, bien sûr, ilrethrow()
pourrait ne pas réussir à lever l'exception, auquel cas quelque chose d'autre se produit.return null
. À l'exception, si quelqu'un à l'avenir rompt le code afin que la dernière ligne devienne accessible, il sera immédiatement clair lorsque l'exception sera levée. Si vous à la placereturn null
, vous avez maintenant unenull
valeur flottante dans votre application qui n'était pas attendue. Qui sait où dans l'applicationNullPointerException
se produira? À moins que vous n'ayez de la chance et que ce soit immédiatement après l'appel à cette fonction, il sera très difficile de trouver le vrai problème.return null
est très susceptible de masquer le bogue car vous ne pouvez pas distinguer les autres cas où il revientnull
de ce bogue.le
L'instruction indique clairement à une PERSONNE lisant le code ce qui se passe, c'est donc beaucoup mieux que de retourner null par exemple. Cela facilite également le débogage si le code est modifié de manière inattendue.
Mais
rethrow(e)
semble juste mal! Donc, dans votre cas, je pense que la refactorisation du code est une meilleure option. Voir les autres réponses (j'aime mieux coredump) pour savoir comment trier votre code.la source
Je ne sais pas s'il y a une convention.
Quoi qu'il en soit, une autre astuce serait de faire comme ça:
Permettant cela:
Et aucun artificiel
RuntimeException
n'est plus nécessaire. Même sirethrow
jamais ne retourne réellement de valeur, c'est assez bon pour le compilateur maintenant.Il retourne une valeur en théorie (signature de méthode), puis il est exempté de le faire car il lève une exception à la place.
Oui, cela peut sembler étrange, mais encore une fois - lancer un fantôme
RuntimeException
ou retourner des nulls qui ne seront jamais vus dans ce monde - ce n'est pas exactement une chose de beauté non plus.Dans un souci de lisibilité, vous pouvez renommer
rethrow
et avoir quelque chose comme:la source
Si vous supprimez
try
complètement le bloc, vous n'avez pas besoin durethrow
ou duthrow
. Ce code fait exactement la même chose que l'original:Ne vous laissez pas tromper par le fait qu'il provient de développeurs plus expérimentés. C'est de la pourriture de code, et ça arrive tout le temps. Il suffit de le réparer.
EDIT: J'ai mal interprété
rethrow(e)
comme relançant simplement l'exceptione
. Si cetterethrow
méthode fait en fait autre chose que de relancer l'exception, sa suppression modifie la sémantique de cette méthode.la source
catch(Exception)
ce qui est une odeur de code odieuse.rethrow
soit inutile (et ce n'est pas le point ici), mais vous ne pouvez pas simplement vous débarrasser du code que vous n'aimez pas et dire qu'il est équivalent à l'original alors qu'il ne l'est clairement pas. Il y a des effets secondaires dans larethrow
fonction personnalisée .