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.

Quando l'elefante si fa uno spuntino...

Una immagine veramente significativa che ho trovato per caso e che è attribuita a Command Prompt.

mercoledì 25 gennaio 2012

Avoid downcast!

Who has never written a piece of code like the following (pseudo Java)?


if( myObject instanceof BASE )
  ((BASE) myObject).doBaseBehavior();
else if( myObject instanceof DERIVED1 )
  ((DERIVED1) myObject).doDerivedBehavior1();
else if( myObject instanceof DERIVED2 )
  ((DERIVED2) myObject).doDerivedBehavior2();

It is called downcast and is the opposite of the Liskov Substitution Principle. The idea is that, at run-time, you have to invoke a specialized behavior through a generic base class. Now, read the sentence again and emphasize the words "specialized", "through" and "generic". Note the order of such words: it is not correct! While it is true that a specialized implementation should offer a generic behavior, this does not mean that you can change ordering to the words and obtain something that can work. "Hey, Java offers instanceof operator and C++ offers dynamic_cast"  - I hear you screaming that. And so what? Do you think that just because there is such an operator in your language you should use in such a way? Not at all!
What happens when you are going to add a new derived subclass? You have to add another branch to your selection statement. That is a really bad looking piece of code, and trust me, sooner or later, you will forget to add such a branch and find your code is buggy.
I've seen this code more and more in every kind of project, and it seems to be nested also in libraries, especially commercial ones (at least this is my experience). You have to avoid this kind of programming. Let consider again your problem and create more abstractions, since what you are really expressing with the above code is that a few instances are pretty the same except for a single behavior. So they are not the same, do not manage them as they could be considered the same, or generalize such behavior so that all instances have the same common interface or prototype and are free to implement it as they need. So that instead of downcasting an instance you are simply casting it at one of its generic interfaces:

if( myObject instanceof MyPrototype )
  ((MyPrototype) myObject).doBehavior();

Interestingly my experience is that this kind of programming template (I would not call it a pattern!) arises from a bad comprehension of the factory pattern: you have a factory that creates different instances of the same interface, but since your interface lacks of specialization and you don't have the control on which implementation is going to be created, you try to downcast it. Again, this is not the solution, or at least, it is a solution that could work in the short term, but that you need to refactor to have robust code.
Finally, please consider that while doing casting the language operators are usually smart enough to inform you about an error in the cast. For instance, the dynamic_cast operator returns 0 if the cast was impossible. Similarly the instanceof operator returns false if the object is null. Take advantage of this information to write better quality code!

Bisogna attendere....

Mi ha colpito parecchio il post di Aaron Seigo, leader del progetto KDE 4, che annuncia un grosso cambiamento che lo riguarda (dal punto di vista IT, ovviamente) e che non vuole svelare fino alla fine della settimana. Inizialmente ho temuto per KDE, visto che perdere Seigo sarebbe, a mio avviso, molto pericoloso per il progetto stesso. Devo infatti ammettere che Aaron ha saputo dare un'ottima impronta ad un progetto particolarmente complesso come quello della rivoluzione dei desktop che KDE 4 ha generato (e che Gnome parecchi mesi dopo ha emulato). Ed è anche per questo che considero Seigo uno dei leader piu' di successo per KDE, assieme ad un suo predecessore, Waldo Bastian (KDE 2) uno dei maggiori artefici del successo di questo a tratti controverso desktop. Fortunatamente, almeno dai toni del post, non sembra che Aaron sia intenzionato a lasciare scoperto KDE.
E anche il caso di ricordare come la tecnologia dietro alla quarta generazione del DE sia stata usata anche da altri progetti commerciali per la realizzazione dei loro widget o dei loro browsers (chi sta pensando ad Apple ha ragione!).
Effettivamente qualcuno ha notato come la data di annuncio del cambiamento da parte di Seigo coincida con il rilascio della nuova versione di owncould, il popolare strumento di cloud computing creato come estensione KDE. E dal secondo post di Seigo circa la collaborazione e l'arte del fare il sospetto sembra piu' che lecito....

martedì 24 gennaio 2012

FreeNAS: backup via rsync

FreeNAS è veramente un ottimo prodotto, altamente performante. Purtroppo manca di una funzione fondamentale di ogni buon server che si rispetti: la possibilità di poter fare backup da macchine remote. Mi spiego meglio: FreeNAS esporta (condivide) il suo spazio storage verso altre macchine, permette anche di sincronizzarsi via rsync con altre macchine FreeNAS ma non permette (da web GUI) di avviare un processo rsync verso una macchina non FreeNAS. Ovviamente è possibile agire da riga di comando, anche se nel caso in cui si abbia un avvio da stick USB servano alcuni accorgimenti. In particolare, essendo il file system di root montato in sola lettura, non sarà possibile memorizzare le chiavi ssh degli host ai quali ci si vuole connettere, cosa indispensabile per gli script automatizzati. Il trucco consiste quindi nel montare il file system root in lettura scrittura, generare le chiavi ssh e collegarsi ad ogni host remoto, e successivamente rimontare il file system in sola lettura. Anche gli script wrapper per rsync (a patto che si usino degli script - scelta consigliata) devono essere memorizzati in una posizione accessibile in lettura/scrittura (oppure nel file system principale, che verrà rimontato in sola lettura e quindi impedirà agli script di essere modificati successivamente); io ad esempio ho creato un dataset "nascosto" (ossia non condiviso) appunto per gli script. Dopodiché si potrà procedere con la web GUI per inserire l'esecuzione dello script al momento voluto.

sabato 21 gennaio 2012

Use late declaration

While developing code you will have to declare one or more variables before using it. If the language allows you to do so, declare them only when you are going to use! There are languages as C that require you to declare a variable at the begin of a code block, that could be far from when it is firstly used. There are other languages that allow you to declare a variable exactly where you are going to use it (Java, C++, Perl,...), and if possible you should get used to this habit. To explain why let us consider the following piece of pseudo Java code:

AuthToken login( String username, String password ) throws AuthException{
    AuthToken token;


    if( username == null || username.isEmpty() )
         throw new AuthException();
    else if( password == null || password.isEmpty() )
         throw new AuthException();
    else{
        // validate login and return a new AuthToken
        return token;
    }
}


The code is quite simple: the method receives two parameters as input, check them againsta some rules and then wrap them into another object called AuthToken that is then returned. What is wrong with such piece of code? Nothing. But that is because it is Java. Now imagine the same code expressed in C++ language. Can you see what is wrong? The AuthToken object is build immediatly as it is declared and then there are two paths that will not use it (the two path that throw an exception). In such case the object will be wasted.
There is something more beside avoiding a memory waste: placing the variable exactly when you are going to use it makes it simpler to comment out a whole piece of code. In the above example, if you refactor the method to return a void, you have to comment out the return statement and also the variable declaration, and the two instructions are quite far. Now, while commenting out the declaration will mark the return statement as not valid, doing the vice versa will not and could lead to a situation where the code is correct even if the variable is still there.
Finally, declaring the variable where it is going to be used makes it simpler to refactor the code introducing new code blocks, and therefore new scope contexts.

PGDay 2011 @ BSD Magazine

Nel numero di Gennaio 2012 della rivista BSD Magazine (dedicata ovviamente al mondo BSD) è stato pubblicato un mio breve resoconto del PGDay 2011. Spero che questo contribuisca a fare crescere ancora di piu' il PGDay Italiano e ITPUG.

PC BSD 9 pronto!

Seguendo a ruota l'annuncio ufficiale di FreeBSD 9 anche la versione finale di PCBSD 9 è stata rilasciata ed è disponibile per il download. Il mio piccolo contributo, diverse traduzioni, sono purtroppo sparse chissà dove nella rete....

Terzo Torneo Indoor Citta' di Carpi

E' passata una settimana dalla conclusione del Terzo Torneo Indoor Città di Carpi, e scrivo un piccolo report solo ora perché non ho avuto tempo prima.
Sono molto soddisfatto di come si è svola la competizione, e penso che tutta la Compagnia Arcieri Re Astolfo possa ritenersi piu' che soddisfatta della gara. Ancora una volta la società si è mostrata all'altezza dell'evento, organizzando una gara fondamentalmente impeccabile. A parte qualche cambiamento e sostituzione dei partecipanti causa mali di stagione, tutti i turni si svolti senza intoppi. Gli atlati hanno potuto competere nella bella palestra dell'Istituto Vallauri, ben illuminata e spaziosa. Il rinfresco è stato sempre ottimo, con dolci e salato veramente ottimi. 
Certo è stato un lavoraccio tutta l'organizzazione, ma stiamo diventando ogni anno piu' bravi e precisi, e sono molto contento di ciò. Penso anche che l'organizzazione della gara sia una buona occasione per stringere il gruppo dei membri della compagnia, che così condividono ancora di piu' il loro hobby comune.

domenica 15 gennaio 2012

FreeBSD wallpapers

Ho scovato quasi per caso questa collezione di wallpapers dedicati a FreeBSD, e devo dire che alcuni sono veramente notevoli!

Qt-Day 2012: io ci saro'

Mi sono iscritto appena apparso il comunicato ufficiale, fra qualche giorno parteciperò al Qt-Day 2012.

sabato 7 gennaio 2012

QtCreator & qmake

Uno degli errori in cui inciampo piu' frequentemente quando uso QtCreator è la mancata esecuzione di qmake. In sostanza ogni volta che modifico le mie classi aggiungendo dei discendenti di QObject e ricompilo il progetto ottengo degli errori come:

tableviewhandler.o: In function `TableViewHandler':
/sviluppo/c/WHR-build-desktop/../WHR/tableviewhandler.cpp:5: undefined reference to `vtable for TableViewHandler'
/sviluppo/c/WHR-build-desktop/../WHR/tableviewhandler.cpp:5: undefined reference to `vtable for TableViewHandler'
tableviewhandler.o: In function `~TableViewHandler':
/sviluppo/c/WHR-build-desktop/../WHR/tableviewhandler.cpp:31: undefined reference to `vtable for TableViewHandler'
/sviluppo/c/WHR-build-desktop/../WHR/tableviewhandler.cpp:31: undefined reference to `vtable for TableViewHandler'
/sviluppo/c/WHR-build-desktop/../WHR/tableviewhandler.cpp:31: undefined reference to `vtable for TableViewHandler'
collect2: ld returned 1 exit status
make: Leaving directory `/sviluppo/c/WHR-build-desktop'
make: *** [WHR] Error 1
The process "/usr/bin/make" exited with code 2.
Error while building project WHR (target: Desktop)
When executing build step 'Make'


che mi ricordano velocemente che il sistema di compilazione non ha aggiornato i file .moc. Occorre allora eseguire, dal menu Build, la sequenza Clean All, Run qmake e infine il classico Buil All per ottenere la compilazione dell'intero progetto.

venerdì 6 gennaio 2012

FreeBSD NULLFS

Ho trovato il mio primo impiego pratico del nullfs di FreeBSD. Il nullfs consente di esportare un filesystem in modalità stacked (uno sopra all'altro).
Nel mio caso ho montato il sistema dei ports di un FreeNAS con installazione su chiave USB (e quindi file system in sola lettura):

mount -uw /
mkdir /usr/ports
mount -ur /
mount -t nullfs /mnt/RPOOL/ports/ /usr/ports
mount
...
/mnt/RPOOL/ports on /usr/ports (nullfs, local)

giovedì 5 gennaio 2012

Qt Developer Network down

Oggi la rete della comunità di sviluppatori Qt (Qt Developers' Network) era in manutenzione, come mostrato dalla simpatica schermata che riporto qui sotto.

martedì 3 gennaio 2012

FreeNAS & ports

Installare l'albero dei ports su FreeNAS quando questo efettua il boot da una chiavetta USB potrebbe non essere così semplice come si può pensare, perché il filesystem di sistema è limitato e alcune locazioni sono montate in sola lettura. Io personalmente ho optato per la seguente strategia:
  • creare uno spazio temporaneo di lavoro per portsnap:
zfs create RPOOL/ports-tmp
  • scaricare l'albero dei ports
portsnap -d /mnt/RPOOL/ports-tmp fetch
  • creare lo spazio finale dei ports
zfs create RPOOL/ports
  • decomprimere l'albero dei port
portsnap -d /mnt/RPOOL/ports-tmp -p /mnt/RPOOL/ports extract

  • rimuovere lo spazio temporaneo
zfs destroy  RPOOL/ports-tmp
  • sovrascrivere la configurazione di default dei ports, che si trova nel file /usr/share/mk/bsd.port.subdir.mk e che è alla base del sistema di compilazione dei ports. Questo passo è il piu' importante, perché il filesystem /usr è insola lettura in questa installazione. Ci sono due soluzioni: la prima consiste nel linkare la directory a /usr/ports, la seconda nello esportare la directory dei ports come link in /usr. Quindi si deve eseguire uno dei due comandi seguenti:
set PORTSDIR=/mnt/RPOOL/ports
ln -s /mnt/RPOOL/ports/ /usr/ports


    E il gioco è fatto. Occorre però prestare attenzione al fatto che /usr rimane in sola lettura, quindi molto del software che si vuole installare non funzionerà a meno di definire una cartella nel NAS vero e proprio e di compilare dai ports usando tale cartella.