¿"Las variables deben vivir en el menor alcance posible" incluye el caso "las variables no deberían existir si es posible"?


32

De acuerdo con la respuesta aceptada sobre "¿ Razonamiento para preferir las variables locales a las variables de instancia? ", Las variables deben vivir en el menor alcance posible.
Simplifique el problema en mi interpretación, significa que deberíamos refactorizar este tipo de código:

public class Main {
    private A a;
    private B b;

    public ABResult getResult() {
        getA();
        getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      a = SomeFactory.getA();
    }

    private getB() {
      b = SomeFactory.getB();
    }
}

en algo como esto:

public class Main {
    public ABResult getResult() {
        A a = getA();
        B b = getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

pero de acuerdo con el "espíritu" de "las variables deben vivir en el alcance más pequeño posible", ¿no es "nunca tener variables" tener un alcance más pequeño que "tener variables"? Así que creo que la versión anterior debería ser refactorizada:

public class Main {
    public ABResult getResult() {
        return ABFactory.mix(getA(), getB());
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

así que eso getResult()no tiene ninguna variable local en absoluto. ¿Es eso cierto?


72
Crear variables explícitas conlleva el beneficio de tener que nombrarlas. La introducción de algunas variables puede convertir rápidamente un método opaco en uno legible.
Jared Goguen

11
Honestamente, un ejemplo en términos de nombres de variables ayb es demasiado artificial para demostrar el valor de las variables locales. Afortunadamente, obtuviste buenas respuestas de todos modos.
Doc Brown

3
En su segundo fragmento, sus variables no son realmente variables . Son efectivamente constantes locales, porque no las modifica. El compilador de Java los tratará de la misma manera si los definió como finales, porque están en el ámbito local y el compilador sabe lo que sucederá con ellos. Es una cuestión de estilo y opinión, si realmente debe usar finalpalabras clave o no.
Hyde

2
@JaredGoguen Crear variables explícitas conlleva la carga de tener que nombrarlas y el beneficio de poder nombrarlas.
Bergi

2
Absolutamente cero de las reglas de estilo de código como "las variables deben vivir en el menor alcance posible" son universalmente aplicables sin pensar. Como tal, nunca querrás basar una decisión de estilo de código en el razonamiento del formulario en esta pregunta, donde tomas una pauta simple y la extiendes a un área menos obviamente cubierta ("las variables deben tener pequeños alcances si es posible" -> "las variables no deberían existir si es posible"). O bien hay buenas razones para apoyar su conclusión específicamente, o su conclusión es una extensión inapropiada; De cualquier manera, no necesita la guía original.
Ben

Respuestas:


110

No. Hay varias razones por las cuales:

  1. Las variables con nombres significativos pueden hacer que el código sea más fácil de comprender.
  2. Romper fórmulas complejas en pasos más pequeños puede hacer que el código sea más fácil de leer.
  3. Almacenamiento en caché.
  4. Retener referencias a objetos para que puedan usarse más de una vez.

Y así.


22
También vale la pena mencionar: el valor se almacenará en la memoria independientemente, por lo que en realidad termina con el mismo alcance de todos modos. ¡También podría nombrarlo (por las razones que Robert menciona arriba)!
Maybe_Factor

55
@Maybe_Factor La directriz no está realmente allí por razones de rendimiento; se trata de lo fácil que es mantener el código. Pero eso todavía significa que los locales significativos son mejores que una gran expresión, a menos que simplemente esté componiendo funciones bien nombradas y diseñadas (por ejemplo, no hay razón para hacerlo var taxIndex = getTaxIndex();).
Luaan

16
Todos son factores importantes. A mi modo de verlo: la brevedad es un objetivo; la claridad es otra; la robustez es otra; el rendimiento es otro; y así. Ninguno de estos son objetivos absolutos , y a veces entran en conflicto. Entonces, nuestro trabajo es encontrar la mejor manera de equilibrar esos objetivos.
Gidds

8
El compilador se encargará de 3 y 4.
Deja de dañar a Monica el

9
El número 4 puede ser importante para la corrección. Si el valor de una llamada de función puede cambiar (p now(). Ej .), Eliminar la variable y llamar al método más de una vez puede generar errores. Esto puede crear una situación que es realmente sutil y difícil de depurar. Puede parecer obvio, pero si está en una misión de refactorización ciega para eliminar variables, es fácil terminar presentando fallas.
JimmyJames

16

De acuerdo, deben evitarse las variables que no son necesarias y que no mejoran la legibilidad del código. Cuantas más variables estén dentro del alcance en un punto dado en el código, más complejo será entender ese código.

Realmente no veo el beneficio de las variables ay ben su ejemplo, por lo que escribiría la versión sin variables. Por otro lado, la función es tan simple en primer lugar que no creo que importe demasiado.

Se vuelve más problemático cuanto más larga sea la función y más variables estén dentro del alcance.

Por ejemplo si tienes

    a=getA();
    b=getB();
    m = ABFactory.mix(a,b);

En la parte superior de una función más grande, aumenta la carga mental de comprender el resto del código al introducir tres variables en lugar de una. Usted tiene que leer a través del resto del código para ver si ao bse utilizan de nuevo. Los locales que tienen un alcance mayor de lo que necesitan son malos para la legibilidad general.

Por supuesto, en el caso en que es necesaria una variable (por ejemplo, para almacenar un resultado temporal), o cuando una variable que hace mejorar la legibilidad del código, a continuación, se debe tener.


2
Por supuesto, en el momento en que un método sea tan largo que el número de locales sea demasiado grande para que sea fácil de mantener, es probable que desee dividirlo de todos modos.
Luaan

44
Uno de los beneficios de las variables "extrañas" var result = getResult(...); return result;es que puede establecer un punto de interrupción returny aprender qué resultes exactamente .
Joker_vD

55
@Joker_vD: un depurador digno de su nombre debería permitirle hacer todo eso de todos modos (ver los resultados de una llamada a función sin almacenarlo en una variable local + establecer un punto de interrupción al salir de la función).
Christian Hackl

2
@ChristianHackl Muy pocos depuradores le permiten hacer eso sin configurar posiblemente relojes complejos que toman tiempo para agregar (y si las funciones tienen efectos secundarios pueden romper todo). Tomaré la versión con variables para depurar cualquier día de la semana.
Gabe Sechan

1
@ChristianHackl y para seguir construyendo sobre eso, incluso si el depurador no le permite hacerlo de forma predeterminada porque el depurador está horriblemente diseñado, simplemente puede modificar el código local en su máquina para almacenar el resultado y luego verificarlo en el depurador . Todavía no hay razón para almacenar un valor en una variable solo para ese propósito.
El gran pato

11

Además de las otras respuestas, me gustaría señalar algo más. El beneficio de mantener pequeño el alcance de una variable no es solo reducir la cantidad de código que tiene acceso sintácticamente a la variable, sino también la cantidad de posibles rutas de flujo de control que pueden modificar potencialmente una variable (ya sea asignándole un nuevo valor o llamando un método de mutación en el objeto existente contenido en la variable).

Las variables de ámbito de clase (instancia o estática) tienen significativamente más rutas de flujo de control posibles que las variables de ámbito local porque pueden ser mutadas por métodos, que pueden llamarse en cualquier orden, cualquier número de veces y, a menudo, por código fuera de la clase .

Echemos un vistazo a su getResultmétodo inicial :

public ABResult getResult() {
    getA();
    getB();
    return ABFactory.mix(this.a, this.b);
}

Ahora, los nombres getAy getBpueden sugerir que se asignarán a this.ay this.b, no podemos saberlo con solo mirarlo getResult. Por lo tanto, es posible que los valores this.ay this.bpasados ​​al mixmétodo provengan del estado del thisobjeto anterior al que getResultse invocó, lo cual es imposible de predecir ya que los clientes controlan cómo y cuándo se invocan los métodos.

En el código revisado con el local ay las bvariables, está claro que hay exactamente un flujo de control (sin excepción) desde la asignación de cada variable hasta su uso, porque las variables se declaran justo antes de ser utilizadas.

Por lo tanto, existe un beneficio significativo al mover variables (modificables) del ámbito de clase al ámbito local (así como mover variables (modificables) desde el exterior de un bucle hacia el interior), ya que simplifica el razonamiento del flujo de control.

Por otro lado, eliminar variables como en su último ejemplo tiene menos beneficio, porque realmente no afecta el razonamiento del flujo de control. También pierde los nombres dados a los valores, lo que no sucede cuando simplemente mueve una variable a un ámbito interno. Esta es una compensación que debe tener en cuenta, por lo que eliminar variables podría ser mejor en algunos casos y peor en otros.

Si no desea perder los nombres de las variables, pero aún así quiere reducir el alcance de las variables (en el caso de que se utilicen dentro de una función más grande), puede considerar ajustar las variables y sus usos en una declaración de bloque ( o moviéndolos a su propia función ).


2

Esto depende un poco del lenguaje, pero diría que uno de los beneficios menos obvios de la programación funcional es que alienta al programador y lector de código a no necesitarlos. Considerar:

(reduce (fn [map string] (assoc map string (inc (map string 0))) 

O algo de LINQ:

var query2 = mydb.MyEntity.Select(x => x.SomeProp).AsEnumerable().Where(x => x == "Prop");

O Node.js:

 return visionFetchLabels(storageUri)
    .then(any(isCat))
    .then(notifySlack(secrets.slackWebhook, `A cat was posted: ${storageUri}`))
    .then(logSuccess, logError)
    .then(callback)

La última es una cadena de invocación de una función en el resultado de una función anterior, sin ninguna variable intermedia. Introducirlos lo haría mucho menos claro.

Sin embargo, la diferencia entre el primer ejemplo y los otros dos es el orden implícito de operación . Esto puede no ser el mismo que el orden en que se calcula realmente, pero es el orden en el que el lector debe pensarlo. Para los dos segundos esto se deja de izquierda a derecha. Para el ejemplo de Lisp / Clojure es más como de derecha a izquierda. Debe ser un poco cauteloso al escribir código que no esté en la "dirección predeterminada" para su idioma, y ​​las expresiones "intermedias" que mezclan los dos definitivamente deben evitarse.

El operador de tubería de F # |>es útil en parte porque le permite escribir cosas de izquierda a derecha que de otro modo tendrían que ser de derecha a izquierda.


2
He usado el guión bajo como mi nombre de variable en expresiones LINQ simples o lambdas. myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
Graham

No estoy seguro de que esto responda la pregunta de OP en lo más mínimo ...
AC

1
¿Por qué los votos negativos? Parece una buena respuesta para mí, incluso si no responde directamente a la pregunta, sigue siendo información útil
reggaeguitar

1

Yo diría que no, porque debería leer "el alcance más pequeño posible" como "entre los ámbitos existentes o los que son razonables de agregar". De lo contrario, implicaría que debe crear ámbitos artificiales (p. Ej., {}Bloques gratuitos en lenguajes tipo C) solo para asegurarse de que el alcance de una variable no se extienda más allá del último uso previsto, y eso generalmente sería desaprobado como ofuscación / desorden a menos que ya exista Una buena razón para que el alcance exista independientemente.


2
Un problema con el alcance en muchos idiomas es que es extremadamente común tener algunas variables cuyo último uso es el cálculo de los valores iniciales de otras variables. Si un lenguaje tenía construcciones explícitamente para propósitos de alcance, eso permitía que los valores calculados usando variables de alcance interno se usaran en la inicialización de los de alcance externo, tales ámbitos podrían ser más útiles que los ámbitos estrictamente anidados que requieren muchos idiomas.
supercat

1

Considere las funciones ( métodos ). Allí no está dividiendo el código en la subtarea más pequeña posible, ni la pieza de código singleton más grande.

Es un límite cambiante, con delimitación de tareas lógicas, en piezas consumibles.

Lo mismo vale para las variables . Señalando estructuras de datos lógicos, en partes comprensibles. O también simplemente nombrando (estableciendo) los parámetros:

boolean automatic = true;
importFile(file, automatic);

Pero, por supuesto, tener una declaración en la parte superior, y doscientas líneas más, el primer uso se acepta hoy en día como un mal estilo. Esto es claramente lo que pretende decir "las variables deberían vivir en el menor alcance posible" . Como un muy cercano "no reutilice variables".


1

Lo que falta es la razón de NO es la depuración / legibilidad. El código debe estar optimizado para eso, y los nombres claros y concisos ayudan mucho, por ejemplo, imagina un 3 vías

if (frobnicate(x) && (get_age(x) > 2000 || calculate_duration(x) < 100 )

esta línea es corta, pero ya es difícil de leer. Agregue algunos parámetros más, y eso si abarca varias líneas.

can_be_frobnicated = frobnicate(x)
is_short_lived_or_ancient = get_age(x) > 2000 || calculate_duration(x) < 100
if (can_be_frobnicated || is_short_lived_or_ancient )

Me resulta más fácil leer y comunicar significado, por lo que no tengo problemas con las variables intermedias.

Otro ejemplo sería lenguajes como R, donde la última línea es automáticamente el valor de retorno:

some_func <- function(x) {
    compute(x)
}

esto es peligroso, ¿se espera o se necesita el retorno? esto es más claro:

some_func <- function(x) {
   rv <- compute(x)
   return(rv)
}

Como siempre, esta es una decisión: elimine las variables intermedias si no mejoran la lectura; de lo contrario, consérvelas o preséntelas.

Otro punto puede ser la depuración: si los resultados intermedios son de interés, puede ser mejor simplemente introducir un intermediario, como en el ejemplo R anterior. La frecuencia con la que se requiere esto es difícil de imaginar y tenga cuidado con lo que verifica (demasiadas variables de depuración son confusas), nuevamente, una llamada de juicio.


0

Refiriéndose solo a su título: absolutamente, si una variable es innecesaria, debe eliminarse.

Pero "innecesario" no significa que se pueda escribir un programa equivalente sin usar la variable, de lo contrario nos dirían que deberíamos escribir todo en binario.

El tipo más común de variable innecesaria es una variable no utilizada, cuanto menor es el alcance de la variable, más fácil es determinar que es innecesaria. Si una variable intermedia es innecesaria es más difícil de determinar, porque no es una situación binaria, es contextual. De hecho, un código fuente idéntico en dos métodos diferentes podría producir una respuesta diferente por parte del mismo usuario, dependiendo de la experiencia pasada en la solución de problemas en el código circundante.

Si su código de ejemplo era exactamente como se representa, sugeriría deshacerse de los dos métodos privados, pero no me preocuparía si guardó el resultado de las llamadas de fábrica en una variable local o simplemente los usó como argumentos para la mezcla método.

La legibilidad del código supera todo excepto el funcionamiento correcto (incluye correctamente criterios de rendimiento aceptables, que rara vez son "lo más rápido posible").

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.