Complessità Ciclomatica: Formula, Soglie e Come Abbassarla
Riassunto
La complessità ciclomatica conta i percorsi di esecuzione indipendenti dentro una funzione. La formula è CC = punti di decisione + 1. Punteggi sotto 10 sono accettabili; sopra 15 il refactoring è urgente. Le guard clause, le funzioni helper estratte, e le tabelle di ricerca sono i modi più veloci per abbassare il numero. La complessità cognitiva misura lo stesso problema dal lato della leggibilità e merita di essere tracciata insieme.
Complessità Ciclomatica: Cos'è e Come Abbassarla
La complessità ciclomatica è un numero che vi dice quanti percorsi di esecuzione indipendenti attraversano un pezzo di codice. Una funzione con una sola istruzione if ha due percorsi: uno dove la condizione è vera, uno dove è falsa. Ogni nuovo if, while, for o case aggiunge un altro percorso a questo conteggio.
Data analyst, team operativi che leggono le metriche di sprint, e chiunque analizzi i report di qualità del codice si imbatterà regolarmente in questo numero. Appare nei controlli delle pull request, nei dashboard della qualità del codice, e nelle pipeline di linting automatico.
La metrica è stata inventata da Thomas McCabe nel 1976 ed è rimasta il benchmark standard quando gli ingegneri chiedono: "Questa funzione sta diventando troppo difficile da testare?". La versione breve: mantenetela sotto 10. Sopra 15, siete nel territorio del refactoring. Il buon senso dei vostri colleghi vi ringrazierà se la tenete bassa.
La Formula Dietro la Complessità Ciclomatica
La formula completa è questa:
M = E - N + 2PDove:
M è il punteggio di complessità ciclomatica
E è il numero di archi nel grafo di controllo di flusso
N è il numero di nodi nel grafo di controllo di flusso
P è il numero di componenti connesse (solitamente 1 per funzione)
Per la maggior parte dei casi, non dovete disegnare il grafo di controllo di flusso. Il scorciatoia è più veloce:
CC = Punti di Decisione + 1Contate ogni if, else if, while, for, &&, ||, case, o operatore ternario nella funzione. Aggiungete 1. Questo è il vostro punteggio di complessità ciclomatica.
Una funzione senza branch ha un punteggio di 1 - è una linea retta dall'inizio alla fine. Aggiungete un if, e siete a 2. Aggiungete cinque altre condizioni e siete a 7, che è ancora gestibile. Aggiungete altri dieci e adesso avete un problema.
Il punteggio mappa direttamente al numero minimo di casi di test necessari per coprire ogni percorso. Una funzione con CC = 10 ha bisogno di almeno 10 test per la copertura completa dei percorsi. Questo da solo rende il numero utile durante la pianificazione dello sprint.
Calcolarlo a Mano: Un Esempio Reale
Prendete questa funzione Python:
def process_order(order, user):
if not order:
return None
if not user.is_active:
return None
if order.total > user.credit_limit:
if not user.has_override:
return None
if order.requires_signature and not user.has_signed:
return None
return order.confirm()Atttraversate i punti di decisione:
if not order= +1if not user.is_active= +1if order.total > user.credit_limit= +1if not user.has_override= +1 (annidato nel blocco precedente)if order.requires_signature= +1and not user.has_signed= +1 (l'andconta come punto di decisione)
Totale: 6 punti di decisione + 1 = CC di 7.
Ancora gestibile adesso, ma cresce in fretta. Aggiungete due altre condizioni di validazione dell'input e siete a 9. Aggiungete uno sprint di nuove regole di business e raggiungete 13 - è quando i revisori di codice cominciano a disegnare cerchi rossi.
La condizione annida- ta (if not user.has_override) è il pericolo silenzioso. Le condizioni sequenziali sono più facili da leggere; le condizioni annidate moltiplicano i percorsi in modo esponenziale.

Cosa Significa Davvero il Vostro Punteggio
Le soglie variano leggermente da organizzazione a organizzazione e da dominio a dominio, ma gli intervalli standard sono:
CC 1-10 (Basso): Accettabile per la maggior parte delle basi di codice.
CC 11-15 (Moderato): Aggiungete revisione tra pari; considerate di dividere la funzione.
CC 16-20 (Alto): Effettuate il refactoring prima di aggiungere nuove funzionalità.
CC 21+ (Critico): Elevata probabilità di difetti; prioritario effettuare il refactoring ora.
Per i sistemi critici per la sicurezza (dispositivi medici, software aerospaziale, motori finanziari), molti team fissano il limite massimo a 5-7. Le linee guida NIST raccomandano non superiore a 10 per le funzioni nel software regolamentato. Il codice di prodotto regolare in genere vive comodamente sotto 10.
Una precisazione importante: il punteggio conta le decisioni, non la difficoltà. Un'istruzione switch con 20 mappature di stringhe semplici ha un punteggio di 21 ma richiede cinque secondi per leggerla. Un blocco annidato a 3 livelli potrebbe avere un punteggio di 4 ma richiedere cinque minuti per capirlo. Il numero è un segnale, non un verdetto.
Complessità Ciclomatica vs. Complessità Cognitiva: La Differenza Reale
La complessità ciclomatica è stata progettata per dirvi quanti casi di test avete bisogno. Tratta tutti i punti di decisione allo stesso modo: un if conta lo stesso che sia annidato tre livelli in profondità o in cima alla funzione.
La complessità cognitiva, introdotta da SonarSource, misura qualcosa di diverso: quanto è difficile leggere davvero il codice. Penalizza l'annidamento più aggressivamente delle condizioni sequenziali, il che riflette meglio come gli sviluppatori in realtà elaborano il codice.
La differenza pratica:
# Versione A: 3 condizioni sequenziali -- CC = 4, Cognitiva = 3
def check_a(x, y, z):
if x > 0:
return False
if y > 0:
return False
if z > 0:
return False
return True# Versione B: 3 condizioni annidate -- CC = 4, Cognitiva = 6
def check_b(x, y, z):
if x > 0:
if y > 0:
if z > 0:
return False
return TrueEntrambi hanno CC = 4. Ma la Versione B è più difficile da leggere - dovete tenere a mente la condizione esterna mentre elaborate le interne. La complessità cognitiva giustamente contrassegna la Versione B come più impegnativa.
Usate la complessità ciclomatica per la pianificazione dei test e la valutazione del rischio di difetti. Usate la complessità cognitiva per individuare il codice che rallenterà il vostro team durante la revisione. Rispondono a domande diverse.

Le Formule Complesse in Excel Hanno lo Stesso Problema
Qui è dove diventa rilevante per chi lavora con fogli di calcolo.
Una formula IF profondamente annida- ta ha esattamente la stessa struttura del codice annidato profondamente. Ogni condizione aggiunge un percorso, e ogni livello di annidamento aggiunge carico cognitivo. La formula sottostante ha quattro punti di decisione, per un CC equivalente di 5:
=IF(A2="Sales",IF(B2>10000,"Tier 1",IF(B2>5000,"Tier 2","Tier 3")),IF(A2="Support","Fixed","Other"))Non è ancora terribile. Ma aggiungete un altro tier o un altro dipartimento e la formula diventa quella cosa che nessuno vuole modificare sei mesi dopo - inclusa la persona che l'ha scritta.
La soluzione in Excel rispecchia la soluzione nel codice: semplificate l'annidamento. Usate IFS() quando avete molteplici condizioni che restituiscono valori diversi dalla stessa colonna:
=IFS(AND(A2="Sales",B2>10000),"Tier 1",AND(A2="Sales",B2>5000),"Tier 2",A2="Sales","Tier 3",A2="Support","Fixed",TRUE,"Other")Oppure meglio ancora: estraete la logica dei tier in una colonna helper e referenciatela nella vostra formula principale. Due formule semplici sono più facili da controllare di una complessa. Ecco cosa succede in una vera cella: la complessità scende, la logica rimane.
Cinque Modi per Abbassare la Complessità Ciclomatica
1. Estraete funzioni helper.
Se una funzione valida l'input, trasforma i dati e scrive su un database, sta facendo tre lavori. Dividetela in tre funzioni. Ognuna scende a un punteggio più basso e può essere testata indipendentemente.
2. Usate early return (guard clause).
Invece di annidare la logica di successo dentro molteplici blocchi if, rifiutate i casi cattivi presto e tornate. Il happy path rimane al margine sinistro.
# Prima: annidato - più difficile da seguire
def process(user):
if user:
if user.is_active:
if user.has_permission:
return do_work(user)
return None
# Dopo: guard clause - più facile da estendere
def process(user):
if not user:
return None
if not user.is_active:
return None
if not user.has_permission:
return None
return do_work(user)La stessa complessità ciclomatica in entrambi i casi, ma la versione guard-clause ha una complessità cognitiva più bassa ed è più facile da estendere.
3. Semplificate le espressioni booleane.
Una lunga condizione if (a and b and not c and d) aggiunge molteplici punti di decisione ed è difficile da leggere. Estraetela a una variabile denominata: eligible = a and b and not c and d. La riga if eligible: si legge in linguaggio naturale e l'intento diventa chiaro.
4. Sostituite i condizionali con tabelle di ricerca.
Una lunga catena if/elif che mappa valori a output è una tabella di ricerca nascosta. Sostituitela con un dizionario:
# Prima: CC = 5 per 4 branch
def get_rate(category):
if category == "A":
return 0.05
elif category == "B":
return 0.10
elif category == "C":
return 0.15
else:
return 0.20
# Dopo: CC = 1
RATES = {"A": 0.05, "B": 0.10, "C": 0.15}
def get_rate(category):
return RATES.get(category, 0.20)5. Dividete le funzioni grandi per responsabilità.
Se una funzione va oltre le 25 righe, chiedetevi cosa fa veramente. Le funzioni che gestiscono molteplici responsabilità accumulano punti di decisione da tutti loro. Dividerle le rende più facili da testare e riutilizzare.
Strumenti Che Lo Calcolano Automaticamente
Non dovete contare a mano su ogni pull request. Diversi strumenti si integrano direttamente negli workflow di sviluppo standard:
SonarQube / SonarCloud - calcola la complessità ciclomatica e cognitiva per ogni funzione su ogni pull request; invia avvisi quando le soglie vengono superate
Radon (Python) - eseguite
radon cc yourfile.py -snel terminale per ottenere un punteggio e una lettera per funzioneESLint (JavaScript/TypeScript) - la regola
complexityapplica un limite massimo:"complexity": ["error", 10]CodeClimate - traccia i trend di complessità tra rami e pull request, utile per monitorare la deriva nel tempo
Estensioni VS Code - strumenti come CodeMetrics mostrano il punteggio di complessità inline accanto alle firme di funzione mentre digitate
Un setup pratico: applicate un avviso a 10 e un errore massimo a 15. Questo vi permette di individuare la deriva senza bloccare ogni pull request su un lieve superamento. Esaminate qualsiasi cosa sopra 10 in revisione di codice; automatizzate il blocco a 15.