Ci sono funzioni che appena le apri ti fanno venire voglia di chiuderle.
Non sono per forza enormi. Non hanno necessariamente dieci if e quattro cicli
annidati. A volte, guardandole con distacco, sembrano persino innocue: una ventina di righe,
qualche condizione, un paio di return. Roba che, su una metrica qualsiasi, passa il controllo
senza far scattare allarmi.
Eppure c'è qualcosa che non torna.
Devi rileggere due volte una condizione. Poi risali per capire da dove arriva una variabile. Apri un metodo privato, e poi un altro. Scopri che un boolean con un nome apparentemente semplice in realtà rappresenta tre casi diversi, di cui uno gestito altrove. Il codice funziona. I test passano. La complessità ciclomatica non è nemmeno così alta.
Però tu sei già al quinto caffè.
Mi è capitato tante volte, soprattutto su codice gestionale: importazioni, ordini, spedizioni, permessi, stati intermedi. Cose che, prese singolarmente, sembrano semplici. Poi le metti insieme, aggiungi qualche eccezione di business, un paio di casi storici, un comportamento "temporaneo" rimasto lì per anni, e all'improvviso una funzione normale diventa un macello.
Ed è qui che entra in gioco la differenza tra cyclomatic complexity e cognitive complexity.
Una misura il codice. L'altra misura la fatica di interpretarlo.
La complessità che si conta
La cyclomatic complexity risponde a una domanda concreta: quanti percorsi diversi può prendere questo pezzo di codice?
Ogni decisione introduce un possibile bivio. Un if, un case, un
ciclo, certe condizioni composte: tutti elementi che aumentano il numero di strade possibili.
Più rami decisionali ci sono, più percorsi dovresti considerare.
È una metrica utile. Soprattutto quando devi ragionare sui test. Se un metodo ha tanti percorsi, dovrò coprire più casi per essere ragionevolmente sicuro che funzioni. Il problema è quando iniziamo a trattarla come se fosse l'unica misura sensata di "quanto è complicato questo codice".
Perché eseguire codice e leggere codice sono due attività diverse.
Il computer segue un ramo alla volta. Non si distrae, non interpreta, non viene interrotto da una call, non deve chiedersi perché quella variabile si chiami così.
Noi sì.
La complessità che si sente
Due funzioni possono avere una complessità ciclomatica simile, ma una si legge in trenta secondi e l'altra ti obbliga a prendere appunti.
È una differenza che non sempre salta fuori dai numeri.
La senti quando devi ricordarti tutto quello che è successo sopra. La senti quando leggi una
condizione negativa tipo if (!isNotValid()) e ti fermi un attimo, perché il
cervello deve fare il giro largo. La senti quando un nome è tecnicamente corretto, ma non ti
dice davvero niente del dominio.
La senti anche con le logiche "smart". Quelle compatte, all'apparenza eleganti, dove un
ternario fa troppo, una callback nasconde un effetto collaterale, una variabile cambia
significato a metà funzione, un controllo importante viene delegato a un nome rassicurante
tipo isValid, che poi dentro contiene mezzo mondo.
Nessuna di queste cose, da sola, alza in modo drammatico la cyclomatic complexity.
Tutte insieme, però, fanno la differenza tra un codice che leggi e un codice che attraversi con fatica.
La cognitive complexity, come idea, prova a misurare proprio questo: non solo quanti rami ci sono, ma quanto pesano per chi legge. È una metrica imperfetta. La fatica di capire codice non si misura davvero con precisione. Però almeno guarda il problema dal lato giusto: non solo quello della macchina, ma quello della persona che dovrà metterci le mani.
La metafora del magazzino
Sulla carta puoi dire: "per arrivare dallo scaffale A allo scaffale B ci sono cinque percorsi". È una misura utile e oggettiva. Guardi la piantina, conti gli incroci, sai quante decisioni vanno prese.
Questa è la cyclomatic complexity. Poi però entra una persona vera…
Deve ricordarsi che quello scaffale ha due codici molto simili. Che certi prodotti sono sull'ultimo scaffale e quindi serve la scala. Che una corsia è stretta e con quel carrello non ci passi perché c'è roba in mezzo. Che l'etichetta è stata cambiata la settimana scorsa, ma non tutti lo sanno. Che se manca un pezzo bisogna seguire una procedura diversa, che magari non è scritta da nessuna parte perché "si è sempre fatto così".
Non è il numero di bivi a fregarti. È tutto quello che devi ricordare mentre li attraversi.
La cyclomatic complexity è guardare la piantina e contare gli incroci. La cognitive complexity è mettersi nei panni di chi deve fare picking alle 17:55, con tre ordini urgenti, un prodotto fuori posto e un'etichetta che non viene letta dal palmare.
Il problema non è solo quante decisioni devi prendere. È quante informazioni devi tenere in testa mentre le prendi.
Nel codice succede la stessa cosa. Una funzione con pochi rami, ma con nomi ambigui, stati impliciti e regole sparse, è un magazzino dove le etichette mentono. Una funzione con qualche ramo in più, ma esplicita, lineare, con casi ben separati, è un magazzino con i corridoi numerati e le frecce per terra.
E poi è arrivata l'AI
Qui il discorso diventa ancora più frizzante.
Dire "l'AI scrive codice cattivo" è troppo facile, e spesso è anche falso. L'AI può scrivere codice che funziona, ordinato, con una complessità ciclomatica più che accettabile. Il punto è un altro…
Molto codice generato dall'AI tende a essere locale. Risolve bene il problema che hai davanti, dentro la finestra di contesto che le hai dato. Ma non sempre capisce dove quel codice andrà a piazzarsi. Non sempre sa che esiste già un servizio simile da qualche parte. Non sempre rispetta le convenzioni non scritte del progetto. Non sempre usa i nomi che, in quel dominio specifico, hanno senso per chi ci lavora ogni giorno.
E allora ti ritrovi con codice che ha pochi bivi, ma molti sottintesi.
Ma c'è anche un altro aspetto che oggi è importante: quel codice non dovrà leggerlo solo una persona. Sempre più spesso lo leggerà anche un altro agente.
Un agente non si stanca come noi. Non arriva al quinto caffè, non si incazza davanti a un
if scritto male, non ha la sensazione fisica di essersi perso. Però deve
comunque orientarsi. Deve capire quali file sono rilevanti, quali nomi contano davvero,
quali metodi hanno effetti collaterali, quali regole sono esplicite e quali invece sono solo
implicite nel modo in cui il progetto è cresciuto.
E più il codice è cognitivamente complesso, più aumenta il rischio che l'agente faccia la cosa sbagliata.
Per noi la complessità diventa fatica. Per un agente diventa probabilità di errore. Magari non perché "capisce poco", ma perché deve ricostruire troppo da segnali deboli.
È lo stesso problema visto da due prospettive diverse.
Una persona, davanti a quel codice, rallenta. Un agente, davanti a quel codice, può scegliere il punto sbagliato in cui intervenire.
Questo cambia anche il modo in cui dovremmo pensare alla leggibilità.
Scrivere codice leggibile non serve più solo a chi verrà dopo di noi. Serve anche agli strumenti che useremo per farci aiutare. Più vogliamo delegare all'AI una parte del lavoro sul codice, più quel codice deve essere esplicito, coerente, navigabile.
E alla fine è un paradosso: per far lavorare meglio le macchine, dobbiamo scrivere codice più umano.
Il rischio concreto, oggi, non è nemmeno scrivere codice peggiore. È scrivere molto più codice, e avere meno tempo, o meno voglia, di rileggerlo davvero.
Il punto, se c'è
Per anni ci siamo abituati a misurare la complessità come se fosse soprattutto un problema matematico. E va bene… quei numeri servono.
Però il codice non vive nei report. Vive nelle mani di chi lo deve cambiare, che questo sia un umano o un agente.
La cyclomatic complexity ci dice quanto codice dobbiamo testare. La cognitive complexity ci ricorda quanto codice dobbiamo capire.
Sono due domande diverse. Ma nella manutenzione quotidiana, la seconda pesa di più.
Forse il punto è tutto qui: non dovremmo chiederci soltanto se il codice funziona. Dovremmo chiederci se lascia a chi arriva dopo una mappa leggibile, oppure un labirinto ben formattato.
Perché un labirinto ben formattato, oggi, è facilissimo da costruire.
Basta "chiederlo" al primo agent che passa. ;)