En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

jueves, 27 de septiembre de 2012

¿Variables globales? No, Gracias
cómo usar las variables globales en C

Hoy vamos a tratar de destruir otro de los pilares de la no-programación: la variable global. Ojo: las variables globales (así como el goto, por ejemplo) son parte del lenguaje, entonces existen: a veces es posible y/o necesario usarlas. Pero, al igual que el goto, casi siempre es posible prescindir de éllas, con grandes beneficios de estilo (legibilidad y facilidad de mantenimiento, sobre todo) y funcionalidad (menos bugs): Llámalo nada.

Los puntos críticos son muchos, pero ya que no quiero escribir ni un poema ni un libro sobre el tema, he aislado unos pocos. Vamos a ver:

1) Las variables globales no son thread-safe: en la programación multihilo el uso de variables globales puede generar unos mal funcionamientos sutiles y difíciles de encontrar. El caso clásico es la instrucción de lectura/escritura de una global fuera de un área crítica, o sea (por ejemplo) sin la protección de un mutex: en un proyecto de gran envergadura es suficiente un olvido (sólo uno!) de este tipo para darte tantos dolores de cabeza que dejaras de usar las globales para el resto de tu vida como programador. Ver para creer. En este punto, se podría objetar: "Pero yo estoy escribiendo programas normales, no multihilo". Muy bien, declarando previamente (y matizando en la siguiente sección) que un programa "normal" se puede considerar un programa multi-hilo con un solo hilo, doy las gracias por la objeción, que me permite de lanzar el punto 2:

2) las variables globales violan el principio de mantenimiento/reutilización del Software: cuando se escribe profesionalmente Software siempre se debe pensar en el trabajo en equipo, y por lo tanto, en las operaciones de mantenimiento que podrían ser realizadas por otras personas (y, a veces) después de un largo tiempo. Evidentemente, un código, vasto y lleno de globales, es difícil de mantener como un código No Comment (¿recordáis?), porque el historial de una global es poco comprensible, podría verse afectado en muchos lugares diferentes por muchas funciones diferentes, y si para entender un trozo de código tienes que abrir decenas de archivos... os habeis ganado otro dolor de cabeza! Y no hablamos de reutilizar código lleno de globales para otro proyecto: si no lo habéis hecho nunca por lo menos tratar de imaginar la dificultad. Y no sólo: volvemos al punto 1 (thread-safe): ¿quién me dice que el codigo "normal" no tiene que volverse a utilizar (un día) en un proyecto multihilo? Si el código es thread-safe se puede hacer fácilmente, pero si hay globales de por medio... Bueno, buen trabajo (y buena suerte).

3) las variables globales aumentan la dificultad de depuración y multiplican la probabilidad de errores de programación: para la depuración, remito al punto 2: si el valor de una variable es difícil de seguir en términos de mantenimiento también lo será a nivel de depurar. Y habrá más mal funcionamientos para depurar (fantástico!), porque, además de todos los posibles errores de codificación. hay que añadir los de scope: probar este código:
int my_var = 0;

void incrementaMyVar()
{
    my_var += 5;
}

int main(int argc, char **argv)
{
    // hago mil cosas...
    // ...

    // incremento my_var
    incrementaMyVar();

    // hago otras mil cosas...
    // ...

    // declaro una "nueva" variable my var y la uso
    int my_var = 2;        // ah, ah, ah: redeclaración!
    // ...

    // hago todavía mil cosas...
    // ...

    // incremento y test my_var (oops! cual my_var?)
    incrementaMyVar();
    if (my_var == 2)
        formatMyHardDisk();    // uh, uh, uh: era la equivocada!
    // ...

    return 0;
}
El que se ha enseñado arriba era un problema de scope con redefinición local (accidental) de una global. El código que sigue es aún más simple, muestra un descuido de un detalle importante:
int my_var = 0;

void hagoMilCosas()
{
    // hago mil cosas...
    // ...

    // incremento my_var (escondido entre mil instrucciones!)
    my_var += 5;

    // hago otras mil cosas...
    // ...
}

int main(int argc, char **argv)
{
    // hago unas cuantas cosas...
    // ...

    // llamo hagoMilCosas()
    hagoMilCosas();    // oops! he incrementado my_var sin quererlo

    // hago otras cosas...
    // ...

    // test my_var
    if (my_var == 5)
        formatMyHardDisk();    // uh, uh, uh: me he equivocado?
    // ...

    return 0;
}
buena historia, eh?

4) las variables globales violan el principio de encapsulación de variables: bien, esto ni siquiera es necesario que lo explique, una global es cualquier cosa menos que encapsulada... oops, pero esto es OOP, entonces un poco fuera del tema del blog: perdón, quería exagerar. Bueno, ya que estamos hablando de OOP, y entonces de C++, cito con gran placer el grande M.Cline que en su C++FAQ dice (traduzco, vale);
El nombre de una variable global debe empezar con //.

Esta es la forma ideal de declarar una variable global:

// int xyz; <-lo que hace ideal esta global es la inicial //

Esta es la manera ideal de utilizar una variable global:

void mycode()
{
  ...
  // hace_algo_con(xyz);  <-ídem
  ...
}

OK, esto es un juego. Casi. La verdad es que hay casos en que las
variables globales son menos peores que la alternativa - cuando las
globales son el menor de los males. Pero ellas son siempre malvadas.
Así que lavarse las manos después del uso. Dos veces.
Palabras santas.

Sin embargo, si pensáis que todo esto es sólo harina de mi bolsa, intentar pedirle a nuestro amigo Google: global variable are evil? y vais a ver que avalancha de resultados sale. Si estáis interesados en una de las páginas más interesantes la podéis encontrar aquí.

Creo que lo que se ha dicho es suficiente. Obviamente estoy un poco sesgado porque he trabajado mucho sobre software multihilo (y todavía tengo un poco de dolor de cabeza ...), pero os pido que confíeis y difundáis el mensaje lo más posible. Sí, lo sé, decirle a un programador sin experiencia (o a un programador cansado) "no usar variables globales" es como decirle a un niño "no comer demasiados dulces": bueno, hay que hacerlo. La salud en primer lugar.

Hasta el próximo post.

5 comentarios:

  1. Siempre he pensado que la utilización de variables globales es muy beneficiosa y de hecho la he utilizado. Después de ller tu post lo pensaré dos veces antes de usarlas...
    Gracias!

    ResponderEliminar
    Respuestas
    1. De nada Javier, de nada. Y gracias a ti para leer el blog!

      Eliminar
  2. Buen aporte! sería interesante leerle un artículo sobre código ofuscado en C...tal vez entonces al intentar hacer bajamente entendible lo que sucede en el código podrá informar sobre las ventajas de las variables globales y el GOTO para tales fines...un saludo y agradecido por el esfuerzo en el blog! :-)

    ResponderEliminar
    Respuestas
    1. Muchas gracias por el comentario!
      Te habrás dado cuenta que en septiembre 2018 (después de 6 años) dejé de traducir al español mi blog en italiano. Si quieres seguir leyéndome, la versión en Italiano sigue activa, aunque tendrás que usar algún traductor en linea :)
      Ciao!

      Eliminar
  3. Hola de nuevo! sí, claramente leí su aviso de desatención del blog en castellano para enfocar los esfuerzos en el blog en idioma italiano. Agradezco mucho el que haya respondido. Le escribiré en su blog en italiano; más, no olvide mi invitación a efectuar una entrada sobre ofuscación de código en C (obfuscation code C) fundamentada en su experiencia. Adeú :-)

    ResponderEliminar