venerdì 29 luglio 2011

Programmare con stile di Simone Vellei, interessante guida per la scrittura di codice del kernel Linux !!



misc37.jpg
Sono sempre stato un maniaco dello stile di programmazione a partire dalla formattazione del codice. Una delle più interessanti guide sullo stile di programmazione si trova direttamente nei sorgenti del kernel Linux:
<linux-source-directory>/Documentation/CodingStyle
E’ un’ottima documentazione e vorrei condividerla con i miei lettori traducendola in italiano. Trattandosi di una guida per la scrittura di codice del kernel Linux, sono presenti alcune riferimenti o paragrafi riguardanti tale tipo di programmazione, ove possibile, quindi, sarà fornita una versione ritagliata e riadattata per essere di carattere più generale possibile.
CAPITOLO 1: Formattazione
Il carattere speciale “Tab” equivale a 8 caratteri, quindi anche la formattazione del codice è basata su spazi di 8 caratteri. Ci sono movimenti eretici che provano ad usare formattazioni basate su spaziature a 4 (o anche 2!) caratteri, questo equivale a dire che il valore esatto di PI è 3.
Spiegazione razionale: Il concetto che è alla base di una buona formattazione è di definire in maniera chiara l’inizio e la fine di un blocco di codice. Specialmente quando hai trascorso 20 ore consecutive a fissare il tuo schermo, troverai molto più facile distinguere i vari blocchi se avrai fatto uso di una formattazione più larga.
Ora, qualcuno potrebbe obiettare che, avendo una formattazione basata su spaziature a 8 caratteri, il codice si sposti troppo velocemente verso destra, rendendolo di difficile lettura su un schermo di un terminale a 80 caratteri. La risposta a questo è che, se tu hai bisogno di più di 3 livelli di profondità, stai comunque sbagliando e devi sistemare il tuo codice.
In poche parole la formattazione con spaziature a 8 caratteri rende il codice più facilmente leggibile e ha il valore aggiunto di avvertirti quando stai annidando troppo in profondità le tue funzioni.
Non inserire più dichiarazioni su una singola linea a meno che non hai qualcosa da nascondere:
if (condition) do_this;
  do_something_everytime;
Fatta eccezione per i commenti e la documentazione non vengono mai utilizzati singoli spazi per la formattazione e l’esempio qui sopra è volutamente errato.
Usa un editor decente e evita di lasciare spazi alla fine delle linee.
 Eccoci alla seconda puntata della guida Programmare con Stile. Dopo aver analizzato i principi base della formattazione del codice, passiamo all’analisi della gestione delle righe lunghe di codice e del posizionamento delle parentesi. Approfitto per ricordare ai lettori che questa guida è tratta dalle linee guida dello stile di programmazione del kernel Linux, quindi si adatta bene a linguaggi C-like un po’ meno a linguaggi come il python dove la formattazione del codice è parte integrante della grammatica e semantica del linguaggio.

Capitolo 2: Gestire linee e stringhe lunghe
Programmare con stile vuol dire garantire la leggibilità del codice e la sua manutenzione per mezzo di strumenti comunemente disponibili.
Il limite della lunghezza delle linee è di 80 colonne e questo è un limite fortemente obbligatorio.
Dichiarazioni lunghe più di 80 colonne saranno adattate opportunamente su più righe. Le righe spezzate sono sempre sostanzialmente più corte della riga “padre” e sono posizionate sostanzialmente sulla destra. La stessa procedura si applica alle dichiarazioni di funzioni che hanno una lunga lista di argomenti. Anche le stringhe lunghe sono spezzate in stringhe più corte.
if (condition)
        printk(KERN_WARNING "Warning this is a long printk with "
                                        "3 parameters a: %u b: %u "
                                        "c: %u n", a, b, c);
else
        next_statement;
Capitolo 3: posizionare le parentesi
Un’altra particolarità che occorre gestire per aver un buon stile nella programmazione C è il posizionamento delle parentesi. A differenza della scelta della dimensione del carattere “Tab”, qui ci sono alcune ragioni tecniche per scegliere una strategia di posizionamente piuttosto che un’altra. La scelta preferita, comunque, è quella dettata dai profeti Kernighan e Ritchie e, cioè, di aprire la parentesi graffa alla fine della linea e chiuderla all’inizio di una nuova, così come segue:
if (x is true) {
                we do y
        }
Tuttavia, nella definizione di funzioni c’è un comportamento speciale: la parentesi graffa viene aperta all’inizio della riga successiva, così:
int function(int x)
        {
                body of function
        }
Varie persone eretiche in tutto il mondo hanno sostenuto che questa incoerenza è … come dire … incoerente, ma tutte le persone ragionevoli sanno che:
a) K&R hanno ragione
b) K&R hanno ragione
Inoltre, le funzioni sono comunque particolari (non puoi annidarle in C).
Nota che la parentesi graffa chiusa si trova da sola in una nuova riga, fatta eccezione per i casi dove la riga continua con altre dichiarazioni, ad esempio in un “while” con il “do” o in un “else” con un “if”, come questo:
do {
                body of do-loop
        } while (condition);
e
if (x == y) {
                ..
        } else if (x > y) {
                ...
        } else {
                ....
        }
Spiegazione razionale: K&R.
Oltretutto, nota che questa modalità di posizionamento delle parentesi minimizza il numero delle righe vuote (o quasi vuote), senza nessuna perdita in leggibilità. Quindi, siccome la fonte di nuove linee nel tuo schermo non è una risorsa rinnovabile (pensa ad un terminale 25 linee), hai più linee vuote per inserire dei commenti.misc39.jpg

Programmare con stile è una filosofia di vita, una scelta che permette di organizzare in maniera oculata il proprio codice e renderlo leggibile anche ad altri utenti. Oggi analizziamo alcune buone regole per dare nomi a variabili e persviluppare funzioni eleganti.
Capitolo 4: scegliere il nome
Il C è un linguaggio Spartano, e così dovrebbe essere il tuo modo di dare i nomi. A differenza dei programmatori Modula-2 e Pascal, i programmatoriaffascinanti tipo “ThisVariableIsATemporaryCounter”. Un programmatore C chiamerebbe questa variabile “tmp”, che è molto più facile da scrivere, ma molto più difficile da comprendere.
TUTTAVIA, mentre i nomi misti con maiuscole e minuscole non sono visti di buon occhio, dare nomi descrittivi alle variabili globali è un dovere. Chiamare una funzione globale “foo” è un grave errore.
Le variabili GLOBALI (da usare solamente se ne hai REALMENTE bisogno) devono avere dei nomi descrittivi, così come le funzioni globali. Se hai una funzione che conta il numero di utenti attivi, dovresti chiamarla “count_active_users()”, o qualcosa di simile, ma non NON dovresti chiamarla “cntusr()”.
Integrare il tipo di una funzione all’interno del suo nome (la cosidetta notazione Ungherese) è un danno al cervello – il compilatore conosce sempre e comunque i tipi e può controllarli, ciò serve solamente a confondere il programmatore. Non meravigliarti se MicroSoft fa programmi pieni di bug.
I nomi per le variabili LOCALI devono essere corti e adeguati. Se hai qualche intero utilizzato per contare all’interno di un loop, dovrebbe essere probabilmente chiamato “i”. Chiamarlo “loop_counter” non è produttivo, se non c’è nessuna possibilità che esso venga confuso. Similmente con “tmp” può essere chiamata qualsiasi tipo di variabile che è usata per mantenere un valore temporaneo.
Se hai paura di mischiare i nomi delle tue variabili locali, hai un altro problema, che è chiamato “sindrome dello squilibrio ormonale nello sviluppo di funzioni”.
Vedi il prossimo capitolo.
Capitolo 5: scrivere funzioni
Le funzioni dovrebbero essere corte e belle, e fare una cosa sola. Dovrebbero essere contenute in una o due schermate di testo (la dimensione ISO/ANSI dello schermo è 80×24, così come tutti noi la conosciamo), svolgere un solo ruolo e farlo bene.
La massima lunghezza di una funzione è inversamente proporzionale alla complessità e alla profondità dei livelli di tale funzione. Quindi, se hai una funzione concettualmente semplice che ha solamente una lunga (ma semplice) lista di “case”, dove devi fare varie piccole cose per ognuno dei differenti casi, è giusto avere una funzione più lunga.
Tuttavia, se hai una funzione complessa e hai il sospetto che uno studente poco dotato del primo anno delle scuole superiori potrebbe non capire cosa faccia la funzione, devi essere ben attento a seguire i limiti massimi consentiti. Usa funzioni ausiliari con nomi descrittivi (puoi chiedere al compilatore di includerele come in-line se ritieni possano essere procedure critiche per le prestazioni, e lui probabilmente farà un lavoro migliore di quello che avresti fatto).
Un’altra particolarità delle funzioni è il numero delle variabili locali. Non dovrebbe superare 5-10, o stai facendo qualcosa di sbagliato. Ripensa la funzione, e dividila in pezzi più piccoli. Il cervello umano, generalmente, può tenere traccia di 7 differenti cose, basta qualcosa in più e va in confusione. Anche se sei una persona brillante, probabilmente ti piacerebbe ricordare cosa hai fatto durante le due scorse settimane.
Questa puntata “filosofica” è terminata, nella prossima vedremo come commentare il codice e come utilizzare tool per la formattazione automatica del codice.misc40.jpg

Un buon stile di programmazione si riconosce anche da come il codice viene commentato. Descrivere in maniera adeguata il codice permette anche ad altri programmatori di capirne il funzionamento. Gli strumenti GNU, inoltre, ci aiutano in maniera automatica o semi-automatica nella formattazione del codice.
Capitolo 6: Commentare il codice
Commentare è una buona abitudine, a volte, però, si rischia di eccedere nella descrizione del codice. Non cercare MAI di spiegare nei commenti COME funziona il tuo codice: è molto meglio scrivere il codice in modo tale che il funzionamento risulti ovvio, è una perdita di tempo cercare di spiegare del codice scritto male.
In generale, i commenti servono per spiegare COSA fa il tuo codice, non COME. Cerca di evitare, inoltre, di inserire commenti all’interno del corpo di una funzione: se la funzione è così complessa che hai bisogno di commentare individualmente alcune sue parti, probabilmente dovresti rileggere bene il capitolo 5. Puoi inserire piccoli commenti per evidenziare qualcosa di ingegnoso o avvertire di qualche pericolo, in tutti i casi cerca di evitare gli eccessi. Inserisci, piuttosto, i commenti in testa alla funzione, spiegando cosa fa e possibilmente PERCHE’.
Capitolo 7: Avete combinato un pasticcio
Bene, ci siamo, abbiamo fatto tutto. Probabilmente la vostra persona di riferimento, vecchio esperto Unix, vi avrà rivelato che “GNU emacs” formatta automaticamente il codice C per te, e tu avrai notato che sì, fa questo, ma le impostazioni di default che usa lasciano a desiderare (in effetti, sono peggiori di una scrittura casuale – un numero infinito di scimmie che scrivono con GNU emacs non produrranno mai un buon programma).
Quindi, o decidi di buttare GNU emacs, o cambiare le impostazioni di default con alcune più sensate. Se avete scelto la seconda potete inserire all’interno del vostro file .emacs le seguenti righe:
(defun linux-c-mode ()
  "C mode with adjusted defaults"
  (interactive)
  (c-mode)
  (c-set-style "K&R")
  (setq tab-width 8 )
  (setq indent-tabs-mode t)
  (setq c-basic-offset 8 ))
Questo definirà il comando M-x linux-c-mode. Quando scrivi un programma inserisci la stringa -*- linux-c -*- da qualche parte nelle prime due linee, così il comando verrà invocato automaticamente. Inoltre potresti inserire, ad esempio
(setq auto-mode-alist
(cons '("/usr/src/linux.*/.*.[ch]$" . linux-c-mode)
auto-mode-alist)
nel tuo file .emacs se vuoi avere automagicamente la modalità linux-c-mode quando modifichi i sorgenti presenti nella directory /usr/src/linux.
In tutti i modi se non riesci ad avere un emacs che formatti in maniera sensata il tuo codice, non tutto è perduto: usa “indent”.
Ancora una volta GNU indent ha la stessa assurda impostazione che ha GNU emacs, ecco perchè hai bisogno di passargli qualche impostazione da riga di comando. Tuttavia, non è così male, perchè anche gli autori di GNU indent riconoscono l’autorità di K&R (i programmatori GNU non sono il diavolo, sono solamente molto disorientati in questa faccenda).
“indent” ha moltissime opzioni, e, specialmente quando viene usato per riformattare i commenti, dovresti prestare attenzione alla pagina man. Ma ricorda: “indent” non corregge gli errori di una brutta programmazione.
In questa puntata abbiamo scoperto come commentare correttamente il codice e come “piegare” GNU emacs e GNU indent alle volontà dello stile K&R. Aggiungerei una nota strettamete personale nella scelta dell’editor: usate vi con l’impostazione di default e vivrete felici! Nel prossimo (ultimo) appuntamento vedremo come usare efficacemente le macro preprocessore.misc41.jpg

Siamo giunti all’ultima puntata di Programmare con stile. Come ultima questione analizzeremo l’importanza di uncorretto uso di macro preprocessore. Le macro sono utilizzate per definire costanti o etichette, ma anche per gestire flussi condizionali durante la fase di compilazione. Vediamo come utilizzarle in maniera ottimale.
Capitolo 8: Macro preprocessore
I nomi di macro che definiscono costanti devono essere tutti in maiuscolo.
#define CONSTANT 0x12345
Quando, invece, si definiscono più costanti in relazione fra loro è preferibile utilizzare “enum”.
I nomi di macro in maiuscolo sono molto apprezzati, ma è preferibile scrivere in minuscolo le macro che definiscono funzioni.
In generale è preferibile utilizzare funzioni “inline” al posto di funzioni definite come macro.
Macro con più righe dovrebbero essere racchiuse in un blocco “do – while”.
#define macrofun(a, b, c)                       \
  if (a == 5) \
  do { \   do_this(b, c); \   } while (0)
Alcune cose da evitare quando si scrivono macro:
1) Macro che includono controlli di flusso:
#define FOO(x)                                  \
        do {                                    \
                if (blah(x) < 0)                \
                        return -EBUGGERED;      \
        } while(0)
è un’implementazione SBAGLIATA. Sembra una chiamata di funzione, ma in realtà fa uscire dalla funzione dove viene chiamata; crea grande confusione ai programmatori che leggeranno il codice.
2) Macro che dipendono dall’avere una variabile locale con un nome noto
#define FOO(val) bar(index, val)
può apparire come un’ottima pensata, ma è dannatamente confusionaria per chi legge il codice ed è a rischio di errori con alcune innocenti modifiche del codice.
3) Macro con argomenti usati come assegnamento
FOO(x) = y;
questo ti procurerà problemi se qualcuno, ad esempio, convertirà FOO in una funzione inline.
4) Dimenticarsi delle precedenze
#define CONSTANT 0x4000
#define CONSTEXP (CONSTANT | 3)
Le macro che definiscono costanti attraverso espressioni devono racchiudere tali operazioni fra parentesi. Fai attenzione a problemi simili con macro che usano parametri. Il manuale del preprocessore cpp e il manuale del compilatore gcc trattano delle macro in maniera esaustiva.
Abbiamo visto che anche nella definizione di macro si possono prendere accorgimenti per programmare in maniera elegante evitanto molto spesso gravi errori. Come nota personale vorrei aggiungere che dall’esperienza che ho avuto nella programmazione kernel ho sempre portato con me un piccolo trucco/macro per commentare un blocco di codice. Basterà racchiudere un blocco fra “#if 0″ e “#endif” che si forzerà il preprocessore a non compilare quel pezzo di codice. Inoltre, editor seri (chi ha detto Vim? :) ) hanno già la sintassi abilitata per riconoscere quel particolare blocco come commento.

Nessun commento:

Posta un commento