Jan 24, 2011

El for-each no es el único

En el Java 1.4 no existía, así que no había mucho qué hacer. A partir de Java 5, la cláusula "for" fue extendida de manera que pudiera ser utilizada con el propósito de iterar sobre arreglos y colecciones de forma más conveniente. En el caso de C#, el "for-each" existe desde el principio, así que los desarrolladores lo hemos disfrutado desde el primer día. Hablando en plata, el for-each es una verdadera bendición cada vez que necesitamos recorrer una colección cualquiera, evitándonos código tan poco presentable como el siguiente ejemplo en Java:
public static void main(String[] args) {
    List list = new ArrayList();
    list.add("This");
    list.add("is");
    list.add("crap");
    for(Iterator iterator = list.iterator(); iterator.hasNext(); ) {
        System.out.println(iterator.next());
    }
}
A partir de Java 5 las cosas mejoraron mucho, y ahora el código anterior puede ser escrito de forma mucho más compacta e intuitiva:
public static void main(String[] args) {
    List list = new ArrayList();
    list.add("This");
    list.add("is");
    list.add("better");
    for(String value : list) {
        System.out.println(value);
    }
}
Sin embargo, la bendición con el "for" extendido en Java ha venido de la mano con una enfermedad que ha aquejado al mundo del C# durante toda su existencia: la "for-each manía". Resulta que, de momento, nadie necesita el "for" común y corriente, e incluso en muchos casos, hasta los "while" y los "do - while" han pasado a engrosar el baúl de los recuerdos. Ahora todo es for-each de arriba a abajo, sin importar las consecuencias.

Veamos dos pequeños ejemplos del abuso del for-each donde la solución más clara es utilizar un tipo de ciclo diferente:
public static void main(String[] args) {
    String[] list = new String[] {"A", "B", "C"};

    int i = 0;
    for(String value : list) {
        System.out.println(value + " is in position " + i);
        i++;
    }
}
El ejemplo anterior recorre un arreglo con un "for" extendido (léase "for-each") e imprime el valor y la posición del mismo. Sin embargo, para la posición se incrementa una variable "i" tal y como se haría con un ciclo "for" común y corriente. El uso del for extendido en este ejemplo atenta contra la legibilidad del código, el cuál, para mi gusto, podría ser escrito de la siguiente forma:
public static void main(String[] args) {
    String[] list = new String[] {"A", "B", "C"};

    for(int i = 0; i < list.length; i++) {
        System.out.println(list[i] + " is in position " + i);
    }
}
El segundo ejemplo es aún mucho más desafortunado, ya que incluso el código pierde el sentido general de lo que debe expresar:
public static void main(String[] args) {
    int[] list = new int[] {2, 4, 6};

    for(int value : list) {
        if (value >= 5) {
            return;
        }
        System.out.println(value);
    }
}
En este ejemplo se recorre e imprime un listado de números mientras no se encuentre un valor mayor que 5. El uso del for extendido retuerce completamente la lógica del algoritmo y lo hace mucho más difícil de entender. Ni qué decir de la sentencia "return" rompiendo bruscamente el ciclo. Veamos la forma lógica y, a mi entender, mucho más clara de resolver el mismo problema:
public static void main(String[] args) {
    int[] list = new int[] {2, 4, 6};

    int index = 0;
    while(list[index] < 5) {
        System.out.println(list[index]);
        index++;
    }
}
Claro como el agua: el uso del "while" expresa de forma inequívoca lo que queremos expresar, y fluye de forma natural sin necesidad de usar un "return" como punto de ruptura.

Indiscutiblemente el for-each es una herramienta muy poderosa y útil, pero como todo lo demás, tiene sus usos específicos y puede resultar dañino cuando se utiliza incorrectamente. Antes de escribir el próximo for-each, detengámonos a pensar si en realidad es nuestra mejor variante para el problema entre manos. Recordemos que ni es el único, ni es la respuesta a todas las preguntas.

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Alcides, en realidad sí estás de acuerdo con mi primer ejemplo también. Mi ejemplo es un arreglo y funciona perfectamente con un ciclo for. Una lista con acceso O(n) es otro ejemplo que no aparece en el post, por lo que no puedes estar en desacuerdo con algo de lo que yo no hablo.

    Si te fijas, dejé claro que el for-each tiene sus usos y es muy importante (donde se incluye tu linked list). En ningún momento he dicho que no haga falta o que sea dañino. Tu ejemplo está claro, pero es como si alguien me dijera: "Santiago, no estoy de acuerdo con lo que dices, por que en este ejemplo XYZ de mi código es mejor usar reflection y no un while". Yo critiqué mi ejemplo (con un arreglo y con el solo objetivo de imprimir la posición de cada elemento), no al for-each en general.

    Por último, cada lenguaje tiene su librito, lo que los hace más o menos exitosos. Que Python no tenga "for" y solo "for-each" no quiere decir para nada que yo esté de acuerdo con ellos. En mi opinión el "for" sigue siendo tan necesario como el primer día.

    ReplyDelete

Note: Only a member of this blog may post a comment.