Indice
- Un fondamento chiamato Clean Code
- Cosa si intende per codice pulito?
- Principi del codice pulito
- Conclusione

Scrivere codice non significa solo far funzionare un programma, ma anche renderlo leggibile, manutenibile ed efficiente. Il concetto di "Clean Code" si basa su una serie di principi e buone pratiche che permettono agli sviluppatori di scrivere codice chiaro, intuitivo e facilmente modificabile. In questo articolo esploreremo i principi fondamentali del Clean Code, prendendo spunto dal celebre libro di Robert C. Martin, "Clean Code - A Handbook of Agile Software Craftsmanship".
Un fondamento chiamato Clean Code
I 17 capitoli trattano argomenti come la denominazione delle variabili, la scrittura di test e la struttura di funzioni e classi. Ogni capitolo ha ulteriori sottosezioni che seguono una struttura simile: Un esempio negativo viene utilizzato per spiegare una pratica disordinata del codice e poi come procedere al suo posto.
Inoltre, ci sono capitoli riassuntivi su argomenti più complessi, come l'architettura del software o la programmazione concorrente.
Gli esempi di codice contenuti nel libro sono scritti in Java, per cui la conoscenza pregressa del linguaggio costituisce un vantaggio. Tuttavia, ciò non è assolutamente necessario, poiché molti degli esempi sono di facile comprensione e possono essere trasferiti ad altri linguaggi di programmazione.
Cosa si intende per codice pulito?
Una celebre citazione di Grady Booch afferma: “Il codice pulito si legge come una prosa ben scritta.” Questo significa che il codice dovrebbe essere intuitivo e autoesplicativo, riducendo la necessità di commenti superflui.
Ma come si può migliorare la leggibilità del codice e renderlo accessibile ad altri sviluppatori? Il libro di Robert C. Martin risponde a questa domanda fornendo una serie di best practice, come l’uso di nomi chiari per variabili e metodi, la riduzione della complessità e la scrittura di funzioni con una sola responsabilità. Anche piccoli dettagli, come evitare di restituire null o di abusare delle istruzioni switch, possono fare una grande differenza nella qualità del codice.
Prendiamo in esame il seguente codice:
ListaNomi lista = new ListaNomi(); lista.aggiungi("Alice"); lista.aggiungi("Bob"); if (lista.dimensione() > 0) { System.out.println("Primo elemento: " + lista.ottieni(0)); }
A prima vista, il codice sembra funzionare correttamente, ma è possibile migliorarlo. Per esempio, invece di controllare manualmente la dimensione della lista prima di ottenere il primo elemento, potremmo delegare questa logica direttamente alla classe ListaNomi:
Perché non fornire direttamente un metodo per ottenere il primo elemento senza dover controllare manualmente la dimensione della lista?
ListaNomi lista = new ListaNomi(); lista.aggiungi("Alice"); lista.aggiungi("Bob"); System.out.println("Primo elemento: " + lista.primo());
In questo modo, il codice diventa più leggibile e intuitivo. Inoltre, la logica di gestione dell’assenza di elementi può essere nascosta all'interno della classe ListaNomi, riducendo il rischio di errori e rendendo il codice più manutenibile.
Principi del codice pulito
Codice più semplice possibile: KISS
Evitare le ripetizioni inutili: DRY
N.B.: Il termine opposto a DRY è WET (We enjoy typing). Il codice viene definito WET quando appunto nel codice si verificano duplicazioni non necessarie.
L'esempio seguente illustra il principio DRY-Clean-Code. Immaginiamo di dover calcolare lo sconto su due tipi di prodotti in un negozio online.
public class Sconto { public static double calcolaScontoElettronica(double prezzo) { return prezzo - (prezzo * 0.10); // Sconto del 10% su elettronica } public static double calcolaScontoAbbigliamento(double prezzo) { return prezzo - (prezzo * 0.15); // Sconto del 15% su abbigliamento } public static void main(String[] args) { double prezzoElettronica = 1000; double prezzoAbbigliamento = 200; System.out.println("Prezzo scontato elettronica: " + calcolaScontoElettronica(prezzoElettronica)); System.out.println("Prezzo scontato abbigliamento: " + calcolaScontoAbbigliamento(prezzoAbbigliamento)); } }Il problema qui è il codice ripetitivo e difficile da manutenere. Se dobbiamo cambiare la logica di calcolo dello sconto, dobbiamo modificare ogni metodo separatamente. Ora vediamo una versione migliorata, che segue il principio DRY e rende il codice più manutenibile:
public class Sconto { public static double calcolaSconto(double prezzo, double percentualeSconto) { return prezzo - (prezzo * percentualeSconto / 100); } public static void main(String[] args) { double prezzoElettronica = 1000; double prezzoAbbigliamento = 200; System.out.println("Prezzo scontato elettronica: " + calcolaSconto(prezzoElettronica, 10)); System.out.println("Prezzo scontato abbigliamento: " + calcolaSconto(prezzoAbbigliamento, 15)); } }In quest'ultimo caso non c’è più duplicazione, perché la logica dello sconto è centralizzata in un unico metodo. Se dobbiamo cambiare la formula dello sconto, lo facciamo solo in un punto. Infine, possiamo facilmente aggiungere nuovi sconti senza scrivere nuovi metodi.
Single Responsibility Principle (SRP)
Per una migliore comprensione, ecco un esempio: immaginiamo una classe che sia responsabile della gestione degli ordini e della stampa delle fatture. Secondo il principio della responsabilità unica, questa classe dovrebbe essere divisa in due classi separate:
- Ordine - gestisce gli ordini.
- Stampante di fatture - stampa le fatture.
Single Level of Abstraction (SLA)
Per illustrare questo concetto, immaginiamo la funzione di guida di un'auto. Un metodo chiamato guidare() dovrebbe occuparsi esclusivamente della logica di guida, come accelerare, frenare e sterzare. Tuttavia, se all'interno dello stesso metodo venissero inseriti dettagli di implementazione a basso livello, come l'attivazione del motore o la gestione della combustione, si mescolerebbero livelli di astrazione diversi, rendendo il codice più complesso da leggere e mantenere.
Un'indicazione pratica per individuare diversi livelli di astrazione è la presenza di blocchi di codice separati da commenti o spaziature. Spostare tali blocchi in funzioni separate aiuta a migliorare la struttura e la leggibilità del codice.
Composizione tramite eredità
Un'alternativa più efficace è la composizione, che permette di ottenere lo stesso riutilizzo del codice senza introdurre dipendenze eccessive tra le classi. Invece di estendere una classe, è preferibile creare classi indipendenti che collaborano tra loro attraverso interfacce ben definite.
Coerenza nel codice
- Convenzioni di denominazione uniformi per variabili, metodi e classi.
- Struttura del codice organizzata in modo prevedibile.
- Utilizzo di modelli di progettazione ricorrenti per problemi simili.