Considere el siguiente código:
public Object getClone(Cloneable a) throws TotallyFooException {
if (a == null) {
throw new TotallyFooException();
}
else {
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
//cant be reached, in for syntax
return null;
}
El return null;
es necesario ya que se puede detectar una excepción, sin embargo, en tal caso, ya que ya verificamos si era nulo (y supongamos que sabemos que la clase a la que estamos llamando admite la clonación), por lo que sabemos que la declaración try nunca fallará.
¿Es una mala práctica poner la declaración de retorno adicional al final solo para satisfacer la sintaxis y evitar errores de compilación (con un comentario que explique que no se alcanzará), o hay una mejor manera de codificar algo como esto para que el extra ¿La declaración de devolución es innecesaria?
Object
parámetro. Sia
no es una clase que admita elclone
método (¿o está definido en estoObject
?) O si ocurre un error durante elclone
método (o cualquier otro error que no se me ocurra en este momento), se podría lanzar una excepción y alcanzaría la declaración de devolución.Cloneable
lanzar unCloneNotSupportedException
. Fuente: javadocsAssertionError
lugar deInternalError
. Este último parece ser de uso especial si algo en la JVM sale mal. Y básicamente afirmas que no se alcanza el código.Respuestas:
Una forma más clara sin una declaración de devolución adicional es la siguiente. Yo tampoco lo captaría
CloneNotSupportedException
, pero se lo dejé ir a la persona que llama.if (a != null) { try { return a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } throw new TotallyFooException();
Casi siempre es posible jugar con el orden para terminar con una sintaxis más sencilla que la que tiene inicialmente.
fuente
AssertionError
es una clase incorporada con una intención similar aTotallyFooException
(que en realidad no existe).Definitivamente se puede alcanzar. Tenga en cuenta que solo está imprimiendo el seguimiento de pila en la
catch
cláusula.En el escenario donde
a != null
y habrá una excepción,return null
se alcanzará. Puede eliminar esa declaración y reemplazarla conthrow new TotallyFooException();
.En general * , si
null
es un resultado válido de un método (es decir, el usuario lo espera y significa algo), devolverlo como una señal de "datos no encontrados" o de que ocurrió una excepción no es una buena idea. De lo contrario, no veo ningún problema por el que no debería regresarnull
.Tomemos, por ejemplo, el
Scanner#ioException
método:En este caso, el valor devuelto
null
tiene un significado claro, cuando uso el método puedo estar seguro de que lo obtuvenull
solo porque no hubo tal excepción y no porque el método intentó hacer algo y falló.* Tenga en cuenta que a veces desea volver
null
incluso cuando el significado es ambiguo. Por ejemploHashMap#get
:En este caso,
null
puede indicar que el valornull
se encontró y se devolvió, o que el mapa hash no contiene la clave solicitada.fuente
Optional<Throwable>
oOptional<TValue>
, respectivamente. Nota: esto no es una crítica a su respuesta, sino al diseño de API de esos dos métodos. (Sin embargo, es un error de diseño bastante común, que se generaliza no solo en toda la API de Java, sino también, por ejemplo, en .NET).null
es un valor de retorno válido, regresar es una idea aún peor. En otras palabras, regresar por una situación inesperada nunca es una buena idea.null
null
null
, entoncesnull
nunca puede ser un valor de retorno "válido" en el sentido de que debe indicar algo más en lugar de un valor normal valor de retorno. En este caso, devolverlo tiene un significado especial y no veo nada malo en ese diseño (por supuesto, el usuario debe saber cómo manejar esta situación).null
es un posible valor de retorno con el que tiene que lidiar la persona que llama. Eso lo convierte en un valor válido, y tenemos la misma comprensión de "válido" aquí, ya que usted describe "válido" como "el usuario lo espera y significa algo". Pero aquí la situación es diferente de todos modos. El OP afirma por comentario del código que afirma que lareturn null;
declaración "no se puede alcanzar", lo que implica que regresarnull
es incorrecto. Debería ser unthrow new AssertionError();
lugar…Creo que
return null
es una mala práctica para el término de una rama inalcanzable. Es mejor lanzar unRuntimeException
(AssertionError
también sería aceptable) ya que para llegar a esa línea algo salió muy mal y la aplicación se encuentra en un estado desconocido. La mayoría de esto es (como arriba) porque el desarrollador se ha perdido algo (los objetos pueden ser no nulos y no clonables).Es probable que no lo use a
InternalError
menos que esté muy seguro de que el código es inalcanzable (por ejemplo, después de aSystem.exit()
), ya que es más probable que cometa un error que la VM.Solo usaría una excepción personalizada (como
TotallyFooException
) si llegar a esa "línea inalcanzable" significa lo mismo que en cualquier otro lugar donde arroje esa excepción.fuente
AssertionError
también podría ser una opción razonable para lanzar un evento "no puede suceder" .SomeJerkImplementedClonableAndStillThrewACloneNotSupportedExceptionException
. Se extenderíaRuntimeException
, obviamente.Cloneable
no implementa unclone()
método público yclone()
en el objeto es queprotected
el código anterior no se compila realmente.JerkException
, no creo que sea apreciada).Atrapó lo
CloneNotSupportedException
que significa que su código puede manejarlo. Pero después de detectarlo, literalmente no tiene idea de qué hacer cuando llega al final de la función, lo que implica que no podría manejarlo. Así que tienes razón en que es un olor a código en este caso, y en mi opinión significa que no debiste haberlo detectadoCloneNotSupportedException
.fuente
Preferiría usar
Objects.requireNonNull()
para verificar si el parámetro a no es nulo. Entonces, cuando lee el código, queda claro que el parámetro no debe ser nulo.Y para evitar las excepciones marcadas, volvería a lanzar el archivo
CloneNotSupportedException
comoRuntimeException
.Para ambos, puede agregar un texto agradable con la intención de por qué esto no debería suceder o ser el caso.
public Object getClone(Object a) { Objects.requireNonNull(a); try { return a.clone(); } catch (CloneNotSupportedException e) { throw new IllegalArgumentException(e); } }
fuente
En este tipo de situación, escribiría
public Object getClone(SomeInterface a) throws TotallyFooException { // Precondition: "a" should be null or should have a someMethod method that // does not throw a SomeException. if (a == null) { throw new TotallyFooException() ; } else { try { return a.someMethod(); } catch (SomeException e) { throw new IllegalArgumentException(e) ; } } }
Curiosamente, dice que la "declaración de prueba nunca fallará", pero aún así se tomó la molestia de escribir una declaración
e.printStackTrace();
que afirma que nunca se ejecutará. ¿Por qué?Quizás su creencia no esté tan firmemente arraigada. Eso es bueno (en mi opinión), ya que su creencia no se basa en el código que escribió, sino en la expectativa de que su cliente no violará la condición previa. Es mejor programar los métodos públicos a la defensiva.
Por cierto, su código no se compilará para mí. No puedes llamar
a.clone()
incluso si el tipo dea
esCloneable
. Al menos el compilador de Eclipse lo dice. La expresióna.clone()
da errorLo que haría por tu caso específico es
public Object getClone(PubliclyCloneable a) throws TotallyFooException { if (a == null) { throw new TotallyFooException(); } else { return a.clone(); } }
Donde
PubliclyCloneable
se define porinterface PubliclyCloneable { public Object clone() ; }
O, si absolutamente necesita que el tipo de parámetro sea
Cloneable
, al menos se compila lo siguiente.public static Object getClone(Cloneable a) throws TotallyFooException { // Precondition: "a" should be null or point to an object that can be cloned without // throwing any checked exception. if (a == null) { throw new TotallyFooException(); } else { try { return a.getClass().getMethod("clone").invoke(a) ; } catch( IllegalAccessException e ) { throw new AssertionError(null, e) ; } catch( InvocationTargetException e ) { Throwable t = e.getTargetException() ; if( t instanceof Error ) { // Unchecked exceptions are bubbled throw (Error) t ; } else if( t instanceof RuntimeException ) { // Unchecked exceptions are bubbled throw (RuntimeException) t ; } else { // Checked exceptions indicate a precondition violation. throw new IllegalArgumentException(t) ; } } catch( NoSuchMethodException e ) { throw new AssertionError(null, e) ; } } }
fuente
Cloneable
no se declaraclone
como un método público que no arroja ninguna excepción marcada. Hay una discusión interesante en bugs.java.com/view_bug.do?bug_id=4098033Los ejemplos anteriores son válidos y muy Java. Sin embargo, así es como abordaría la pregunta del OP sobre cómo manejar esa devolución:
public Object getClone(Cloneable a) throws CloneNotSupportedException { return a.clone(); }
No hay ningún beneficio por verificar
a
si es nulo. Va a NPE. La impresión de un seguimiento de pila tampoco es útil. El seguimiento de la pila es el mismo independientemente de dónde se maneje.No hay ningún beneficio en desechar el código con pruebas nulas inútiles y manejo de excepciones inútil. Al eliminar la basura, el problema de la devolución es discutible.
(Tenga en cuenta que el OP incluyó un error en el manejo de excepciones; es por eso que se necesitaba la devolución. El OP no se habría equivocado con el método que propongo).
fuente
Como han mencionado otros, en su caso esto no se aplica realmente.
Sin embargo, para responder a la pregunta, ¡los programas de tipo Lint seguramente no lo han descubierto! He visto a dos diferentes pelear por esto en una declaración de cambio.
switch (var) { case A: break; default: return; break; // Unreachable code. Coding standard violation? }
Uno se quejó de que no tener la pausa era una violación del estándar de codificación. El otro se quejó de que tenerlo era uno porque era un código inalcanzable.
Me di cuenta de esto porque dos programadores diferentes seguían revisando el código con la interrupción agregada, luego eliminada, luego agregada y eliminada, dependiendo del analizador de código que ejecutaron ese día.
Si terminas en esta situación, elige uno y comenta la anomalía, que es la buena forma que mostraste. Esa es la mejor y más importante comida para llevar.
fuente
No es 'solo para satisfacer la sintaxis'. Es un requisito semántico del lenguaje que cada ruta de código conduzca a un retorno o un lanzamiento. Este código no cumple. Si se detecta la excepción, se requiere la siguiente devolución.
No hay "mala práctica" al respecto, o sobre la satisfacción del compilador en general.
En cualquier caso, ya sea sintáctico o semántico, no tiene otra opción.
fuente
Volvería a escribir esto para tener la devolución al final. Pseudocódigo:
if a == null throw ... // else not needed, if this is reached, a is not null Object b try { b = a.clone } catch ... return b
fuente
Nadie mencionó esto todavía, así que aquí va:
public static final Object ERROR_OBJECT = ... //... public Object getClone(Cloneable a) throws TotallyFooException { Object ret; if (a == null) throw new TotallyFooException(); //no need for else here try { ret = a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); //something went wrong! ERROR_OBJECT could also be null ret = ERROR_OBJECT; } return ret; }
No me gustan los bloques
return
interiorestry
por esa misma razón.fuente
Si conoce los detalles sobre las entradas involucradas de una manera en la que sabe que la
try
declaración nunca puede fallar, ¿cuál es el punto de tenerla? Evite eltry
si está seguro de que las cosas siempre van a tener éxito (aunque es raro que pueda estar absolutamente seguro durante toda la vida útil de su base de código).En cualquier caso, el compilador desafortunadamente no es un lector de mentes. Ve la función y sus entradas, y dada la información que tiene, necesita esa
return
declaración en la parte inferior tal como la tiene.Todo lo contrario, sugeriría que es una buena práctica evitar las advertencias del compilador, por ejemplo, incluso si eso cuesta otra línea de código. No se preocupe demasiado por el recuento de líneas aquí. Establezca la confiabilidad de la función a través de pruebas y luego continúe. Simplemente fingiendo que podría omitir la
return
declaración, imagine volver a ese código un año después y luego tratar de decidir si esareturn
declaración en la parte inferior va a causar más confusión que un comentario que detalla los detalles de por qué se omitió debido a suposiciones que puede hacer sobre los parámetros de entrada. Lo más probable es que lareturn
declaración sea más fácil de manejar.Dicho esto, específicamente sobre esta parte:
try { return a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } ... //cant be reached, in for syntax return null;
Creo que hay algo un poco extraño con la mentalidad de manejo de excepciones aquí. Por lo general, desea aceptar las excepciones en un sitio donde tiene algo significativo que puede hacer en respuesta.
Puede pensar
try/catch
en un mecanismo de transacción.try
Al realizar estos cambios, si fallan y nos ramificamos en elcatch
bloque, haga esto (lo que sea que esté en elcatch
bloque) en respuesta como parte del proceso de reversión y recuperación.En este caso, simplemente imprimir un seguimiento de pila y luego verse obligado a devolver un valor nulo no es exactamente una mentalidad de transacción / recuperación. El código transfiere la responsabilidad del manejo de errores a todas las llamadas de código
getClone
para verificar manualmente las fallas. Quizás prefieras atrapar elCloneNotSupportedException
y traducirlo a otra forma de excepción más significativa y lanzarla, pero no desea simplemente tragarse la excepción y devolver un nulo en este caso, ya que no es como un sitio de recuperación de transacciones.Terminará por filtrar las responsabilidades a las personas que llaman para verificar manualmente y tratar la falla de esa manera, cuando lanzar una excepción evitaría esto.
Es como si cargaras un archivo, esa es la transacción de alto nivel. Puede que tengas un
try/catch
ahí. Durante el proceso detrying
carga de un archivo, puede clonar objetos. Si hay una falla en cualquier parte de esta operación de alto nivel (cargando el archivo), normalmente querrá lanzar excepciones hasta estetry/catch
bloque de transacciones de nivel superior para que pueda recuperarse con gracia de una falla al cargar un archivo (ya sea debido a un error en la clonación o cualquier otra cosa). Por lo tanto, generalmente no queremos tragarnos una excepción en un lugar granular como este y luego devolver un valor nulo, por ejemplo, ya que eso anularía gran parte del valor y el propósito de las excepciones. En su lugar, queremos propagar las excepciones hasta un sitio donde podamos tratarlas de manera significativa.fuente
Su ejemplo no es ideal para ilustrar su pregunta como se indica en el último párrafo:
Un mejor ejemplo sería la implementación del propio clon:
public class A implements Cloneable { public Object clone() { try { return super.clone() ; } catch (CloneNotSupportedException e) { throw new InternalError(e) ; // vm bug. } } }
Aquí nunca se debe ingresar la cláusula catch . Aún así, la sintaxis requiere arrojar algo o devolver un valor. Dado que devolver algo no tiene sentido,
InternalError
se utiliza para indicar una condición de VM grave.fuente
CloneNotSupportedException
es un "error de VM" - es perfectamente legal que a lo lance. Se podría constituir un error en su código y / o en la superclase, en el que caso de que envolver en una o posiblemente una (pero no una ) podría ser apropiada. O podría simplemente esquivar la excepción: si su superclase no admite la clonación, usted tampoco, por lo que es semánticamente apropiado.Cloneable
RuntimeException
AssertionError
InternalError
CloneNotSupportedException