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...

domingo, 2 de septiembre de 2012

return, o no return, ésa es la pregunta
cómo estructurar el código en C

Él tenía razón, a veces es difícil tomar decisiones existenciales, y hoy yo os voy a proponer una: ¿salida única o múltiple? Bueno, vosotros diríais: "hay cosas más importantes en que pensar". Por supuesto que si, pero aquí se trata de estilo, y como ya se ha señalado aquí, el estilo es importante. Además esta decisión a tomar no es una invención mía, de esto habló mucho antes que yo uno mucho mejor que yo, aunque ya entonces había alguien que no estaba completamente de acuerdo.

Creo que el dilema es claro: estamos hablando de Programación Estructurada y no de tonterías. Y si alguien está pensando "pero la programación estructurada esta superada", o bien "con la OOP el enfoque es completamente diferente", lo paro de inmediato. Aquí se habla del C, el padre (o al menos el tío) de todas los lenguajes modernos, y C es un lenguaje estructurado. Y punto. Y si pensáis que C y Programación Estructurada están superados, mejor que os toméis una pausa para la reflexión ...

¿Y entonces? Bueno, vamos a ver un ejemplo para ver si así nos aclaramos las ideas. Escribimos una función que abre un archivo, extrae la primera línea, procesa la línea de datos y sale. Como siempre, se podría escribir en varios miles de maneras diferentes, pero aislaremos las dos buenas para el ejemplo (bueno, aislamos también una tercera manera, pero solo para cerrar bonito el post). Consideremos en primer lugar el tipo Single-Exit:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;
    int retcode;

    // abro el file en path
    FILE *fp;
    if (fp = fopen(path, "r")) {
        // leo el file
        if (getline(&line, &len, fp) != -1) {
            // test contenido file
            if (!strncmp(line, "output=", 7)) {
                // procesa datos
                // ...

                retcode = 0;    // proc. OK: set retcode=OK
            }
            else
                retcode = -3;   // str err: set retcode=err

            free(line);     // libera recursos
        }
        else
            retcode = -2;   // getline err: set retcode=err

        fclose(fp);     // libera recursos
    }
    else
        retcode = -1;   // fopen err: set retcode=err

    // salgo con el retcode seteado
    return retcode;
}
Y ahora pasamos a analizar su perfecto equivalente del tipo multiple-exit, o sea:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;

    // abro el file en path
    FILE *fp;
    if ((fp = fopen(path, "r")) == NULL) {
        // salgo con error
        return -1;
    }

    // leo el file
    if (getline(&line, &len, fp) == -1) {
        // libero recursos y salgo con error
        fclose(fp);
        return -2;
    }

    // test contenido file
    if (strncmp(line, "output=", 7)) {
        // libero recursos y salgo con error
        fclose(fp);
        free(line);
        return -3;
    }

    // procesa datos
    // ...

    // proc. OK: libero recursos y salgo con OK
    fclose(fp);
    free(line);
    return 0;
}
¿Cuál es el mejor tipo? Bueno, para mí (y él) es el tipo Single-Exit, pero hay que decir que el partido del Multiple-Exit es grande y está formado (también) por gente muy preparada.

Entonces, ¿dónde está la verdad, tal vez en el medio? No, decir siempre que la verdad está en el medio es la excusa de los indecisos. Yo elijo la solución con el mejor estilo, la más bella y fácil de leer, y, añado, la que te da más control mientras la escribes: con el tipo Single-Exit es más difícil que te olvides de liberar recursos en el momento adecuado (pensar en un programa más complejo, no en el ejemplo). Y también el tipo Single-Exit es más fácil de leer y depurar (probarlo, pero siempre con un caso más complejo).

Se podría hacer una excepción: cuando con el tipo Single-Exit el nivel de anidamiento es demasiado alto, tal vez se podría hacer un híbrido: empezar con los test básicos (tipo "¿el parámetro pasado es nulo?") y sus respectivos return, y, luego, seguir en Single-Exit (pero, si al final el código resultante no os convence, tal vez es mejor si dividáis en dos la función).

Sin embargo, soy menos radical de lo que parece: entre el código que he escrito en mi pasado hay (basta con ver aquí) código de Multiple-Exit o híbrido. El hecho es que hay que ser un poco flexibles: programar (bien) es un arte, y demasiadas restricciones no ayudan a la expresividad.

Como preanunciado cerramos con el tercer tipo: la siguiente función también funciona, y es un equivalente perfecto de las anteriores:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;

    // lo hago todo de una vez
    FILE *fp;
    if ((fp = fopen(path, "r")) && (getline(&line, &len, fp) != -1) &&
            !strncmp(line, "output=", 7)) {

        // procesa datos
        // ...

        // datos procesados: libero recursos y salgo con OK
        fclose(fp);
        free(line);
        return 0;
    }
    else {
        // libero recursos y salgo con error
        if (fp)
            fclose(fp);

        if (line)
            free(line);

        return -1;
    }
}
Bueno, si os estáis planeando escribir una función en este modo (tal vez poniendo aun más test en el mismo if) relajaros, tomaros un descanso, leeros un buen libro y, a continuación, intentarlo nuevamente con la cabeza finalmente libre de malos pensamientos: tal vez (espero) que ya no se os ocurrirá de escribirla de esa manera.

Hasta el próximo post.

No hay comentarios:

Publicar un comentario