Como parte de la escritura de un iterador, me encontré escribiendo el siguiente código (eliminación de errores de manejo)
public T next() {
try {
return next;
} finally {
next = fetcher.fetchNext(next);
}
}
resulta un poco más fácil de leer que
public T next() {
T tmp = next;
next = fetcher.fetchNext(next);
return tmp;
}
Sé que es un ejemplo simple, donde la diferencia en la legibilidad puede no ser tan abrumadora, pero estoy interesado en la opinión general sobre si es malo usar try-finally en casos como este donde no hay excepciones involucradas, o si realmente se prefiere cuando simplifica el código.
Si es malo: ¿por qué? Estilo, rendimiento, trampas, ...?
Conclusión Gracias a todas sus respuestas! Supongo que la conclusión (al menos para mí) es que el primer ejemplo podría haber sido más legible si fuera un patrón común, pero que no lo es. Por lo tanto, la confusión introducida al usar una construcción fuera de su propósito, junto con el flujo de excepciones posiblemente ofuscado, superará cualquier simplificación.
fuente
finally
bloques.Iterator
, donde de hecho necesita algún tipo de captación previa parahasNext()
poder trabajar. Inténtalo tú mismo.Iterator
es que necesita recuperar el valorhasNext()
(porque recuperarlo es a menudo la única forma de averiguar si existe) y devolverlonext()
como lo hizo el OP.Respuestas:
Personalmente, pensaría en la idea de la separación de consultas de comando . Cuando llegas al final, next () tiene dos propósitos: el propósito anunciado de recuperar el elemento next, y el efecto secundario oculto de mutar el estado interno de next. Entonces, lo que está haciendo es realizar el propósito anunciado en el cuerpo del método y luego agregar un efecto secundario oculto en una cláusula final, que parece ... incómodo, aunque no 'incorrecto', exactamente.
Lo que realmente se reduce a lo comprensible que es el código, diría yo, y en este caso la respuesta es "meh". Está "probando" una simple declaración de retorno y luego está ejecutando un efecto secundario oculto en un bloque de código que se supone que es para la recuperación de errores a prueba de fallas. Lo que estás haciendo es 'inteligente', y el código 'inteligente' a menudo induce a los mantenedores a murmurar: "qué ... oh, creo que lo entiendo". Mejor para las personas que leen su código para murmurar "sí, uh-huh, tiene sentido, sí ..."
Entonces, ¿qué pasa si separas la mutación de estado de las llamadas de acceso? Me imagino que el problema de legibilidad que le preocupa se vuelve discutible, pero no sé cómo eso afecta su abstracción más amplia. Algo a tener en cuenta, de todos modos.
fuente
Desde el punto de vista del estilo, creo que estas tres líneas:
... son más obvios y más cortos que un bloque try / finally. Dado que no espera que se produzcan excepciones, el uso de un
try
bloqueo solo confundirá a las personas.fuente
Eso dependería del propósito del código dentro del bloque finalmente. El ejemplo canónico es cerrar un flujo después de leerlo / escribirlo, algún tipo de "limpieza" que siempre debe hacerse. Restaurar un objeto (en este caso un iterador) a un estado válido en mi humilde opinión también cuenta como limpieza, por lo que no veo ningún problema aquí. Si OTOH estaba usando
return
tan pronto como se encontró su valor de retorno, y agregando una gran cantidad de código no relacionado en el bloque finalmente, entonces oscurecería el propósito y lo haría todo menos comprensible.No veo ningún problema en usarlo cuando "no hay excepciones involucradas". Es muy común usarlo
try...finally
sin uncatch
código cuando el código solo puede arrojarseRuntimeException
y no planeas manejarlo. A veces, finalmente es solo una salvaguarda, y usted sabe por la lógica de su programa que nunca se lanzará una excepción (la clásica condición de "esto nunca debería suceder").Dificultades: cualquier excepción planteada dentro del
try
bloque hará que elfinally
bock se ejecute. Eso puede ponerte en un estado inconsistente. Entonces, si su declaración de devolución fuera algo como:y este código generó una excepción,
fetchNext
aún se ejecutaría. OTOH si lo codificó como:entonces no correría. Sé que está asumiendo que el código de prueba nunca puede generar ninguna excepción, pero para casos más complejos que esto, ¿cómo puede estar seguro? Si su iterador fuera un objeto de larga duración, eso continuaría existiendo incluso si ocurriera un error irrecuperable en el hilo actual, entonces sería importante mantenerlo en un estado válido en todo momento. De lo contrario, realmente no importa mucho ...
Rendimiento: sería interesante descompilar dicho código para ver cómo funciona bajo el capó, pero no conozco lo suficiente de la JVM como para adivinarlo ... Las excepciones suelen ser "excepcionales", por lo que el código que maneja no es necesario que estén optimizados para la velocidad (de ahí el consejo de nunca usar excepciones en el flujo de control normal de sus programas), pero no lo sé
finally
.fuente
finally
de esta manera, entonces? Quiero decir, como dices, el código termina teniendo consecuencias no deseadas; peor aún, probablemente ni siquiera pienses en excepciones.null
código con errores establece una referencia a una secuencia abierta ; intentar leerlo generará una excepción, intentar cerrarlo en elfinally
bloque generará otra excepción. Además, esta es una situación en la que el estado del cálculo no importa, desea cerrar la transmisión independientemente de si se produjo una excepción. Puede haber otras situaciones como esa también. Por esta razón, lo estoy tratando como una trampa para los usos válidos, en lugar de una recomendación para nunca usarlo.La declaración del título: "... finalmente cláusula para hacer el trabajo después del regreso ..." es falsa. El último bloque ocurre antes de que regrese la función Ese es el punto final de hecho.
Lo que está abusando aquí es el orden de evaluación, donde el valor de next se almacena para regresar antes de mutarlo. No es una práctica común y, en mi opinión, es incorrecta, ya que hace que el código no sea secuencial y, por lo tanto, sea mucho más difícil de seguir.
El uso previsto de los bloques finalmente es para el manejo de excepciones, donde deben ocurrir algunas consecuencias independientemente de las excepciones lanzadas, por ejemplo, limpiar algunos recursos, cerrar la conexión de base de datos, cerrar un archivo / socket, etc.
fuente
Sería muy cuidadoso, porque (en general, no en su ejemplo) parte en try puede arrojar una excepción, y si se descarta, no se devolverá nada y si realmente tiene la intención de ejecutar lo que está finalmente después del retorno, lo hará ejecutar cuando no lo quisieras.
Y otra lata de gusanos es que, en general, alguna acción útil finalmente puede generar excepciones, por lo que puede terminar con un método realmente desordenado.
Debido a esto, alguien que lo lea debe pensar en estos problemas, por lo que es más difícil de entender.
fuente