Me asignaron a mantener una aplicación escrita hace algún tiempo por desarrolladores más calificados. Me encontré con este código:
public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
try {
return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
rethrow(e);
}
throw new RuntimeException("cannot reach here");
}
Tengo curiosidad si el lanzamiento RuntimeException("cannot reach here")
está justificado. Probablemente me estoy perdiendo algo obvio sabiendo que este código proviene de un colega más experimentado.
EDITAR: Aquí hay que volver a lanzar el cuerpo al que se refieren algunas respuestas. Considero que no es importante en esta pregunta.
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
fuente
fuente
rethrow
no lograthrow
una excepción. (lo que puede suceder algún día, si la implementación cambia)Respuestas:
Primero, gracias por responder su pregunta y mostrarnos qué
rethrow
hace. Entonces, de hecho, lo que hace es convertir excepciones con propiedades en clases de excepciones más finas. Más sobre esto más tarde.Como no respondí realmente a la pregunta principal originalmente, aquí va: sí, generalmente es un mal estilo lanzar excepciones de tiempo de ejecución en código inalcanzable; será mejor que use afirmaciones, o incluso mejor, evite el problema. Como ya se señaló, el compilador aquí no puede estar seguro de que el código nunca salga del
try/catch
bloque. Puede refactorizar su código aprovechando que ...Los errores son valores
(Como era de esperar, es bien conocido en ir )
Usemos un ejemplo más simple, el que usé antes de su edición: así que imagine que está registrando algo y creando una excepción de contenedor como en la respuesta de Konrad . Digamos que es
logAndWrap
.En lugar de lanzar la excepción como un efecto secundario de
logAndWrap
, podría dejar que haga su trabajo como un efecto secundario y hacer que devuelva una excepción (al menos, la que aparece en la entrada). No necesita usar genéricos, solo funciones básicas:Entonces,
throw
explícitamente, y su compilador está contento:¿Qué pasa si te olvidas
throw
?Como se explica en el comentario de Joe23 , una forma de programación defensiva para garantizar que siempre se lance la excepción consistiría en hacer explícitamente un
throw new CustomWrapperException(exception)
al final delogAndWrap
, como lo hace Guava . De esa manera, sabrá que se lanzará la excepción y su analizador de tipos estará contento. Sin embargo, sus excepciones personalizadas deben ser excepciones no marcadas, lo que no siempre es posible. Además, calificaría el riesgo de que faltara un desarrollador para escribirthrow
como muy bajo: el desarrollador debe olvidarlo y el método circundante no debería devolver nada; de lo contrario, el compilador detectaría un retorno faltante. Sin embargo, esta es una forma interesante de luchar contra el sistema de tipos, y funciona.Rethrow
El actual también
rethrow
se puede escribir como una función, pero tengo problemas con su implementación actual:Hay muchos moldes inútiles comoDe hecho, se requieren moldes (ver comentarios):Al lanzar / devolver nuevas excepciones, la anterior se descarta; en el siguiente código, a
MailLoginNotAvailableException
no me permite saber qué inicio de sesión no está disponible, lo cual es inconveniente; además, los seguimientos de pila estarán incompletos:¿Por qué el código de origen no arroja esas excepciones especializadas en primer lugar? Sospecho que
rethrow
se usa como una capa de compatibilidad entre un subsistema (de correo) y la lógica de negocios (tal vez la intención es ocultar detalles de implementación como excepciones lanzadas reemplazándolas por excepciones personalizadas). Incluso si estoy de acuerdo en que sería mejor tener un código libre de capturas, como se sugiere en la respuesta de Pete Becker , no creo que tenga la oportunidad de eliminar el códigocatch
yrethrow
aquí sin mayores refactorizaciones.fuente
logAndWrap
método, podría lanzar la excepción en lugar de devolverla, pero mantenga el tipo de devolución(Runtime)Exception
. De esta forma, el bloque catch puede permanecer de la forma en que lo escribió (sin el lanzamiento de un código inalcanzable). Pero tiene la ventaja adicional de que no se puede olvidarthrow
. Si devuelve la excepción y olvida el lanzamiento, esto provocará que la excepción se trague en silencio. Lanzar mientras se tiene un tipo de retorno RuntimeException hará feliz al compilador al tiempo que evita estos errores. AsíThrowables.propagate
se implementa la guayaba .logAndWrap
pero no hace nada con la excepción devuelta? Eso es interesante. Dentro de una función que debe devolver un valor, el compilador notaría que hay una ruta sin que se devuelva ningún valor, pero esto es útil para métodos que no devuelven nada. Gracias de nuevo.Esta
rethrow(e);
función viola el principio que dice que en circunstancias normales, una función regresará, mientras que en circunstancias excepcionales, una función arrojará una excepción. Esta función viola este principio al lanzar una excepción en circunstancias normales. Esa es la fuente de toda la confusión.El compilador asume que esta función volverá en circunstancias normales, por lo que hasta donde el compilador puede decir, la ejecución puede llegar al final de la
retrieveUserMailConfiguration
función, en cuyo punto es un error no tener unareturn
declaración. SeRuntimeException
supone que arrojarlo alivia esta preocupación del compilador, pero es una forma bastante torpe de hacerlo. Otra forma de prevenir elfunction must return a value
error es agregar unareturn null; //to keep the compiler happy
declaración, pero eso es igualmente torpe en mi opinión.Entonces, personalmente, reemplazaría esto:
con este:
o, mejor aún, (como sugirió coredump ,) con esto:
Por lo tanto, el flujo de control se haría obvio para el compilador, por lo que su final
throw new RuntimeException("cannot reach here");
se volvería no solo redundante, sino que en realidad ni siquiera sería compilable, ya que el compilador lo marcaría como código inalcanzable.Esa es la forma más elegante y en realidad también más simple de salir de esta fea situación.
fuente
throw reportAndTransform(e)
un par de minutos después de que publiqué mi propia respuesta. Tal vez modificó su pregunta de forma independiente, y me disculpo de antemano si es así, pero de lo contrario, dar un poco de crédito es algo que la gente suele hacer.throw reportAndTransform(e)
. Muchos patrones de diseño son parches, faltan características del lenguaje. Este es uno de ellos.El
throw
probablemente se añadió a moverse por el "método debe devolver un valor" error que ocurriría de otra manera - los datos de Java fluyen analizador es lo suficientemente inteligente como para entender que noreturn
es necesario después de unathrow
, pero no después de su costumbrerethrow()
método, y no hay ninguna@NoReturn
anotación que podrías usar para arreglar esto.Sin embargo, crear una nueva Excepción en código inalcanzable parece superfluo. Simplemente escribiría
return null
, sabiendo que, de hecho, nunca sucede.fuente
throw new...
línea y se queja de que no hay declaración de devolución. ¿Es mala programación? ¿Existe alguna convención sobre la sustitución de una declaración de retorno inalcanzable? Dejaré esta pregunta sin respuesta por un tiempo para alentar más comentarios. Sin embargo, gracias por tu respuesta.rethrow()
método oculta el hecho de quecatch
está volviendo a lanzar la excepción. Evitaría hacer algo así. Además, por supuesto, enrethrow()
realidad podría no volver a lanzar la excepción, en cuyo caso sucede algo más.return null
. Con la excepción, si alguien en el futuro rompe el código para que la última línea sea accesible, quedará claro de inmediato cuando se lanza la excepción. Si en su lugarreturn null
, ahora tiene unnull
valor flotando en su aplicación que no se esperaba. ¿Quién sabe dóndeNullPointerException
surgirá en la aplicación ? A menos que tenga suerte y sea media después de la llamada a esta función, será muy difícil localizar el problema real.return null
es muy probable que enmascare el error porque no puede distinguir los otros casos en los que regresanull
de este error.los
declaración deja en claro a una PERSONA lee el código lo que está sucediendo, por lo que es mucho mejor que devolver nulo, por ejemplo. También facilita la depuración si el código se cambia de forma inesperada.
Sin embargo, ¡
rethrow(e)
parece estar mal! Entonces, en su caso , creo que refactorizar el código es una mejor opción. Consulte las otras respuestas (me gusta lo mejor de coredump) para obtener formas de ordenar su código.fuente
No sé si hay una convención.
De todos modos, otro truco sería hacerlo así:
Teniendo en cuenta esto:
Y ya no
RuntimeException
se necesita artificial . Aunque enrethrow
realidad nunca devuelve ningún valor, ahora es lo suficientemente bueno para el compilador.Devuelve un valor en teoría (firma del método), y luego está exento de hacerlo, ya que arroja una excepción.
Sí, puede parecer extraño, pero de nuevo, arrojar un fantasma
RuntimeException
o devolver nulos que nunca se verán en este mundo, tampoco es exactamente una belleza.En busca de la legibilidad, puede cambiar el nombre
rethrow
y tener algo como:fuente
Si elimina el
try
bloque por completo, entonces no necesita elrethrow
o elthrow
. Este código hace exactamente lo mismo que el original:No te dejes engañar por el hecho de que proviene de desarrolladores más experimentados. Esta es la descomposición del código, y ocurre todo el tiempo. Solo arréglalo.
EDITAR: leí mal
rethrow(e)
como simplemente volver a lanzar la excepcióne
. Si eserethrow
método realmente hace algo más que volver a lanzar la excepción, deshacerse de él cambia la semántica de este método.fuente
catch(Exception)
que es un olor a código odioso.rethrow
sea inútil (y este no es el punto aquí), pero no puede deshacerse del código que no le gusta y decir que es equivalente al original cuando claramente no lo es. Hay efectos secundarios en larethrow
función personalizada .