Prova ad usarlo male!

Dal cubicolo di fianco mi segnalano questo post per associazione di idee con il mio di qualche giorno fa. L’autore sa il fatto suo e la questione è molto intrigante…

Quando qualcuno mi propone una soluzione ad un problema di qualche tipo asserendo che “così sarebbe più facile”, spesso cado in uno stato ansiogeno prodotto dalla certezza che in realtà “così smetterà di funzionare”. Ma è difficile argomentare quando a supporto della propria teoria si ha solo una “sensazione” – abbiamo bisogno di un metodo quantitativo.

Facendo riferimento alle interfacce di programmazione, Russell propone la teoria del “rendine difficoltoso l’uso scorretto”. Il concetto è semplice e facile a dirsi, un pò meno a farsi, ma lui da bravo kernel hacker lo sa bene, quindi ecco la sua ricetta per valutare l’efficacia di un metodo al fine di ottenere interfacce difficili da usare male (l’italiano qui non mi supporta un granchè accidenti). Ovviamente vince chi fa più punti!

Sbagliare è impossibile: 10 punti.
Rientrano in questa categoria le funzioni del tipo fai_questo() o fai_quello(). Ovviamente si può riuscire ad usare male anche queste, ad esempio non chiamandole affatto, ma in linea di massima dovremmo essere al sicuro (i fans del “code by intention” potrebbero contestare l’uso del condizionale😉 )

Fare in modo che il compilatore (ma pure il linker) ci impediscano di sbagliare: 9 punti.

#define BUILD_BUG_ON(cond) sizeof(char[1-2*!!(cond)])

Così si ottiene una specie di assert a tempo di compilazione, se cond è diverso da zero il codice non compila. Spargete BUILD_BUG_ON in giro per i sorgenti ed il gioco è fatto.

Fare in modo che il compilatore ci avvisi che stiamo sbagliando: 8 punti.
Dai sorgenti del kernel:

/*
* min()/max() macros that also do
* strict type-checking.. See the
* "unnecessary" pointer comparison.
*/
#define min(x,y) ({ \
  typeof(x) _x = (x);    \
  typeof(y) _y = (y);    \
  (void) (&_x == &_y);    \
  _x < _y ? _x : _y; })

Se provate ad invocare min() su due variabili di tipo diverso il compilatore emetterà un warning.

Quello ovvio è (probabilmente) l’uso corretto: 7 punti.
Applicazione del rasoio di Occam alla programmazione. Voi chiamereste la funzione exit() o _exit()? Non deludetemi…

Dare nomi alle cose che dovrebbero suggerirne l’uso: 6 punti.
Ancora dal kernel: la funzione che restituisce il reference count per un modulo a volte può fallire (così va il mondo); Russel fa notare come sia vagamente meglio chiamare tale funzione try_module_get() piuttosto che module_get(). Programmatore avvisato…

Fa’ le cosine a modo o schianto: 5 punti.
Se il prim0 venuto tenta di usare la nostra API in malo modo, possiamo inviargli un messaggio subliminale uccidendo l’applicazione a runtime… assert() è nostro amico!

Seguire le convenzioni: 4 punti.
A volte il conformismo paga… Scrivere delle funzioni i cui prototipi presentino gli argomenti in un ordine logico e noto in letteratura è cosa buona, ad esempio in un wrapper di memcpy mi aspetterei di trovare “la destinazione prima della sorgente”.

La lettura della documentazione ci impedirà di sbagliare: 3 punti.
Vabè, diciamo solo che se il funzionamento dell’API si basa sulla premessa che bisogna leggere la documentazione siamo messi male…

La lettura dell’implementazione ci impedirà di sbagliare: 2 punti.
Questa credo si commenti da sola!

La lettura del thread appropriato sulla mailing list ci impedirà di sbagliare: 1 punto.
A volte le API hanno interfacce inspiegabilmente strane, e magari il motivo è da ricercarsi in qualche ragione storico-filosofica (tipo, “sui sistemi VAX c’erano solo 6 caratteri per fare questo o quello”), in questi casi chiedere sulla lista o sfogliare le FAQ può aiutare, ma certo l’API di turno non brilla per usabilità!

Per un’introduzione al concetto di “Easy to Use” vs “Hard to Misuse” leggete qui.