Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

venerdì 31 agosto 2012

return, o non return, questo è il dilemma
come strutturare il codice in C

Aveva ragione lui, a volte è difficile prendere delle decisioni esistenziali, e oggi ve ne propongo una io: singolo o multiplo punto di uscita ? Va bene, voi direte, "ci sono cose più importanti a cui pensare". Certamente, ma qui si tratta di stile, e, come già fatto notare qui, lo stile è importante. Del resto questa decisione da prendere non è una mia invenzione, ne ha parlato molto prima di me uno molto più bravo di me, anche se già ai tempi c'era chi non era completamente d'accordo.

Penso che il dilemma sia chiaro: stiamo parlando di Programmazione Strutturata mica di quisquilie. E se qualcuno sta pensando "ma la programmazione strutturata è superata", oppure "con la OOP l'approccio è completamente diverso", lo fermo subito. Qui si parla del C, il padre (o perlomeno lo zio) di tutti i linguaggi moderni, e il C è un linguaggio strutturato. Punto e basta. E se pensate che il C e la Programmazione Strutturata sono superati vi conviene prendervi una pausa di riflessione...

E allora ? Va beh, vediamo un esempio così ci chiariamo le idee. Scriviamo una funzione che apre un file, estrae la prima linea, processa i dati della linea ed esce. Come sempre si potrebbe scrivere in varie migliaia di modi diversi, ma noi isoliamo i due che ci servono per l'esempio (beh, isoliamo anche un terzo modo, ma quello è per finire in bellezza il post). Vediamo prima il tipo Single-Exit:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;
    int retcode;

    // apro il file in argomento
    FILE *fp;
    if (fp = fopen(path, "r")) {
        // leggo il file
        if (getline(&line, &len, fp) != -1) {
            // test contenuto file
            if (!strncmp(line, "output=", 7)) {
                // processa dati
                // ...

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

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

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

    // esce con il retcode settato
    return retcode;
} 

E adesso possiamo passare a analizzare il suo perfetto equivalente in modalità Multiple-Exit, ossia:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;

    // apro il file in argomento
    FILE *fp;
    if ((fp = fopen(path, "r")) == NULL) {
        // esce con errore
        return -1;
    }

    // leggo il file
    if (getline(&line, &len, fp) == -1) {
        // libera risorse ed esce con errore
        fclose(fp);
        return -2;
    }

    // test contenuto file
    if (strncmp(line, "output=", 7)) {
        // libera risorse ed esce con errore
        fclose(fp);
        free(line);
        return -3;
    }

    // processa dati
    // ...

    // proc. OK: libera risorse ed esce con OK
    fclose(fp);
    free(line);
    return 0;
} 

Quale è il tipo migliore ? Beh, per me (e lui) è il tipo Single-Exit, ma, c'è da dire che il partito del Multiple-Exit è numeroso e formato (anche) da gente molto preparata.

E allora dove sta la verità, forse nel mezzo ? No, dire sempre che verità sta nel mezzo è la scusa degli indecisi. Io scelgo la soluzione con lo stile migliore, la più bella e facile da leggere, e, aggiungo, quella che ti dà un controllo maggiore mentre la scrivi: nel tipo Single-Exit è più difficile dimenticarsi di liberare le risorse giuste nel momento giusto (pensate a un programma più complesso, non all'esempio). E, inoltre, il tipo Single-Exit è anche più facile da leggere e debuggare (provare per credere, ma sempre con un caso più complesso).

Potrei fare un eccezione: quando con il tipo Single-Exit il livello di annidamento diventa troppo alto forse si potrebbe fare un ibrido: mettere i test basici (tipo "il parametro passato è nullo ?") all'inizio con i relativi return, e poi seguire in modo Single-Exit (ma, se alla fine il codice risultante non vi convince, magari è meglio se spezzate in due la funzione).

Comunque, sono meno radicale di quello che sembra: tra il codice che ho scritto nei miei trascorsi c'è (basta guardare qui) del codice Multiple-Exit o ibrido. Il fatto è che bisogna essere un po' elastici: programmare (bene) è un'arte, e troppe restrizioni non giovano all'espressività.

Come preannunciato chiudiamo in bellezza con il terzo tipo: anche la funzione seguente funziona, ed è un perfetto equivalente delle precedenti: 
int readFile3(
    char *path)
{
    char *line = NULL;
    size_t len = 0;

    // faccio tutto in una botta sola
    FILE *fp;
    if ((fp = fopen(path, "r")) && (getline(&line, &len, fp) != -1) &&
            !strncmp(line, "output=", 7)) {

        // processa dati
        // ...

        // dati processati: libera risorse ed esce con OK
        fclose(fp);
        free(line);
        return 0;
    }
    else {
        // libera risorse ed esce con errore
        if (fp)
            fclose(fp);

        if (line)
            free(line);

        return -1;
    }
}

Ecco, se vi viene in mente di scrivere una funzione in quest'ultima maniera (magari mettendo ancora più test nello stesso if) rilassatevi, prendetevi un periodo di riposo, leggete qualche libro interessante e poi riprovateci con la testa finalmente sgombra da cattivi pensieri: magari (spero) non vi verrà più in mente di scriverla così. 

Ciao e al prossimo post.

Nessun commento:

Posta un commento