Che cos'è il clean code?

Di 쉬었음.com

Il clean code è codice scritto non soltanto per compilare ed essere eseguito, ma affinché altri sviluppatori possano comprenderne l'intento e successivamente modificarlo, estenderlo e verificarlo in sicurezza. Non è un concetto definito da un unico standard internazionale rigoroso o da un punteggio. È piuttosto un termine pratico che comprende obiettivi di qualità quali leggibilità, comprensibilità, manutenibilità, coerenza e sicurezza delle modifiche. google.github.io

All'inizio è facile pensare che sia semplicemente «codice bello da vedere». Nella pratica, tuttavia, i momenti importanti arrivano dopo che il codice è stato scritto per la prima volta: quando si corregge una funzionalità, si trova un bug, si aggiunge un requisito o si revisiona il lavoro di un collega. Il clean code riguarda soprattutto la riduzione del tempo necessario e della probabilità di commettere errori in questi momenti. Pertanto, la chiave non è memorizzare particolari tecniche sintattiche, ma considerare ciò che chi legge deve sapere e dove una modifica avrà un impatto.

Che cosa significa esattamente clean code?

Il software non è un documento che si scrive una volta sola e poi si considera concluso. Il codice esistente viene riletto quando si aggiunge uno stato dell'ordine, si modifica una regola di prezzo o si indaga su un errore. Chi legge può essere lo sviluppatore originale, ma spesso è un altro membro del team o il proprio io futuro. Il clean code indica uno stato in cui chi legge può comprendere in tempi relativamente rapidi il ruolo del codice, i suoi input e output, le condizioni importanti e i probabili punti di modifica.

Qui, «pulito» non indica soltanto un giudizio estetico. Per esempio, anche un codice ben formattato è rischioso da modificare se i suoi nomi sono ambigui, se varie responsabilità sono mescolate in un'unica funzione e se non esiste un modo per verificarlo. Al contrario, il codice può essere migliore dal punto di vista della manutenzione se il suo ruolo è chiaro, se rispetta le convenzioni del team e se dispone di test che permettono di confermare le modifiche, anche senza usare uno stile particolarmente distintivo. La code review esamina non solo lo stile, ma anche il design, la correttezza funzionale, la complessità, i test e la documentazione. google.github.io

Il termine clean code è diventato ampiamente noto grazie al libro Clean Code di Robert C. Martin, pubblicato nel 2008. Tuttavia, le raccomandazioni del libro sono collocate nel contesto di linguaggi specifici e delle pratiche dello sviluppo orientato agli oggetti. Invece di applicare senza modifiche un libro o una regola nota a ogni linguaggio e dimensione di programma, è più appropriato valutare se risolva un problema nell'attuale codebase e nel team. www.informit.com

Perché il codice che funziona non è sufficiente?

Produrre il risultato desiderato per gli input attuali è il requisito più basilare di un programma. Tuttavia, anche una funzionalità corretta è difficile da gestire nel lungo periodo se si rompe facilmente alla modifica successiva. Per esempio, una funzione lunga può contenere il calcolo degli sconti, controlli delle autorizzazioni, rendering della visualizzazione e archiviazione dei dati. Può funzionare ora, ma chi tenta di modificare solo la politica degli sconti ha maggiori probabilità di influenzare anche la gestione delle autorizzazioni o l'ordine di archiviazione.

Il codice difficile da leggere non è soltanto una questione di tempo di lettura maggiore. Senza fiducia nell'intento, gli sviluppatori possono copiare una logica simile, modificare un'area più ampia del necessario o ricreare regole già esistenti. Anche i revisori hanno difficoltà a valutare l'impatto di una modifica. La manutenibilità è la proprietà di non ostacolare le modifiche future, e il clean code punta a migliorare tale manutenibilità.

Tuttavia, nessuno può eliminare in anticipo ogni costo delle modifiche future. Quando i requisiti stessi sono complessi o sistemi esterni impongono vincoli forti, anche il codice sarà complesso in una certa misura. L'obiettivo migliore non è fingere che la realtà sia semplice, ma distinguere la complessità evitabile da quella inevitabile. Se la complessità è necessaria, la sua ragione dovrebbe essere resa visibile attraverso struttura, nomi, test e documentazione.

In che modo i buoni nomi rivelano l'intento del codice?

I nomi sono le informazioni che chi legge incontra più spesso quando cerca di comprendere inizialmente il codice. Nomi generici quali x, data, process e flag possono essere familiari a chi li ha scritti, ma non dicono agli altri che cosa rappresentano. Nomi come expiredCouponCount, isEligibleForRefund e calculateShippingFee, invece, comunicano in modo relativamente diretto lo scopo di un valore o di un'operazione. I nomi significativi sono anche un modo per trasferire nel codice stesso informazioni che altrimenti dovrebbero essere spiegate nei commenti. google.github.io

Una buona denominazione è una questione di specificità, non di lunghezza. Un concetto ampiamente condiviso in un ambito ristretto può avere un nome breve, mentre un valore usato in un ambito più ampio può richiedere più contesto. Per esempio, l'indice di ciclo i può essere comprensibile in un ciclo molto breve. Ma se il valore restituito da una funzione o un campo di un oggetto si chiama soltanto result, è difficile capire se rappresenti un esito positivo, un importo o il risultato di una query.

È utile anche distinguere i verbi dai sostantivi. La lettura tende a essere naturale quando le funzioni usano nomi basati su verbi che rivelano ciò che fanno, mentre valori e oggetti usano nomi basati su sostantivi che rivelano ciò che sono. sendReceipt() è un'azione, mentre receiptEmail è un dato. Tuttavia, allungare un nome non elimina automaticamente l'ambiguità. handleUserData è più lungo, ma resta poco chiaro che cosa gestisca.

// Example with unclear intent
if (a) {
  doIt(b);
}

// Example where the purpose of the condition and action is visible
if (isPaymentApproved) {
  sendOrderConfirmation(order);
}

I nomi nel secondo esempio dovrebbero comunque essere adattati al contesto reale. Il punto è consentire a chi legge di comprendere la decisione importante senza dover cercare lontano le definizioni di a e b. Rispetto a una struttura in cui i commenti ripetono ciò che i nomi già spiegano, fare in modo che nomi e composizione del codice si spieghino da soli comporta un rischio minore che la spiegazione diventi obsoleta dopo una modifica.

Quanto occorre suddividere funzioni e struttura?

Quando una funzione o un modulo fa troppe cose, chi legge deve tenere a mente varie regole contemporaneamente. Se la validazione degli input, il calcolo, le chiamate esterne, la gestione degli errori e la formattazione del risultato sono mescolati in un unico blocco, modificare una parte può richiedere di comprendere l'intero flusso. Separare i passaggi correlati in unità dotate di nome può rendere più facile leggere il flusso di alto livello.

Per esempio, un processo di conferma dell'ordine potrebbe essere mostrato attraverso passaggi quali validateOrder, calculateTotal, reserveInventory e createPayment, che esprimono il flusso di business. Lo scopo della separazione non è aumentare il numero di funzioni, ma rendere più facili da leggere la responsabilità e l'ordine di ogni passaggio. Se una funzione estratta è composta da una sola riga e il suo nome è meno chiaro dell'espressione originale, è difficile concludere che l'estrazione migliori la comprensione.

Una suddivisione eccessiva crea il problema opposto. Per comprendere una singola azione, chi legge potrebbe dover spostarsi continuamente tra molti file e funzioni minimali. Astrazioni come interfacce o tipi hanno il vantaggio di nascondere i dettagli di implementazione, ma possono anche nascondere il contesto necessario. L'astrazione dovrebbe essere usata quando offre un beneficio chiaro, non applicata partendo dall'assunto che «più astrazione significa sempre design migliore». google.github.io

La decisione di suddividere può quindi essere valutata con domande come queste:

  • Questa parte ha un ruolo che può essere spiegato indipendentemente?
  • Il suo nome spiega l'intento meglio della lettura del codice interno?
  • La stessa regola è ripetuta in più punti, offrendo un motivo per riunirla in un solo punto?
  • Crea un confine per cui, quando si effettua una modifica, basta esaminare solo questa parte?
  • Dopo la separazione, seguire le chiamate rende invece meno chiaro il flusso complessivo?

Queste domande non producono automaticamente una risposta. Tuttavia, concentrano l'attenzione sul costo reale che chi legge sostiene per comprendere il codice, anziché su regole superficiali quali «funzioni brevi».

La semplicità equivale ad avere meno funzionalità?

Nel clean code, semplicità non significa rinunciare alle funzionalità necessarie. Significa piuttosto evitare strutture non necessarie, punti di estensione inutilizzati e deviazioni difficili da comprendere che non sono richieste dai requisiti attuali. Se si generalizza basandosi solo su ipotesi riguardo a esigenze future, chi legge oggi deve comprendere casi che non esistono ancora.

Per esempio, creare in anticipo un sistema di plug-in multilivello per una piccola funzionalità con un solo metodo di pagamento può lasciare spazio a future estensioni. Ma aumenta anche nell'immediato i percorsi di codice, la configurazione e le combinazioni che devono essere testate. Al contrario, se l'aggiunta di metodi di pagamento è già confermata e le loro regole differiscono in modo sostanziale, creare un confine comune può ridurre le modifiche future. Nessuna delle due scelte è sempre migliore in anticipo.

La semplicità non significa nemmeno «il minor numero di righe di codice». Comprimere molte condizioni e trasformazioni in una sola riga può sembrare ingegnoso a chi l'ha scritto, ma chi la modifica deve interpretarne precedenze ed eccezioni. Al contrario, usare valori intermedi con nomi appropriati e separare le condizioni può aumentare il numero di righe rendendo al contempo più semplice il ragionamento. Le linee guida per la code review sottolineano inoltre che gli sviluppatori futuri dovrebbero poter leggere, comprendere e modificare il codice. google.github.io

In pratica, è utile considerare insieme due tipi di semplicità. Il primo è la semplicità dell'implementazione stessa: se vi siano pochi stati, rami, dipendenze e duplicazioni non necessari. Il secondo è la semplicità d'uso e di modifica: se chi chiama può usarla correttamente con facilità e se è chiaro il punto da modificare quando cambiano le regole. Una scelta che rende semplice l'uso esterno può talvolta essere migliore anche se l'interno è leggermente più complesso.

Perché è necessario uno stile coerente e perché non basta?

Quando rientri, interruzioni di riga, organizzazione dei file e convenzioni di denominazione variano tutti, chi legge deve interpretare il formato ogni volta. Usare con coerenza uno stile concordato dal team può ridurre l'attenzione spesa sulle differenze superficiali nel codice. Gli strumenti che controllano meccanicamente le regole, come formattatori automatici e linter, possono essere particolarmente utili per questo lavoro ripetitivo.

Tuttavia, seguire soltanto lo stile non rende il codice pulito. Anche se ogni nome segue la stessa convenzione, i ruoli possono restare ambigui; anche se la lunghezza delle righe è corretta, il design può rimanere eccessivamente aggrovigliato. La revisione della qualità del codice considera design, funzionalità, complessità, test e documentazione oltre allo stile. google.github.io

Nell'applicare le regole di stile, in genere è pratico rispettare le convenzioni esistenti del team. Provare una notazione preferita in un solo nuovo file può sembrare un dettaglio minore, ma può indebolire la coerenza dell'intero progetto. Al contrario, una convenzione esistente può essere discussa e cambiata se un miglioramento accresce significativamente la chiarezza. Ciò che conta non è competere su quale regola sia più elegante, ma se il team possa leggere e modificare il codice in modo coerente.

La code review richiede inoltre di distinguere le piccole differenze di preferenza dai problemi che incidono sulla manutenibilità. Pretendere la perfezione in ogni modifica può rallentare il miglioramento stesso. Se una modifica migliora nel complesso manutenibilità, leggibilità e comprensibilità, accettarla in modo incrementale può essere più realistico. google.github.io

Qual è il rapporto tra test e clean code?

I test sono mezzi di verifica eseguibili per il comportamento promesso dal codice. Qui, una promessa indica un comportamento osservabile come «vengono pagati soltanto gli ordini validi», «un ordine già annullato non viene annullato di nuovo» oppure «l'importo specificato viene detratto quando sono soddisfatte le condizioni di sconto». I test forniscono una base per controllare se un comportamento critico si sia rotto dopo una modifica.

Se il clean code viene visto soltanto come codice dall'aspetto gradevole, i test possono sembrare separati da esso. Ma in una definizione che include la modifica sicura, i test sono centrali. Durante una pulizia strutturale, occorre poter confermare che il comportamento esterno sia stato preservato e, quando si aggiunge una nuova regola, occorre verificare che le vecchie regole non siano state rotte accidentalmente. Il codice manutenibile dovrebbe avere test che verificano la logica centrale e il comportamento promesso e che aiutano a individuare la causa dei malfunzionamenti. google.github.io

Avere molti test da solo non garantisce la qualità. Test troppo strettamente accoppiati a un ordine interno secondario possono rendere difficili persino miglioramenti strutturali legittimi. Al contrario, test che omettono condizioni al limite importanti e regole di business potrebbero non contribuire abbastanza alla sicurezza delle modifiche, anche se sono numerosi. Anche i nomi dei test e la struttura arrange-act-assert dovrebbero essere scritti chiaramente, affinché chi legge sappia che cosa è garantito.

Per esempio, se una logica calcola un periodo di idoneità al rimborso, è più significativo testare i confini della regola effettiva, quali la data stessa della scadenza, il momento immediatamente successivo alla scadenza e l'input mancante, anziché controllare solo date ordinarie. I casi da testare dipendono dai requisiti del prodotto e dal rischio. Il punto chiave è fare in modo che i test comunichino non soltanto che «il codice esiste», ma «quale comportamento deve continuare a essere preservato».

Quando sono necessari commenti e documentazione?

I commenti non sono negativi. Sono particolarmente preziosi quando trasmettono informazioni di contesto che il codice fatica a esprimere. Per esempio, i soli nomi potrebbero non comunicare adeguatamente un workaround per un comportamento anomalo in un servizio esterno, vincoli legali o contrattuali, una scelta basata su misurazioni delle prestazioni o il motivo di un codice temporaneo di compatibilità che verrà rimosso dopo una determinata data. Queste informazioni aiutano i futuri manutentori a comprendere perché non dovrebbero sostituirlo con un approccio più semplice. google.github.io

Al contrario, i commenti che traducono semplicemente ciò che il codice già dice possono perdere nel tempo l'allineamento con il codice. Un commento che dice «incrementa il conteggio di 1» accanto a count = count + 1 non aggiunge nuove informazioni. In tal caso, possono avere priorità un nome migliore o una struttura più diretta. Quanto più lunghi diventano i commenti, tanto più vale la pena verificare se segnalino un intento del codice poco chiaro.

Anche la collocazione appropriata della documentazione può differire. Una ragione locale all'interno di una funzione può essere adatta a un commento vicino. Regole d'uso, metodi di configurazione e condizioni di compatibilità condivisi da più moduli possono essere più facili da trovare in documentazione separata o in descrizioni dell'interfaccia. Ovunque venga collocata, l'importante è fornire a chi legge il contesto necessario per prendere decisioni e aggiornarla insieme al codice quando questo cambia.

In che cosa differiscono clean code, refactoring e coding style?

Questi tre termini sono spesso menzionati insieme, ma hanno ruoli diversi. Il clean code è uno stato qualitativo o una prospettiva mirata a un codice facile da comprendere e modificare. Il refactoring è l'attività di migliorare la struttura interna preservando il comportamento osservabile dall'esterno. Il coding style è una convenzione per l'espressione del codice, come rientri, notazione di denominazione e spaziatura.

CategoriaDomanda chiaveAmbito
Clean codeQuesto codice può essere compreso e modificato in sicurezza?Nomi, struttura, complessità, test, documentazione, coerenza
RefactoringCome si può migliorare la struttura preservando il comportamento?Un'attività di miglioramento strutturale
Coding styleIn quale formato il team esprime il codice?Convenzioni di notazione e formattazione

Il refactoring è un modo per creare o mantenere clean code. Per esempio, calcoli del prezzo duplicati possono essere riuniti in un solo punto, nomi ambigui possono essere modificati e condizioni possono essere organizzate in unità più facili da comprendere. Tuttavia, le modifiche strutturali effettuate senza confermare che il comportamento sia preservato possono essere rischiose, quindi test e revisione sono importanti.

Lo stile riduce gli attriti nella collaborazione, ma non risolve automaticamente i problemi di design. Al contrario, un codice chiaro con una struttura funzionante non è automaticamente negativo solo perché il suo stile differisce leggermente. Comprendere questa distinzione riduce l'errore di attribuire lo stesso peso a problemi di formattazione e a rischi reali di manutenzione nelle revisioni. google.github.io

Che cosa dovrebbe avere priorità in presenza di vincoli di prestazioni e sicurezza?

L'enfasi del clean code su semplicità e chiarezza non significa sacrificare prestazioni, sicurezza, compatibilità o affidabilità operativa. Per esempio, una cache richiesta per le prestazioni, passaggi di validazione richiesti per la sicurezza o la gestione della compatibilità con un vecchio sistema esterno possono rendere il codice più complesso. Se tale complessità è basata su requisiti reali e risultati di misurazioni, può essere più appropriata di un'alternativa che sembra soltanto più semplice.

In questa situazione, l'atteggiamento importante non è nascondere la complessità. I vincoli, il comportamento che deve essere garantito e i motivi per non usare un'implementazione convenzionale possono essere resi visibili tramite nomi, struttura, test e commenti necessari. Il principio di dare priorità a fatti tecnici e dati rispetto alle preferenze personali si applica a queste decisioni. google.github.io

Per esempio, se un'implementazione facile da leggere non soddisfa i requisiti di risposta nell'ambiente di produzione reale, esiste un motivo per scegliere un'implementazione più complessa. Tuttavia, non è desiderabile rendere complesso tutto il codice soltanto partendo dal presupposto che sia «per le prestazioni». Dopo aver misurato il problema e confermato i requisiti, occorre confrontare sia i costi sia i benefici della complessità.

Lo stesso vale per la sicurezza. Passaggi quali validazione dell'input, controlli di autorizzazione e gestione degli errori possono allungare il flusso del codice. Ciò non significa che possano essere omessi per rendere il codice più breve. Una buona struttura colloca questi passaggi necessari dove sono facili da riconoscere e aiuta a evitare che regole sensibili vengano disperse arbitrariamente nella codebase.

Quali sono le idee sbagliate comuni sul clean code?

La prima è l'idea sbagliata che «più corto è sempre meglio». Funzioni brevi ed espressioni concise possono aiutare, ma il numero di righe non è il criterio. Una scomposizione e un'astrazione eccessive possono allungare i percorsi di chiamata e nascondere il contesto. Invece di chiedersi se il codice sia diventato più breve, occorre chiedersi se chi legge possa comprendere più facilmente il flusso principale e le sue ragioni. google.github.io

La seconda è l'idea sbagliata che «meno commenti è sempre meglio». L'idea di esprimere con nomi e struttura il contenuto che il codice può spiegare da sé non significa rimuovere informazioni di contesto utili. In particolare, le ragioni delle scelte e i vincoli esterni potrebbero dover rimanere nei commenti o nella documentazione. I buoni commenti non ripetono il codice; forniscono contesto difficile da conoscere dal solo codice. google.github.io

La terza è l'idea sbagliata che «il codice è buono solo se segue ogni regola». Le raccomandazioni sono strumenti di giudizio, non un codice legale applicabile a ogni situazione. Le priorità variano in base alle caratteristiche del linguaggio, alle convenzioni del progetto esistente, ai requisiti di prestazioni e sicurezza e all'esperienza del team. È più importante verificare se l'applicazione di una regola renda effettivamente il codice più chiaro.

La quarta è l'idea sbagliata che «il design debba essere perfetto fin dall'inizio». I requisiti cambiano e alcune informazioni non possono essere note inizialmente. Anziché ritardare le modifiche inseguendo soltanto la perfezione, è più realistico continuare a introdurre piccoli miglioramenti che rendano il sistema attuale nel complesso più facile da leggere e mantenere. google.github.io

Come si può valutare il clean code nella pratica?

È difficile valutare il clean code con una checklist assoluta soltanto, ma davanti a una modifica si possono porre varie domande. Per prima cosa, considerate se una persona che vede il codice per la prima volta possa spiegarne lo scopo principale. Poi, quando si modifica una regola, verificate se il punto da modificare sia relativamente chiaro o se sia necessario modificare anche aree non correlate. Infine, confermate che esistano test o metodi di revisione per verificare il comportamento centrale dopo la modifica.

Ecco domande pratiche da usare durante la scrittura o la revisione di una funzionalità:

  • Si può comprendere approssimativamente il ruolo di un valore, una funzione o un modulo dal solo nome?
  • Una funzione mescola inutilmente regole di business diverse o operazioni esterne?
  • La stessa regola importante è copiata in più punti?
  • Si adatta naturalmente alle convenzioni del team per denominazione, formattazione e organizzazione dei file?
  • Le ragioni delle scelte o i vincoli che il codice non può esprimere sono stati registrati quando necessario?
  • Esiste un modo per verificare il comportamento centrale e le condizioni al limite rischiose?
  • La semplificazione ha trascurato requisiti di prestazioni, sicurezza o compatibilità?
  • L'astrazione o la separazione riducono effettivamente il costo della comprensione, oppure allungano soltanto il percorso che chi legge deve seguire?

Non è necessario rispondere subito a tutte queste domande. Cercare di risolvere ogni problema di design in una piccola modifica può bloccare la revisione. È pratico correggere prima i problemi a impatto elevato e indirizzare il resto verso una direzione migliore nelle modifiche successive. L'obiettivo della code review può essere il miglioramento continuo della manutenibilità, della leggibilità e della comprensibilità del sistema, anziché la produzione di codice perfetto. google.github.io

Conclusione: il clean code è qualità per il cambiamento, non un formato fisso

Il clean code non indica soltanto un elenco di regole tratte da un libro particolare o una formattazione ordinata. È una prospettiva di qualità che rende visibile l'intento del codice nei nomi e nella struttura, riduce la complessità non necessaria, consente una lettura coerente all'interno di un team e permette di verificare il comportamento dopo le modifiche. I commenti servono a trasmettere informazioni di contesto, i test supportano la sicurezza delle modifiche e le astrazioni si usano quando rendono realmente più facile comprendere e modificare.

La forma del buon codice può differire da progetto a progetto. Ciò che conta non è se appaia breve o segua una regola famosa, ma se lo sviluppatore successivo possa comprenderlo e modificarlo correttamente con i requisiti e i vincoli attuali. Migliorare continuamente piccoli aspetti quali nomi, condizioni, test e strutture da questa prospettiva è il punto di partenza pratico del clean code. google.github.iogoogle.github.io

Domande frequenti

Il clean code può essere valutato con una formula o un punteggio fisso?

No. Il clean code non è un singolo standard internazionale né una formula di misurazione; è una prospettiva pratica sulla qualità volta a migliorare comprensibilità, manutenibilità, coerenza e sicurezza delle modifiche. Le scelte appropriate possono variare in base al linguaggio del progetto, al team e ai vincoli operativi.

Il codice è sempre pulito se è breve?

No. Un codice breve talvolta può rendere più chiaro l'intento, ma una compressione, scomposizione o astrazione eccessiva può nascondere il contesto e il flusso di esecuzione, rendendo il codice più difficile da leggere. Il criterio importante non è il numero di righe, ma se chi legge può comprendere l'intento e modificare il codice in sicurezza.

Avere molti commenti significa che il codice è di alta qualità?

Non necessariamente. Il comportamento che può essere espresso tramite nomi e struttura spesso è spiegato meglio dal codice stesso. Tuttavia, i commenti sono preziosi per informazioni di contesto difficili da dedurre dal solo codice, come le ragioni di una decisione, i vincoli esterni o le eccezioni inevitabili.

Clean code e refactoring sono la stessa cosa?

Non sono la stessa cosa. Il clean code indica uno stato in cui il codice è comprensibile e facile da mantenere, mentre il refactoring è l'attività di migliorare la struttura interna preservando il comportamento osservabile dall'esterno. Il refactoring può quindi essere un modo per arrivare a un codice più pulito.

Bisogna rinunciare ai principi del clean code quando serve codice complesso per le prestazioni?

No. La complessità realmente richiesta da prestazioni, sicurezza, compatibilità o condizioni operative può essere necessaria. Invece di ignorare i requisiti perché un approccio più semplice sembra più pulito, scegli la complessità sulla base di misurazioni e prove tecniche e rendine visibile la motivazione.