guides - java standard coding rules
Bloques con nombre para limitar el alcance variable: ¿buena idea? (14)
Me gustaría ir directamente a refactorizar a métodos más pequeños. Si un método es lo suficientemente grande como para que se rompa así, realmente es necesario dividirlo en varios métodos, si es posible.
Si bien limitar el alcance es bueno, esto no es realmente para lo que son los bloques con nombre. Es unidiomático, lo cual es muy raro que sea algo bueno.
Durante años, he estado usando bloques con nombre para limitar el alcance de las variables temporales. Nunca he visto esto hecho en otro lado, lo que me hace preguntarme si esta es una mala idea. Especialmente dado que el Eclipse IDE los señala como advertencias por defecto.
He usado esto con buenos resultados, creo, en mi propio código. Pero dado que no es idiomático hasta el punto en que los buenos programadores desconfiarán cuando lo vean, realmente tengo dos formas de hacerlo:
- evitar hacerlo, o
- promoverlo, con la esperanza de que se convierta en un idioma.
Ejemplo (dentro de un método más grande):
final Date nextTuesday;
initNextTuesday: {
GregorianCalendar cal = new GregorianCalendar();
... // About 5-10 lines of setting the calendar fields
nextTuesday = cal.getTime();
}
Aquí estoy usando un GregorianCalendar solo para inicializar una fecha, y quiero asegurarme de no volver a utilizarla accidentalmente.
Algunas personas han comentado que en realidad no necesitas nombrar el bloque. Si bien es cierto, un bloque sin formato se parece más a un error, ya que la intención no está clara. Además, nombrar algo te anima a pensar sobre la intención del bloqueo. El objetivo aquí es identificar distintas secciones de código, no dar a cada variable temporal su propio alcance.
Muchas personas han comentado que lo mejor es ir directamente a los métodos pequeños. Estoy de acuerdo en que este debería ser tu primer instinto. Sin embargo, puede haber varios factores atenuantes:
- Incluso para considerar un bloque con nombre, el código debe ser corto, código único que nunca se llamará en otro lugar.
- Un bloque con nombre es una forma rápida de organizar un método de gran tamaño sin crear un método único con una docena de parámetros. Esto es especialmente cierto cuando una clase está en flujo, y es probable que las entradas cambien de una versión a otra.
- La creación de un nuevo método alienta su reutilización, lo que puede desaconsejarse si los casos de uso no están bien establecidos. Un bloqueo con nombre es más fácil (psicológicamente, al menos) para tirar.
- Especialmente para las pruebas unitarias, es posible que deba definir una docena de objetos diferentes para las aserciones únicas, y son lo suficientemente diferentes como para que (aún) no pueda encontrar una forma de consolidarlos en una pequeña cantidad de métodos, ni puede piense en una manera de distinguirlos con nombres que no tienen una longitud de un kilómetro.
Ventajas de usar el alcance nombrado:
- No puede reutilizar accidentalmente variables temporales
- El alcance limitado le da al recopilador de basura y al compilador JIT más información sobre la intención del programador
- El nombre del bloque proporciona un comentario en un bloque de código, que me parece más legible que los comentarios abiertos
- Hace que sea más fácil refactorizar el código de un método grande en pequeños métodos, o viceversa, ya que el bloque nombrado es más fácil de separar que el código no estructurado.
Desventajas:
No es idiomático: los programadores que no han visto este uso de bloques con nombre (es decir, todos menos yo) asumen que tiene errores, ya que no pueden encontrar referencias al nombre del bloque. (Al igual que Eclipse). Y lograr que algo se vuelva idiomático es una batalla cuesta arriba.
Se puede utilizar como una excusa para los malos hábitos de programación, tales como:
- Haciendo métodos enormes y monolíticos donde varios métodos pequeños serían más legibles.
- Capas de indentación demasiado profundas para leer fácilmente.
Nota: He editado esta pregunta extensamente, en base a algunas respuestas reflexivas. ¡Gracias!
Usar bloques para limitar el alcance es una buena técnica en mi libro.
Pero dado que estás usando la etiqueta para hacer el trabajo de un comentario, ¿por qué no usar un comentario real en su lugar? Esto eliminaría la confusión sobre la etiqueta sin referencia.
He hecho esto en algunos de mis c #. Aunque no sabía que pudieras nombrar los bloques, tendré que intentarlo para ver si funciona también en c #.
Creo que el bloque de alcance puede ser una buena idea, ya que puede encapsular código específico para algo dentro de un bloque de código, donde es posible que no desee dividirlo en su propia función.
En cuanto a la desventaja de anidarlos, veo que a medida que se bloquea más de un programador que no sea de alcance.
Si tiene de 5 a 10 líneas de código que pueden colocarse de manera segura en un bloque como ese, el mismo código podría ser extraído en un método.
Esto podría parecer una diferencia semántica, pero al menos con la extracción en un método, obtendría el beneficio de la capacidad de reutilización.
El hecho de que existan no significa que deba usarse. La mayoría de las ventajas obtenidas al usar bloques con nombre se obtienen mejor utilizando un nuevo método privado.
- No podrá usar las variables temporales declaradas en el nuevo método
- El compilador GC y JIT obtendrán la misma información utilizando un nuevo método
- El uso de un nombre descriptivo para el nuevo método (usando "Date privadoNextTuesday ()" en su caso) permitirá la ventaja del código de autocomentario
- No es necesario volver a configurar el código cuando ya lo haya "prefactorado"
Además de estos beneficios, también obtiene beneficios de reutilización de códigos y acortará sus métodos largos.
Esta es la primera vez que veo a otra persona usando bloques. ¡Uf! Pensé que era el único. Sé que no lo inventé, recordé haberlo leído en alguna parte, posiblemente de mi anterior mundo C ++.
Sin embargo, no uso las etiquetas y solo comento lo que estoy haciendo.
No estoy de acuerdo con todos los tipos que te piden que lo extraigas en un método. La mayoría de las cosas que usamos en tales bloques no son realmente bloques reutilizables. Tiene sentido en una gran inicialización Y SÍ, he usado bloques para evitar errores COPY / PASTE.
BR,
~ A
Los ámbitos con nombre están técnicamente bien aquí, es solo que no se utilizan de esta manera muy a menudo. Por lo tanto, cuando alguien más venga a mantener su código en el futuro, puede no ser inmediatamente obvio por qué están allí. En mi humilde opinión, un método de ayuda privada sería una mejor opción ...
Es una buena técnica en mi libro. Administrar una gran cantidad de métodos desechables es malo y las razones por las que proporciona nombres a los bloques son buenas.
¿Cómo se ve el bytecode generado? Esa sería mi única duda. Sospecho que quita el nombre del bloque e incluso podría beneficiarse de mayores optimizaciones. Pero tendrías que verificar.
Usaría un bloque con un comentario en lugar de agregar una etiqueta allí.
Cuando veo una etiqueta, no puedo suponer que nada más está haciendo referencia al bloque.
Si cambio el comportamiento del bloque, es posible que el nombre de la etiqueta ya no sea apropiado. Pero no puedo extender la mano y cambiarlo: tendré que buscar el resto del método para determinar qué etiqueta está llamando al bloque. En ese momento, descubriré que es una etiqueta sin referencia.
El uso de un comentario es más claro en este caso, porque describe el comportamiento del bloque sin imponer ningún trabajo adicional por parte del mantenedor.
Perdón por resucitar esto, pero no vi a nadie mencionar lo que considero un punto muy importante. Veamos tu ejemplo:
final Date nextTuesday;
initNextTuesday: {
GregorianCalendar cal = new GregorianCalendar();
... // About 5-10 lines of setting the calendar fields
nextTuesday = cal.getTime();
}
La inclusión de esta lógica de inicialización aquí hace que sea más fácil de entender si está leyendo el archivo de arriba a abajo y se preocupa por cada línea. Pero piensa en cómo lees el código. ¿Comienzas a leer desde la parte superior de un archivo y continúas al final? ¡Por supuesto no! La única vez que harías eso es durante una revisión del código. En cambio, es probable que tenga un punto de partida basado en conocimientos previos, un seguimiento de pila, etc. Luego, profundice más / más en la ruta de ejecución hasta que encuentre lo que está buscando. Optimice la lectura según la ruta de ejecución, no las revisiones de código.
¿La persona que lee el código que usa nextTuesday
realmente quiere leer sobre cómo se inicializó? Yo diría que la única información que necesitan es que haya una Date
correspondiente al próximo martes. Toda esta información está contenida en su declaración. Este es un ejemplo perfecto de código que se debe dividir en un método privado, porque no es necesario comprender la lógica que al lector le importa .
final Date nextTuesday;
initNextTuesday: {
GregorianCalendar cal = new GregorianCalendar();
//1
//2
//3
//4
//5
nextTuesday = cal.getTime();
}
vs:
final Date nextTuesday = getNextTuesday();
¿Qué preferirías leer en tu camino a través de un módulo?
Me encanta la idea de usar bloque para limitar el alcance var. Muchas veces me confundieron los vars de vida corta con un gran alcance que debería desaparecer inmediatamente después del uso. El método largo + muchos vars no finales hacen que sea difícil razonar sobre la intención del codificador, especialmente cuando los comentarios son raros. Considerando gran parte de la lógica que veo en un método fueron como a continuación
Type foo(args..){
declare ret
...
make temp vars to add information on ret
...
make some more temp vars to add info on ret. not much related to above code. but previously declared vars are still alive
...
return ret
}
si los vars pueden tener un alcance menor que todo el cuerpo del método, puedo olvidarme rápidamente de la mayoría de ellos (lo bueno).
También estoy de acuerdo en que demasiadas o pocas cosas privadas conducen al código de spaghetti.
En realidad, lo que estaba buscando era algo así como el método anidado en los lenguajes funcionales, y parece que su primo en Java es un { BLOCK } (la clase interna y la expresión labmda no son para esto ...).
Sin embargo, preferiría utilizar un bloque sin nombre, ya que puede ser engañoso para las personas que intentan encontrar la referencia a la etiqueta, además de que puedo explicarlo mejor con el bloque comentado.
Para usar un método privado, lo consideraría como el próximo paso para usar bloques.
Bloques de nombre ayuda: utilizar break como una forma de Goto
Usar break como una forma civilizada de goto.
class Break {
public static void main(String args[]) {
boolean t = true;
first: {
second: {
third: {
System.out.println("Before the break.");
if (t)
break second; // break out of second block
System.out.println("This won''t execute");
}
System.out.println("This won''t execute");
}
System.out.println("This is after second block.");
}
}
}
Usar break para salir de los bucles anidados
class BreakLoop4 {
public static void main(String args[]) {
outer: for (int i = 0; i < 3; i++) {
System.out.print("Pass " + i + ": ");
for (int j = 0; j < 100; j++) {
if (j == 10)
break outer; // exit both loops
System.out.print(j + " ");
}
System.out.println("This will not print");
}
System.out.println("Loops complete.");
}
}
Enlace de origen
A veces uso bloques sin nombre para aislar elementos mutables necesarios para preparar algo inmutable. En lugar de tener una etiqueta, coloco el bloque debajo de la declaración de variable inmutable.
final String example;
{
final StringBuilder sb = new StringBuilder();
for(int i = 0; i < 100; i++)
sb.append(i);
example = sb.toString();
}
Cuando encuentro algún otro uso para el bloque, o simplemente pienso que está en el camino, lo convierto en un método.
Si esto fue malo, ¿por qué esta es una función en el idioma? Tiene un propósito, y lo has encontrado.
A menudo escribo el código exactamente como en tu ejemplo. Cuando quiera inicializar una variable, y hay un pequeño cálculo que necesita hacer para determinar qué debería ser, y eso involucra un par de variables ... entonces no quiere que esas variables anden por todo el alcance de su función, entonces un pequeño alcance para contener la inicialización funciona muy bien.
Los minicopios son una forma fácil de dividir el código en ''párrafos''. Si se divide en métodos, puede hacer que el código sea más difícil de navegar cuando esos métodos no se llaman desde ningún otro lugar y tienen un tipo de orden en serie en el que deben ejecutarse.
Siempre es un equilibrio, pero si crees que será más fácil de mantener y en realidad agrega valor a un futuro lector de tu código si todo está en línea, apúntalo.
No hay reglas duras y rápidas. A veces me canso un poco con compañeros de trabajo que ponen todo en su propio método o clase o archivo, y esto se convierte en una pesadilla para navegar. ¡Hay un buen equilibrio en alguna parte!