Estoy escribiendo un programa en Java donde en un momento necesito cargar una contraseña para mi almacén de claves. Solo por diversión, intenté mantener mi contraseña en Java lo más breve posible haciendo esto:
//Some code
....
KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");
{
char[] password = getPassword();
keyStore.load(new FileInputStream(keyStoreLocation), password);
keyManager.init(keyStore, password);
}
...
//Some more code
Ahora, sé que en este caso es un poco tonto. Hay muchas otras cosas que podría haber hecho, la mayoría de ellas realmente mejores (no podría haber usado una variable en absoluto).
Sin embargo, tenía curiosidad si había un caso en el que hacer esto no fuera tan tonto. Lo único que se me ocurre es si desea reutilizar nombres de variables comunes como count
, o temp
, pero las convenciones de nomenclatura y los métodos cortos hacen que sea poco probable que sea útil.
¿Hay algún caso en el que usar bloques solo para reducir el alcance variable tenga sentido?
Respuestas:
Primero, hablando de la mecánica subyacente:
En C ++ ámbito == de por vida se invocan destructores b / c a la salida del ámbito. Además, una distinción importante en C / C ++ podemos declarar objetos locales. En el tiempo de ejecución para C / C ++, el compilador generalmente asignará un marco de pila para el método que sea tan grande como sea necesario, por adelantado, en lugar de asignar más espacio de pila en la entrada a cada ámbito (que declara variables). Entonces, el compilador está colapsando o aplanando los ámbitos.
El compilador de C / C ++ puede reutilizar el espacio de almacenamiento de la pila para locales que no entren en conflicto con la vida útil (generalmente usará el análisis del código real para determinar esto en lugar del alcance, ¡b / c que es más preciso que el alcance!).
Menciono la sintaxis de Java C / C ++ b / c, es decir, llaves y alcance, al menos en parte derivada de esa familia. Y también porque C ++ surgió en los comentarios de las preguntas.
Por el contrario, no es posible tener objetos locales en Java: todos los objetos son objetos de montón, y todos los locales / formales son variables de referencia o tipos primitivos (por cierto, lo mismo es cierto para las estadísticas).
Además, en Java, el alcance y la vida útil no se equiparan exactamente: estar dentro / fuera del alcance es principalmente un concepto de tiempo de compilación que va a accesibilidad y conflictos de nombres; nada sucede realmente en Java al salir del alcance con respecto a la limpieza de variables. La recolección de basura de Java determina (el punto final de la) vida útil de los objetos.
Además, el mecanismo de código de bytes de Java (la salida del compilador de Java) tiende a promover las variables de usuario declaradas dentro de ámbitos limitados al nivel superior del método, porque no hay una función de alcance en el nivel de código de bytes (esto es similar al manejo C / C ++ del apilar). En el mejor de los casos, el compilador podría reutilizar ranuras de variables locales, pero (a diferencia de C / C ++) solo si su tipo es el mismo.
(Sin embargo, para asegurarse de que el compilador JIT subyacente en el tiempo de ejecución podría reutilizar el mismo espacio de almacenamiento de pila para dos locales localizados de forma diferente (y con ranuras diferentes), con un análisis suficiente del código de bytes).
Hablando en beneficio del programador, tendería a estar de acuerdo con otros en que un método (privado) es una mejor construcción, incluso si se usa solo una vez.
Aún así, no hay nada realmente malo en usarlo para desconfiar nombres.
Lo he hecho en raras circunstancias cuando hacer métodos individuales es una carga. Por ejemplo, al escribir un intérprete usando una
switch
declaración grande dentro de un ciclo, podría (dependiendo de los factores) introducir un bloque separado para cada unocase
para mantener los casos individuales más separados entre sí, en lugar de hacer que cada caso sea un método diferente (cada uno que solo se invoca una vez).(Tenga en cuenta que, como código en bloques, los casos individuales tienen acceso a las declaraciones "break;" y "continue;" con respecto al ciclo de cierre, mientras que los métodos requerirían retornos booleanos y el uso de condicionales de la persona que llama para obtener acceso a estos flujos de control declaraciones)
fuente
{ int x = 42; String s="blah"; } { long y = 0; }
, lalong
variable será reutilizar las ranuras de las dos variables de,x
ys
con todos los compiladores utilizados comúnmente (javac
yecj
).En mi opinión, sería más claro extraer el bloque con su propio método. Si dejó este bloque, espero ver un comentario que aclare por qué está poniendo el bloque allí en primer lugar. En ese momento, se siente más limpio tener la llamada al método
initializeKeyManager()
que mantiene la variable de contraseña limitada al alcance del método y muestra su intención con el bloque de código.fuente
Pueden ser útiles en Rust, con sus estrictas reglas de préstamo. Y, en general, en lenguajes funcionales, donde ese bloque devuelve un valor. ¿Pero en lenguajes como Java? Si bien la idea parece elegante, en la práctica rara vez se usa, por lo que la confusión al verla eclipsará la claridad potencial de limitar el alcance de las variables.
Sin embargo, hay un caso en el que encuentro esto útil: ¡en una
switch
declaración! A veces quieres declarar una variable en uncase
:Esto, sin embargo, fallará:
switch
las declaraciones son extrañas: son declaraciones de control, pero debido a su caída no introducen ámbitos.Entonces, puede usar bloques para crear los ámbitos usted mismo:
fuente
Generalmente se considera una buena práctica mantener los métodos cortos. Reducir el alcance de algunas variables puede ser una señal de que su método es bastante largo, lo suficiente como para que piense que el alcance de las variables debe reducirse. En este caso, probablemente valga la pena crear un método separado para esa parte del código.
Los casos que me parecerían problemáticos es cuando esa parte del código usa más que un puñado de argumentos. Hay situaciones en las que podría terminar con pequeños métodos que requieren una gran cantidad de parámetros (o necesitan una solución alternativa para simular la devolución de múltiples valores). La solución a ese problema en particular es crear otra clase que mantenga las diversas variables que usa e implemente implementa todos los pasos que tiene en mente en sus métodos relativamente cortos: este es un caso de uso típico para usar un patrón Builder .
Dicho esto, todas estas pautas de codificación se pueden aplicar libremente a veces. A veces hay casos extraños, donde el código puede ser más legible si lo mantiene en un método un poco más largo, en lugar de dividirlo en pequeños submétodos (o patrones de construcción). Por lo general, esto funciona para problemas que son de tamaño mediano, bastante lineales y donde demasiada división realmente dificulta la legibilidad (especialmente si necesita saltar entre demasiados métodos para comprender lo que hace el código).
Puede tener sentido, pero es muy raro.
Aquí está tu código:
Estás creando un
FileInputStream
, pero nunca lo estás cerrando.Una forma de resolver esto es usar una declaración de prueba con recursos . Alcanza el alcance
InputStream
, y también puede usar este bloque para reducir el alcance de otras variables si lo desea (dentro de lo razonable, ya que estas líneas están relacionadas):(Por cierto, también es mejor usarlo en
getDefaultAlgorithm()
lugar de hacerloSunX509
).Además, si bien el consejo de separar fragmentos de código en métodos separados es generalmente correcto. Raramente vale la pena si es solo por 3 líneas (si es que hay algo, ese es un caso en el que crear un método separado dificulta la legibilidad).
fuente
En mi humilde opinión, generalmente es algo que debe evitarse en gran medida para el código de producción porque generalmente no está tentado a hacerlo, excepto en funciones esporádicas que realizan tareas dispares. Tiendo a hacerlo en algún código de desecho utilizado para probar cosas, pero no encuentro la tentación de hacerlo en el código de producción, donde he pensado con anticipación qué debe hacer cada función, ya que, naturalmente, la función tendrá alcance limitado con respecto a su estado local.
Nunca he visto ejemplos de bloques anónimos que se usen de esta manera (sin involucrar condicionales, un
try
bloque para una transacción, etc.) para reducir el alcance de una manera significativa en una función que no plantea la pregunta de por qué no pudo se dividirá aún más en funciones más simples con alcances reducidos si realmente se benefició prácticamente desde un punto de vista de SE genuino desde los bloques anónimos. Por lo general, es un código ecléctico que está haciendo un montón de cosas poco relacionadas o muy poco relacionadas donde estamos más tentados a alcanzar esto.Como ejemplo, si está tratando de hacer esto para reutilizar una variable llamada
count
, entonces sugiere que está contando dos cosas dispares. Si el nombre de la variable será tan corto comocount
para mí, entonces tiene sentido vincularlo con el contexto de la función, que podría estar contando un tipo de cosa. Luego puede ver instantáneamente el nombre y / o la documentación de la función, vercount
y saber instantáneamente lo que significa en el contexto de lo que está haciendo la función sin analizar todo el código. A menudo no encuentro un buen argumento para que una función cuente dos cosas diferentes reutilizando el mismo nombre de variable de manera que los ámbitos / bloques anónimos sean tan atractivos en comparación con las alternativas. Eso no sugiere que todas las funciones tengan que contar solo una cosa. YO'beneficio de ingeniería para una función que reutiliza el mismo nombre de variable para contar dos o más cosas y usa bloques anónimos para limitar el alcance de cada recuento individual. Si la función es simple y clara, no es el fin del mundo tener dos variables de conteo con nombres diferentes, y la primera posiblemente tenga algunas líneas más de visibilidad de alcance de lo que idealmente se requiere. Dichas funciones generalmente no son la fuente de errores que carecen de tales bloques anónimos para reducir aún más el alcance mínimo de sus variables locales.No es una sugerencia para métodos superfluos
Esto no es para sugerirle que cree métodos con fuerza, ya sea solo para reducir el alcance. Podría decirse que es igual de malo o peor, y lo que sugiero no debería exigir la necesidad de métodos "auxiliares" privados incómodos más que la necesidad de ámbitos anónimos. Eso es pensar demasiado en el código tal como está ahora y cómo reducir el alcance de las variables que pensar en cómo resolver conceptualmente el problema a nivel de la interfaz de manera que produzca una visibilidad limpia y corta de los estados de la función local de forma natural sin una anidación profunda de bloques y más de 6 niveles de sangría. Estoy de acuerdo con Bruno en que puede obstaculizar la legibilidad del código insertando con fuerza 3 líneas de código en una función, pero eso comienza con el supuesto de que está desarrollando las funciones que crea en función de su implementación existente, en lugar de diseñar las funciones sin enredarse en implementaciones. Si lo hace de esta manera, he encontrado poca necesidad de bloques anónimos que no sirvan para nada más allá de reducir el alcance de la variable dentro de un método dado, a menos que esté tratando celosamente de reducir el alcance de una variable con solo unas pocas líneas menos de código inofensivo donde la introducción exótica de estos bloques anónimos posiblemente contribuya con la sobrecarga intelectual que eliminan.
Tratando de reducir los alcances mínimos aún más
Si valía la pena reducir los ámbitos de variables locales al mínimo absoluto, entonces debería haber una amplia aceptación de código como este:
... ya que eso causa la visibilidad mínima del estado al ni siquiera crear variables para referirse a ellas en primer lugar. No quiero parecer dogmático, pero en realidad creo que la solución pragmática es evitar los bloques anónimos cuando sea posible, así como evitar la monstruosa línea de código anterior, y si parecen tan absolutamente necesarios en un contexto de producción del perspectiva de la corrección y el mantenimiento de invariantes dentro de una función, entonces definitivamente creo que vale la pena volver a examinar cómo está organizando su código en funciones y diseñando sus interfaces. Naturalmente, si su método tiene 400 líneas de largo y el alcance de una variable es visible para 300 líneas de código más de lo necesario, eso podría ser un verdadero problema de ingeniería, pero no es necesariamente un problema para resolver con bloques anónimos.
Por lo menos, el uso de bloques anónimos en todo el lugar es exótico, no idiomático, y el código exótico conlleva el riesgo de ser odiado por otros, si no por usted mismo, años después.
La utilidad práctica de reducir el alcance
La utilidad final de reducir el alcance de las variables es permitirle obtener la administración de estado correcta y mantenerla correcta y permitirle razonar fácilmente sobre lo que hace cualquier parte de una base de código, para poder mantener invariantes conceptuales. Si la administración de estado local de una sola función es tan compleja que tiene que reducir a la fuerza el alcance con un bloque anónimo en el código que no está destinado a ser finalizado y bueno, nuevamente, eso es una señal para mí de que la función en sí misma necesita ser reexaminada . Si tiene dificultades para razonar sobre el manejo del estado de las variables en un ámbito de función local, imagine la dificultad de razonar sobre las variables privadas accesibles para cada método de una clase completa. No podemos usar bloques anónimos para reducir su visibilidad. Para mí, ayuda comenzar con la aceptación de que las variables tenderán a tener un alcance ligeramente más amplio de lo que idealmente necesitan tener en muchos idiomas, siempre que no se salga de control hasta el punto en que tenga dificultades para mantener invariantes. No es algo para resolver tanto con bloques anónimos como lo veo como aceptar desde un punto de vista pragmático.
fuente
Creo que la motivación es razón suficiente para introducir un bloqueo, sí.
Como señaló Casey, el bloque es un "olor a código" que indica que es mejor que extraiga el bloque en una función. Pero no puedes.
John Carmark escribió una nota sobre el código en línea en 2007. Su resumen final
Así que piense , haga una elección con un propósito y (opcional) deje evidencia que describa su motivación para la elección que hizo.
fuente
Mi opinión puede ser controvertida, pero sí, creo que ciertamente hay situaciones en las que usaría este tipo de estilo. Sin embargo, "solo para reducir el alcance de la variable" no es un argumento válido, porque eso es lo que ya está haciendo. Y hacer algo solo para hacerlo no es una razón válida. También tenga en cuenta que esta explicación no tiene sentido si su equipo ya ha resuelto si este tipo de sintaxis es convencional o no.
Principalmente soy un programador de C #, pero he oído que en Java, devolver una contraseña como
char[]
es para permitirle reducir el tiempo que la contraseña permanecerá en la memoria. En este ejemplo, no ayudará, ya que el recolector de basura puede recolectar la matriz desde el momento en que no está en uso, por lo que no importa si la contraseña abandona el alcance. No estoy discutiendo si es viable borrar la matriz después de que haya terminado con ella, pero en este caso, si desea hacerlo, el alcance de la variable tiene sentido:Esto es realmente similar a la declaración de prueba con recursos , porque examina el recurso y realiza una finalización después de que haya terminado. Una vez más, tenga en cuenta que no estoy discutiendo el manejo de las contraseñas de esta manera, solo que si decide hacerlo, este estilo es razonable.
La razón de esto es que la variable ya no es válida. Lo ha creado, usado e invalidado su estado para que no contenga información significativa. No tiene sentido usar la variable después de este bloque, por lo que es razonable abarcarla.
Otro ejemplo en el que puedo pensar es cuando tienes dos variables que tienen un nombre y un significado similares, pero trabajas con una y luego con otra, y quieres mantenerlas separadas. He escrito este código en C #:
Puede argumentar que simplemente puedo declarar
ILGenerator il;
en la parte superior del método, pero tampoco quiero reutilizar la variable para diferentes objetos (enfoque funcional). En este caso, los bloques facilitan la separación de los trabajos que se realizan, tanto sintácticamente como visualmente. También dice que después del bloqueo, he terminadoil
y nada debería acceder.Un argumento en contra de este ejemplo es el uso de métodos. Tal vez sí, pero en este caso, el código no es tan largo, y separarlo en diferentes métodos también necesitaría transmitir todas las variables que se usan en el código.
fuente