di Francesco BalenaFino a non molti anni fa ero abbastanza refrattario a meccanismi e tool che misurassero in qualche modo le caratteristiche e la leggibilità del codice. Erano i tempi di VB6, quando era davvero difficile scrivere del codice "pulito", non disponendo ad esempio di ereditarietà e di gestione strutturata degli errori. Ma il motivo principale per il mio disinteresse per le metriche del software derivava soprattutto dal fatto che scrivevo codice in quasi totale solitudine, e quando interagivo con altri sviluppatori era a livello binario di componenti e interfacce. Io non andavo a mettere il naso nel loro codice e viceversa.
Le cose sono cambiate da quando esiste Code Architects, dove si lavora in team e spesso occorre uniformare gli stili degli sviluppatori che lavorano sullo stesso progetto. Viene allora naturale voler confrontare, ad esempio, la quantità dei commenti che ciascuno inserisce nel proprio codice, oppure la lunghezza e la complessità "media" dei singoli metodi. Allora ho cominciato ad interessarmi a questo argomento, e uno dei risultati di questo interesse è stato il libro Practical Guidelines and Best Practices for the Microsoft Visual Basic .NET and Visual C# Developer, un titolo chilometrico per una raccolta di 735 regole, il cui scopo è di rendere il codice più leggibile, efficiente, e scalabile possibile. (Per la cronaca, la versione italiana, nella traduzione di Natale Fino, appare con il titolo più "umano" Microsoft .NET Framework: Regole di stile e Best Practice... fine dei consigli per gli acquisti!
)
Ovviamente il problema di misurare in qualche modo la complessità del codice esiste da sempre, e molti illustri scienziati hanno definito con esattezza alcuni indici in grado di fornire un valore il più oggettivo possibile per tale complessità. Il significato di alcuni indici è evidente: numero di classi e metodi, numero totale di righe nel programma, numero medio di istruzioni nei metodi, numero medio di metodi per classe, quantità di commenti e percentuale sulle righe totali, ecc. Altre quantità abbastanza interessanti sono la profondità massima dell'inheritance tree delle classi del programma, il numero di classi che derivano da una classe base, il numero di classi esterne da cui l'applicazione dipende, e così via. Sono tutte quantità interessanti, ma che alla fine non forniscono un quadro reale della complessità del programma e della leggibilità del codice - a parte forse la quantità di commenti, che è un indicatore prezioso di quanto facilmente il codice potrà essere manutenuto e aggiornato in futuro.
Tra gli indici più importanti e realmente utili vi è sicuramente l'indice ciclomatico, che indica quanti sono i percorsi possibili all'interno di un metodo. Ad esempio, se un metodo contiene un blocco di istruzioni sequenziali il suo indice ciclomatico è 1; se invece contiene un blocco If o If...Else allora ha un indice ciclomatico pari a 2; se invece contiene un Select Case (VB) o switch (C#) con N blocchi case, allora il suo indice ciclomatico è pari a N se il blocco contiene il blocco Case Else (VB) o default (C#), oppure N+1 se tale blocco non c'è. Il calcolo diventa più complesso quando i blocchi condizionali sono nidificati: un metodo con un blocco If che contiene al suo interno un altro blocco If (con o senza Else) ha un indice ciclomatico pari a 3, perchè sono possibili tre differenti percorsi di esecuzione; se un Select/switch con clausola Case Else/default ha N blocchi Case ciascuno dei quali contiene un blocco If, l'indice ciclomatico sale a N*2, e così via.
L'indice ciclomatico assume una importanza significativa al momento del test del codice. Se un metodo ha indice ciclomatico pari a 3 significa che dovrò preparare almeno tre test per controllare che funzioni bene in tutti i casi. (Ovviamente, in realtà dovrei prepararne un numero maggiore, in quanto occorre di solito controllare le condizioni al limite.) Chi utilizza la metodologia Test Drive Development (TDD) e lavora con NUnit o simile, dovrebbe essere molto sensibile a questo tema, che si preannuncia ancora più importante con il rilascio di Team System, che integrerà questi strumenti all'interno di Visual Studio.
Incidentalmente, l'indice ciclomatico è legato anche al refactoring. Infatti, un metodo con un alto indice ciclomatico dovrebbe essere rifattorizzato per suddividerlo in metodi più semplici. In generale il refactoring è sempre vantaggioso se migliora la leggibilità del codice, ma lo è anche di più se suddividendo un metodo lungo in metodi più semplici si riesce anche a ridurre l'indice ciclomatico complessivo (e quindi il numero di test da effettuare).
Un altro indice abbastanza importante, almeno per il mio stile di codifica, è il numero di exit point di un metodo. Maggiore è questo valore, maggiore è lo sforzo necessario per testare e modificare il codice nel metodo. Idealmente, infatti, tutti i metodi dovrebbero avere un solo exit point, in modo da poter facilmente inserire, se serve, una istruzione per il tracing. Un altro motivo per conoscere questo valore è che in futuro potrei voler modificare il valore di ritorno prima di uscire dal metodo, ad esempio per assicurarmi che la stringa restituita sia in minuscolo, che un numero sia positivo, o che un oggetto non sia Nothing/null.
Quando finalmente si è convinti che avere tutte queste informazioni sul proprio codice non è proprio una perdita di tempo, ci si deve scontrare con la realtà. Nel mondo .NET non esistono molti tool in grado di fornire queste informazioni. Ci sono molti prodotti commerciali, qualche freeware e qualche progetto open source, ma non sono riuscito a trovare un unico prodotto che faccia al caso mio, soprattutto per VB.NET (in C# le cose vanno meglio, come al solito). Ecco quello che ho trovato finora con una ricerca via Google. In molti casi si tratta di prodotti che fanno anche altro (tipicamente, refactoring) oltre a calcolare metriche. Il costo in dollari si riferisce alla versione single-user.
Ecco alcuni tool che lavorano direttamente a livello di Intermediate Language (IL):
vil (freeware): http://www.1bot.com/
NDepend (open source): http://smacchia.chez.tiscali.fr/NDepend.html
Reflector.CodeMetrics (free Reflector add-in): http://projectdistributor.net/Projects/Project.aspx?projectId=42
Questi invece lavorano sui sorgenti C# (alcuni anche con Java):
devMetrics ($75): http://www.anticipatingminds.com/Content/Products/devMetrics/devMetrics.aspx
C# Refactory ($99): http://www.xtreme-simplicity.net/index.cfm?pageID=3&htmlpage=CSharpRefactory.html
RSM ($199): http://msquaredtechnologies.com/m2rsm/index.htm
C#-Metrics ($250): http://www.semanticdesigns.com/Products/Metrics/CSharpMetrics.html
dotEasy (open source, alpha version): http://www.doteasy.addr.com/
Source Monitor (freeware): http://www.campwoodsw.com/index.html
Questi sono gli unici che analizzano i sorgenti VB.NET:
Project Analyzer ($199): http://www.aivosto.com/project/project.html
Essential Project Manager (prezzo non precisato): http://www.powersoftware.com/epm/
Il problema dei tool che lavorano direttamente sugli assembly compilati (anzichè sui sorgenti) è che non possono calcolare esattamente le metriche che riguardano la leggibilità del codice, ad es. la quantità di commenti e il numero di statement per metodo. Ma soprattutto, il problema è che i compilatori VB e C# producono un codice IL con un numero maggiore di confronti e salti di quelli presenti nel sorgente, quindi l'indice ciclomatico calcolato da questi tool è sempre superiore a quello reale. Tutti questi tool supportano gli indici più comuni, anche se non mi pare - a leggere la documentazione - che ce ne sia uno che mostri il numero di exit point di un metodo. La maggior parte sono tool a riga di comando, quindi non hanno una bellissima interfaccia utente ma hanno il vantaggio di poter essere integrati nel proprio processo di build.
Alla fine, ho pensato che scrivere un tool in grado di calcolare gli indici che realmente mi interessano non dovrebbe essere troppo complicato, almeno se si ha una sufficiente padronanza delle regular expression. Quindi ho deciso di mettermi al lavoro sul un piccolo tool tutto mio. Se tutto va bene, tra qualche giorno posto sul blog i miei risultati.
Nel frattempo, se avete dimestichezza con strumenti del genere - sia tra quelli citati sopra che altri che non ho trovato nelle mie ricerche - lasciate pure un commento, please!