No he visto a nadie discutir esto, así que voy a agregar más ideas para pensar. La respuesta/consejo corto es que no use variables de instancia sobre variables locales solo porque crea que son más fáciles de devolver valores. Vas a hacer que trabajar con tu código sea muy difícil si no usas las variables locales y las variables de instancia de manera apropiada. Producirá algunos errores graves que son realmente difíciles de rastrear. Si quieres entender lo que quiero decir con errores serios, y lo que podría parecer, sigue leyendo.
Probemos y usemos solo las variables de instancia que sugiera para escribir en las funciones. Voy a crear una clase muy simple:
public class BadIdea {
public Enum Color { GREEN, RED, BLUE, PURPLE };
public Color[] map = new Colors[] {
Color.GREEN,
Color.GREEN,
Color.RED,
Color.BLUE,
Color.PURPLE,
Color.RED,
Color.PURPLE };
List<Integer> indexes = new ArrayList<Integer>();
public int counter = 0;
public int index = 0;
public void findColor(Color value) {
indexes.clear();
for(index = 0; index < map.length; index++) {
if(map[index] == value) {
indexes.add(index);
counter++;
}
}
}
public void findOppositeColors(Color value) {
indexes.clear();
for(index = 0; i < index < map.length; index++) {
if(map[index] != value) {
indexes.add(index);
counter++;
}
}
}
}
Este es un programa tonto lo sé, pero lo podemos usar para ilustrar el concepto de que el uso de variables de instancia para cosas como esta es una idea tremendamente mala. Lo más importante que encontrará es que esos métodos usan todas las variables de instancia que tenemos. Y modifica los índices, el contador y el índice cada vez que son llamados. El primer problema que encontrará es que llamar a esos métodos uno después del otro puede modificar las respuestas de ejecuciones anteriores. Así, por ejemplo, si escribió el siguiente código:
BadIdea idea = new BadIdea();
idea.findColor(Color.RED);
idea.findColor(Color.GREEN); // whoops we just lost the results from finding all Color.RED
Desde findColor utiliza variables de instancia de seguimiento de los valores devueltos que sólo puede devolver un resultado a la vez. Vamos a tratar de salvar de una referencia a esos resultados antes de llamar de nuevo:?
BadIdea idea = new BadIdea();
idea.findColor(Color.RED);
List<Integer> redPositions = idea.indexes;
int redCount = idea.counter;
idea.findColor(Color.GREEN); // this causes red positions to be lost! (i.e. idea.indexes.clear()
List<Integer> greenPositions = idea.indexes;
int greenCount = idea.counter;
En este segundo ejemplo hemos salvado las posiciones rojas en la tercera línea, pero lo mismo sucedió ¿Por qué los perdemos ?! Porque idea.indexes se borró en lugar de asignarse, por lo que solo se puede usar una respuesta por vez. Tienes que terminar de usar ese resultado por completo antes de volver a llamarlo. Una vez que vuelves a llamar a un método, los resultados se borran y pierdes todo. Para solucionarlo, deberá asignar un nuevo resultado cada vez, de modo que las respuestas rojas y verdes estén separadas. Así que vamos a clonar nuestras respuestas para crear nuevas copias de las cosas:
BadIdea idea = new BadIdea();
idea.findColor(Color.RED);
List<Integer> redPositions = idea.indexes.clone();
int redCount = idea.counter;
idea.findColor(Color.GREEN);
List<Integer> greenPositions = idea.indexes.clone();
int greenCount = idea.counter;
Ok, finalmente tenemos dos resultados separados.Los resultados de rojo y verde ahora están separados. Pero, teníamos que saber mucho sobre cómo funcionaba BadIdea internamente antes de que el programa funcionara, ¿no? Debemos recordar clonar los retornos cada vez que lo llamemos para asegurarnos de que nuestros resultados no se vean perjudicados. ¿Por qué la persona que llama se ve obligada a recordar estos detalles? ¿No sería más fácil si no tuviéramos que hacer eso?
Observe también que la persona que llama tiene que usar variables locales para recordar los resultados, por lo tanto, si no usó variables locales en los métodos de BadIdea, la persona que llama debe usarlos para recordar los resultados. Entonces, ¿qué lograste realmente? Realmente le acabas de pasar el problema a la persona que llama obligándolos a hacer más. Y el trabajo que presiona a la persona que llama no es una regla fácil de seguir porque hay muchas excepciones a la regla.
Ahora intentemos hacerlo con dos métodos diferentes. Observe cómo he sido "inteligente" y reutilicé esas mismas variables de instancia para "guardar memoria" y mantuve el código compacto. ;-)
BadIdea idea = new BadIdea();
idea.findColor(Color.RED);
List<Integer> redPositions = idea.indexes;
int redCount = idea.counter;
idea.findOppositeColors(Color.RED); // this causes red positions to be lost again!!
List<Integer> greenPositions = idea.indexes;
int greenCount = idea.counter;
¡Sucedió lo mismo! ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡Maldad, estaba siendo "inteligente" y ahorraba memoria y el código usa menos Este es el verdadero peligro de usar variables de instancia, como esto es que los métodos de llamada ahora dependen del orden. Si cambio el orden de las llamadas al método, los resultados son diferentes, aunque realmente no he cambiado el estado subyacente de BadIdea. No cambié el contenido del mapa. ¿Por qué el programa arroja resultados diferentes cuando llamo a los métodos en diferente orden?
idea.findColor(Color.RED)
idea.findOppositeColors(Color.RED)
produce un resultado diferente que si cambié esos dos métodos:
idea.findOppositeColors(Color.RED)
idea.findColor(Color.RED)
Este tipo de errores son realmente difíciles de localizar, especialmente cuando esas líneas no están uno al lado del otro. Puede romper completamente su programa simplemente agregando una nueva llamada en cualquier lugar entre esas dos líneas y obtener resultados tremendamente diferentes. Claro que cuando tratamos con pocas líneas, es fácil detectar errores. Pero, en un programa más grande, puede desperdiciar días intentando reproducirlos aunque los datos del programa no hayan cambiado.
Y esto solo tiene en cuenta los problemas de un solo hilo. Si BadIdea se estaba utilizando en una situación de subprocesos múltiples, los errores pueden ser realmente extraños. ¿Qué sucede si se llaman al mismo tiempo a findColors() y findOppositeColors()? Crash, todo tu pelo se cae, la muerte, el espacio y el tiempo colapsan en una singularidad y el universo se traga? Probablemente al menos dos de esos. Es probable que ahora los temas estén por encima de tu cabeza, pero espero que podamos alejarte de hacer cosas malas ahora, así que cuando hagas esas malas prácticas, no te causen verdadero dolor.
¿Notaste qué tan cuidadoso tenías que estar al llamar a los métodos? Se sobreescribieron mutuamente, compartieron memoria posiblemente de forma aleatoria, tenía que recordar los detalles de cómo funcionaba en el interior para que funcionara en el exterior, cambiar el orden en que se llamaban las cosas producía cambios muy grandes en las siguientes líneas, y solo podría funcionar en una sola situación de subproceso. Hacer cosas como esta producirá código realmente frágil que parece derrumbarse cada vez que lo tocas. Estas prácticas que mostré contribuyeron directamente a que el código fuera frágil.
Si bien esto podría parecer encapsulado, es exactamente lo contrario porque los detalles técnicos de cómo lo escribió deben ser conocidos por el que llama. La persona que llama tiene que escribir su código de una manera muy particular para hacer que su código funcione, y no pueden hacerlo sin conocer los detalles técnicos de su código. Esto a menudo se denomina Leaky Abstraction porque se supone que la clase oculta los detalles técnicos detrás de una abstracción/interfaz, pero los detalles técnicos se filtran forzando a la persona que llama a cambiar su comportamiento.Cada solución tiene algún grado de filtración, pero al usar cualquiera de las técnicas anteriores como estas garantías, sin importar el problema que intente resolver, será terriblemente permeable si las aplica. Entonces veamos el GoodIdea ahora.
reescritura Vamos a utilizar las variables locales:
public class GoodIdea {
...
public List<Integer> findColor(Color value) {
List<Integer> results = new ArrayList<Integer>();
for(int i = 0; i < map.length; i++) {
if(map[index] == value) {
results.add(i);
}
}
return results;
}
public List<Integer> findOppositeColors(Color value) {
List<Integer> results = new ArrayList<Integer>();
for(int i = 0; i < map.length; i++) {
if(map[index] != value) {
results.add(i);
}
}
return results;
}
}
Esto soluciona todos los problemas que discutimos anteriormente. Sé que no estoy rastreando el contador o devolviéndolo, pero si lo hago, puedo crear una nueva clase y devolverla en lugar de List. A veces utilizo el siguiente objeto para devolver resultados múltiples rápidamente:
public class Pair<K,T> {
public K first;
public T second;
public Pair(K first, T second) {
this.first = first;
this.second = second;
}
}
Respuesta larga, pero un tema muy importante.
¿Tiene permiso para definir otras clases? –