sabato 28 gennaio 2012

La mia opinione sulle asserzioni

Ad una conferenza uno sviluppatore certificato ha fatto un commento che mi ha lasciato perplesso:
"...sarebbe bello che le asserzioni funzionassero anche a run-time..."
Tale affermazione potrebbe essere estremamente fuorviante, ed è per questo che voglio esprimere la mia opinione sulle asserzioni e il loro uso. Una asserzione è un test su una condizione booleana che interrompe il flusso del programma qualora la condizione sia verificata. Ad esempio:

assert( ! username.empty() );

verifica che lo username non sia vuoto, nel qual caso interrompe (abort) l'esecuzione del programma.
Concettualmente una asserzione potrebbe essere considerata simile al seguente blocco di psudo-codice:

if( username.empty() )
   throw new Error();

Anche se concettualmente il pezzo di codice qui sopra è corretto, semanticamente è errato poiché non verifica che l'applicazione non sia in release mode, e quindi andrebbe riscritto come:

#ifdef DEBUG_MODE

if( username.empty() )
   throw new Error();
#endif

o senza le macro di compilazione come:


if( debugMode &&  username.empty() )
   throw new Error();



Per capire perché è così importante il test sulla modalità di debug occorre capire il vero scopo delle asserzioni. Le asserzioni non sono controlli di validità degli argomenti, sono controlli che condizioni impossibili non si possano mai verificare. Consideriamo un sistema di autenticazione:


private bool doAuthentication( String& username, String& password ){
    assert( ! username.empty() );
    assert( ! password.empty() );


    // do authentication stuff
}




public bool auth( String& usnermae, String& password ){
   if( username.empty() )
      // alert the user
   else if( password.empty() )
     // alert the user
   else
    return doAuthentication( username, password );
}

L'idea è quella di avere un metodo di autenticazione, auth, pubblico e richiamabile dall'esterno, e una sua implementazione doAuthentication privata. Si noti che le asserzioni sono solo nell'implementazione, mentre il metodo pubblico effettua analoghi controlli ma senza asserzioni. In effetti quello che si vuole evitare è che il sistema di autenticazione sia richiamato con dati nulli, ossia che doAuthentication sia invocato con parametri vuoti. Questa è una condizione impossibile, non si deve mai verificare, e il compito delle asserzioni è di garantire che durante lo sviluppo non si introducano bug che coinvolgano una chiamata a doAuthentication con parametri vuoti. Ma dal lato utente bloccare l'esecuzione del programma perché i dati sono incompleti non è la scelta corretta: si devono comunque controllare i dati ed eventualmente informare l'utente (con un messaggio di errore) circa la completezza dei dati. In altre parole, si vuole evitare un abort pur tenendo i controlli a run-time. Dall'esempio di cui sopra si potrebbe riassumere che:

  • le asserzioni vengono usate per le implementazioni (metodi non pubblici) per controllare che non ci siano bug "vistosi";
  • le asserzioni non corrispondono a controlli di validazione;
  • i controlli dei parametri devono sempre essere presenti nei metodi pubblici, ossia si deve evitare di lavorare con dati taint.

Nessun commento: