¿Debemos eliminar las variables locales si podemos?

91

Por ejemplo, para mantener una CPU en Android, puedo usar código como este:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

pero creo que las variables locales powerManager y wakeLock pueden eliminarse:

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

escena similar aparece en la vista de alerta de iOS, por ejemplo: desde

UIAlertView *alert = [[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil];
[alert show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

a:

[[[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil] show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

¿Es una buena práctica eliminar una variable local si solo se usa una vez en el alcance?

    
pregunta ggrr 04.01.2017 - 04:35
fuente

14 respuestas

229

El código se lee mucho más a menudo de lo que está escrito, por lo que debe tener compasión de la pobre alma que tendrá que leer el código dentro de seis meses (puede ser usted) y luchar por el código más claro y fácil de entender. . En mi opinión, la primera forma, con variables locales, es mucho más comprensible. Veo tres acciones en tres líneas, en lugar de tres acciones en una línea.

Y si crees que estás optimizando algo al deshacerte de las variables locales, no lo estás. Un compilador moderno colocará powerManager en un registro 1 , ya sea que se use una variable local o no, para llamar al método newWakeLock . Lo mismo es cierto para wakeLock . Así que terminas con el mismo código compilado en cualquier caso.

1 Si hubo una gran cantidad de códigos intermedios entre la declaración y el uso de la variable local, podría ir a la pila, pero es un detalle menor.

    
respondido por el Tony BenBrahim 04.01.2017 - 07:49
fuente
90

Algunos comentarios altamente a favor expresaron esto, pero ninguna de las respuestas que vi lo hizo, así que lo agregaré como respuesta. Su factor principal para decidir este problema es:

Debugabilidad

A menudo, los desarrolladores dedican mucho más tiempo y esfuerzo a la depuración que al escribir código.

Con las variables locales, puede:

  • Punto de interrupción en la línea donde está asignado (con todas las demás decoraciones de punto de interrupción, como punto de interrupción condicional, etc.)

  • Inspeccionar / mirar / imprimir / cambiar el valor de la variable local

  • Los problemas de captura que surgen debido a los lanzamientos.

  • Tiene rastros de pila legibles (la línea XYZ tiene una operación en lugar de 10)

Sin las variables locales, todas las tareas anteriores son mucho más difíciles, extremadamente difíciles o totalmente imposibles, dependiendo de tu depurador.

Como tal, sigue la infame máxima (escribe el código de la forma en que lo harías como si el siguiente desarrollador que lo mantuviera después de ti fuera un loco loco que sabe dónde vives), y errá por el lado de más fácil debugability , que significa usar variables locales .

    
respondido por el DVK 04.01.2017 - 16:20
fuente
45

Solo si hace que el código sea más fácil de entender. En tus ejemplos, creo que hace que sea más difícil de leer.

Eliminar las variables en el código compilado es una operación trivial para cualquier compilador respetable. Puedes inspeccionar la salida por ti mismo para verificar.

    
respondido por el whatsisname 04.01.2017 - 04:54
fuente
28

Su pregunta "¿es una buena práctica eliminar una variable local si solo se usa una vez en el alcance?" Está probando los criterios equivocados. La utilidad de una variable local no depende de la cantidad de veces que se usa, sino de si hace que el código sea más claro. Etiquetar valores intermedios con nombres significativos puede mejorar la claridad en situaciones como la que presenta, ya que divide el código en partes más pequeñas y más digeribles.

En mi opinión, el código no modificado que presentaste es más claro que el código modificado y, por lo tanto, no debe modificarse. De hecho, consideraría sacar una variable local de tu código modificado para mejorar la claridad.

No esperaría ningún impacto en el rendimiento de las variables locales, e incluso si hay uno, es probable que sea demasiado pequeño para que valga la pena considerarlo, a menos que el código esté en un circuito muy cerrado en una parte crítica del programa.

    
respondido por el Jack Aidley 04.01.2017 - 10:02
fuente
14

Tal vez.

Personalmente, dudaría en eliminar las variables locales si se trata de un encasillado. Encuentro su versión compacta menos legible ya que la cantidad de paréntesis comienza a acercarse al límite mental que tengo. Incluso introduce un nuevo conjunto de corchetes que no era necesario en la versión un poco más detallada con una variable local.

El resaltado de sintaxis proporcionado por el editor de código podría mitigar dichas preocupaciones en cierta medida.

Para mí, este es el mejor compromiso:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc").acquire();

Manteniendo así una variable local y abandonando la otra. En C # utilizaría la palabra clave var en la primera línea, ya que la transmisión de tipos ya proporciona la información de tipo.

    
respondido por el 9Rune5 04.01.2017 - 05:03
fuente
12

Aunque acepto la validez de las respuestas que favorecen a las variables locales, voy a interpretar al defensor del diablo y presentar una opinión contraria.

Personalmente, estoy en contra del uso de variables locales únicamente para fines de documentación, aunque esa razón en sí misma indica que la práctica tiene un valor: las variables locales efectivamente seudocontiguen el código.

En su caso, el problema principal es la sangría y no la ausencia de variables. Podría (y debería) dar formato al código de la siguiente manera:

((PowerManager)getSystemService(POWER_SERVICE))
        .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag")
        .acquire();

Compare eso con la versión con variables locales:

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

Con las variables locales: los nombres agregan cierta información al lector y los tipos están claramente especificados, pero las llamadas al método real son menos visibles y (en mi opinión) no se lee tan claramente.

Sin usar variables locales: es más conciso y las llamadas de método son más visibles, pero puede solicitar más información sobre lo que se está devolviendo. Sin embargo, algunos IDE pueden mostrarte esto.

Tres comentarios adicionales:

  1. En mi opinión, agregar código que no tiene un uso funcional a veces puede ser confuso ya que el lector debe darse cuenta de que, de hecho, no tiene un uso funcional y simplemente está ahí para la "documentación". Por ejemplo, si este método tenía 100 líneas de longitud, no sería obvio que las variables locales (y, por lo tanto, el resultado de la llamada al método) no fueran necesarias en algún momento posterior, a menos que lea el método completo.

  2. Agregar las variables locales y los medios debe especificar sus tipos, y esto introduce una dependencia en el código que de otra manera no estaría allí. Si el tipo de retorno de los métodos cambiara trivialmente (por ejemplo, se le cambió el nombre), tendría que actualizar su código, mientras que sin las variables locales no lo haría.

  3. La depuración será más fácil con las variables locales. Pero la solución para eso es arreglar las deficiencias en los depuradores, no cambiar el código.

respondido por el rghome 04.01.2017 - 16:25
fuente
6

Hay una tensión de motivaciones aquí. La introducción de variables temporales puede hacer que el código sea más fácil de leer. Pero también pueden prevenir, y hacer que sea más difícil ver, otras refactorizaciones posibles, como Extract Method , y Replace Temp con Query . Estos últimos tipos de refactorizaciones, cuando son apropiados, a menudo proporcionan beneficios mucho mayores de lo que lo haría la variable temporal.

Sobre las motivaciones para estas últimas refactorizaciones, escribe Fowler :

  

"El problema con las temps es que son temporales y locales. Debido a que solo se pueden ver en el contexto del método en el que se usan, las temps tienden a fomentar métodos más largos, porque esa es la única manera en que se puede llegar al Temp. Al reemplazar la temperatura por un método de consulta, cualquier método de la clase puede obtener la información. Eso ayuda mucho a crear un código más limpio para la clase ".

Entonces, sí, use temps si hacen que el código sea más legible, especialmente si esa es una norma local para usted y su equipo. Pero tenga en cuenta que hacerlo a veces puede hacer que sea más difícil detectar mejoras alternativas más grandes. Si puede desarrollar su capacidad de percibir cuándo vale la pena ir sin esos temporales y sentirse más cómodo al hacerlo, entonces probablemente sea algo bueno.

FWIW Personalmente evité leer el libro "Refactoring" de Fowler durante diez años porque imaginé que no había mucho que decir sobre temas tan sencillos. Estaba completamente equivocado. Cuando finalmente lo leí, cambió mis prácticas diarias de codificación, para mejor.

    
respondido por el Jonathan Hartley 05.01.2017 - 09:28
fuente
3

Bueno, es una buena idea eliminar las variables locales si puedes para que el código sea más legible. Esa larga frase tuya no es legible, pero luego sigue un estilo OO bastante detallado. Si pudieras reducirlo a algo como

acquireWakeLock(POWER_SERVICE, PARTIAL, "abc");

entonces diría que probablemente sería una buena idea. Quizás puedas introducir métodos de ayuda para reducirlo a algo similar; Si un código como este aparece varias veces, entonces podría valer la pena.

    
respondido por el leftaroundabout 04.01.2017 - 18:09
fuente
2

Consideremos la Ley de Demeter aquí. En el artículo de Wikipedia sobre LoD se indica lo siguiente:

  

La Ley de Demeter para funciones requiere que un método m de un objeto O solo pueda invocar los métodos de los siguientes tipos de objetos: [2]

     
  1. O mismo
  2.   
  3. parámetros de m
  4.   
  5. Cualquier objeto creado / creado dentro de m
  6.   
  7. objetos componentes directos de O
  8.   
  9. Una variable global, accesible por O, en el alcance de m
  10.   

Una de las consecuencias de seguir esta Ley es que debe evitar invocar métodos de objetos devueltos por otros métodos en una larga cadena de puntos, como se muestra en el segundo ejemplo anterior:

((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag").acquire();

Es realmente difícil entender qué está haciendo esto. Para comprenderlo, debe comprender lo que hace cada procedimiento y luego descifrar a qué función se llama en qué objeto. El primer bloque de código

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock=powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

muestra claramente lo que estás tratando de hacer: estás obteniendo un objeto PowerManager, obteniendo un objeto WakeLock de PowerManager y luego adquiriendo el wakeLock. Lo anterior sigue la regla # 3 de LoD: está creando una instancia de estos objetos dentro de su código y, por lo tanto, puede usarlos para hacer lo que necesita.

Quizás otra forma de pensar es en recordar que al crear software, debe escribir para mayor claridad, no para abreviar. El 90% de todo el desarrollo de software es mantenimiento. Nunca escriba un código que no le gustaría mantener.

La mejor de las suertes.

    
respondido por el Bob Jarvis 04.01.2017 - 13:35
fuente
2

Una cosa a tener en cuenta es que el código se lee con frecuencia, a diferentes "profundidades". Este código:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

es fácil de "skim". Son 3 declaraciones. Primero se nos ocurre un PowerManager . Luego se nos ocurre un WakeLock . Entonces nosotros acquire el wakeLock . Puedo ver eso realmente fácilmente solo mirando el comienzo de cada línea; las asignaciones de variables simples son realmente fáciles de reconocer parcialmente como "Escriba varName = ..." y simplemente repasar mentalmente el "...". De manera similar, la última afirmación obviamente no es la forma de la asignación, pero solo involucra dos nombres, por lo que la "esencia principal" es inmediatamente aparente. A menudo, eso es todo lo que necesito saber si estuviera tratando de responder "¿qué hace este código?" a un nivel alto.

Si estoy persiguiendo un error sutil que creo que está aquí, obviamente, tendré que repasar esto con mucho más detalle, y realmente recordaré los "..." s. Pero la estructura de sentencias por separado aún me ayuda a hacer esa enunciación a la vez (especialmente útil si necesito profundizar en la implementación de las cosas a las que se llama en cada enunciado; cuando vuelvo, entiendo completamente "una unidad" y luego puede pasar a la siguiente declaración).

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

Ahora es todo una declaración. La estructura de nivel superior no es tan fácil de leer por encima; en la versión original del OP, sin saltos de línea ni sangría para comunicar visualmente cualquier estructura, habría tenido que contar paréntesis para decodificarla en una secuencia de 3 pasos. Si algunas de las expresiones de varias partes están anidadas una dentro de la otra en lugar de organizarse como una cadena de llamadas a métodos, entonces aún podría parecer similar a esto, así que debo tener cuidado al confiar en eso sin contar los paréntesis. Y si realmente confío en la muesca y simplemente me dirijo a la última cosa como el supuesto punto de todo esto, ¿qué me dice .acquire() por sí solo?

Aunque a veces, eso puede ser lo que quieras. Si aplico tu transformación a mitad de camino y escribí:

WakeLock wakeLock =
     ((PowerManeger)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLockTage");
wakeLock.acquire();

Ahora esto se comunica a un rápido skim "obtener un WakeLock , luego acquire it". Incluso más simple que la primera versión. Es inmediatamente obvio que lo que se adquiere es un WakeLock . Si obtener el PowerManager es solo un sub-detalle que es bastante insignificante hasta el punto de este código, pero el wakeLock es importante, entonces en realidad podría ayudar a enterrar el PowerManager por lo que es natural echarle un vistazo si solo estás tratando de obtener rápidamente una idea de lo que hace este código. Y al no nombrarlo, se comunica que se solo se usa una vez, y a veces es lo que es importante (si el resto del alcance es muy largo, tendría que leer todo eso) para saber si alguna vez se usa de nuevo, aunque el uso de sub-ámbitos explícitos puede ser otra forma de solucionarlo, si su idioma los admite).

Lo que hace que todo dependa del contexto y de lo que quieras comunicar. Al igual que con escribir prosa en lenguaje natural, hay siempre muchas formas de escribir una determinada pieza de código que son básicamente equivalentes en términos de contenido de información. Al igual que con la escritura en prosa en lenguaje natural, la forma de elegir entre ellos generalmente no debería ser no aplicar reglas mecanicistas como "eliminar cualquier variable local que solo ocurra una vez". Más bien, cómo elige escribir su código enfatizará ciertas cosas y desestimar otras. Debería esforzarse por tomar esas decisiones conscientemente (incluida la opción de escribir un código menos legible por razones técnicas a veces), en función de lo que realmente desea enfatizar. Especialmente piense en qué servirá a los lectores que solo necesitan "obtener la esencia" de su código (en varios niveles), ya que eso sucederá con mucha más frecuencia que una lectura muy cercana de expresión por expresión.

    
respondido por el Ben 06.01.2017 - 05:13
fuente
2

Esto es incluso un antipattern con su propio nombre: Train Wreck . Ya se han indicado varias razones para evitar el reemplazo:

  • Más difícil de leer
  • Más difícil de depurar (tanto para ver variables como para detectar ubicaciones de excepciones)
  • Violación de la Ley de Demeter (LoD)

Considera si este método sabe demasiado de otros objetos. El encadenamiento de métodos es una alternativa que podría ayudarlo a reducir el acoplamiento.

También recuerda que las referencias a objetos son realmente baratas.

    
respondido por el Borjab 05.01.2017 - 11:22
fuente
1

La pregunta planteada es "¿Debemos eliminar las variables locales si podemos"?

No, no debes eliminar solo porque puedes.

Debes seguir las pautas de codificación en tu tienda.

En su mayor parte, las variables locales hacen que el código sea más fácil de leer y depurar.

Me gusta lo que hiciste con PowerManager powerManager . Para mí, una minúscula de la clase significa que es solo un uso del tiempo.

Cuando no debería, es si la variable va a conservar recursos costosos. Muchos idiomas tienen sintaxis para una variable local que necesita ser limpiada / liberada. En C # está utilizando.

using(SQLconnection conn = new SQLconnnection())
{
    using(SQLcommand cmd = SQLconnnection.CreateCommand())
    {
    }
}
    
respondido por el paparazzo 05.01.2017 - 17:47
fuente
1

Un aspecto importante no se ha mencionado en las otras respuestas: cada vez que agregas una variable, introduces un estado mutable. Esto suele ser algo malo porque hace que su código sea más complejo y, por lo tanto, más difícil de entender y de probar. Por supuesto, cuanto menor sea el alcance de la variable, menor será el problema.

En realidad, lo que desea no es una variable cuyo valor se pueda modificar, sino una constante temporal. Por lo tanto, considere expresar esto en su código si su idioma lo permite. En Java puede usar final y en C ++ puede usar const . En la mayoría de los lenguajes funcionales, este es el comportamiento predeterminado.

Es cierto que las variables locales son el tipo menos dañino de estado mutable. Las variables miembro pueden causar muchos más problemas y las variables estáticas son aún peores. Aún así, me parece importante expresar con la mayor precisión posible en su código lo que se supone que debe hacer. Y hay una gran diferencia entre una variable que podría modificarse más adelante y un mero nombre para un resultado intermedio. Entonces, si su idioma le permite expresar esta diferencia, simplemente hágalo.

    
respondido por el Frank Puffer 05.01.2017 - 10:01
fuente
-1

Para mí, ninguno de esos es más difícil de leer, siempre y cuando hagas una sangría adecuada. Si el uso de una variable intermedia hiciera las cosas más claras, el patrón de creación no existiría.

Por supuesto, la función principal del patrón de construcción no es deshacerse de la variable intermedia, sino ensamblar con fluidez una variable compleja. Pero sí elimina la variable intermedia en el camino.

Los problemas con las variables intermedias son los siguientes:

  1. Nombrarlos correctamente
  2. Cuando lea su código, veré una variable local declarada aquí. Comenzaré a preguntarme si es una variable intermedia o si se usará en otro lugar.

Sin embargo, hay un beneficio interesante: puede inspeccionar los resultados intermedios.

Si piensa que es interesante poder ver el estado de la variable intermediaria cuando la depuración es interesante, entonces use una. Esta es prácticamente la única razón por la que uso variables intermedias.

    
respondido por el Walfrat 04.01.2017 - 11:46
fuente

Lea otras preguntas en las etiquetas