Mappa Via Marconi 20, Bussolengo (VR)
Email info@devinterface.com

Scrivere codice pulito: best practice e principi fondamentali

Indice


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

Con il titolo “Clean Code - A Handbook of Agile Software Craftsmanship”, Robert C. Martin, chiamato anche Uncle Bob, scrive in oltre 450 pagine i modi e i mezzi per scrivere buon codice. Il libro è stato pubblicato da Prentice Hall nel 2008 e da allora ha venduto più di 160.000 copie solo nella versione inglese.

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?

Il codice pulito è un codice che risulta semplice da leggere e comprensibile per qualsiasi sviluppatore. Ogni parte del codice dovrebbe comunicare chiaramente la propria funzione senza richiedere uno sforzo eccessivo per l’interpretazione. Un codice pulito non è solo corretto dal punto di vista funzionale, ma è anche strutturato in modo che chiunque possa modificarlo e ampliarlo con facilità.

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

Come si fa a scrivere codice pulito? Il codice pulito consiste nel prendere in considerazione alcuni principi guida della programmazione. Non si tratta tanto di istruzioni specifiche su cosa si dovrebbe programmare in una certa situazione e come, quanto piuttosto di uno sguardo riflessivo sulla propria pratica di lavoro come sviluppatore. Il significato di codice pulito è quindi controverso all'interno della comunità degli sviluppatori: Ciò che è “pulito” per un gruppo di programmatori può essere considerato “sporco” da altri. Il grado di pulizia di un codice è quindi sempre, in una certa misura, soggettivo. Di seguito presentiamo alcuni principi consolidati di codice pulito che la maggior parte degli sviluppatori trova utili.



Codice più semplice possibile: KISS

KISS (Keep it simple, stupid) è uno dei più antichi principi di pulizia del codice. È stato utilizzato dalle forze armate statunitensi negli anni '60. KISS ricorda ai programmatori di progettare il codice nel modo più semplice possibile. La complessità non necessaria deve essere evitata. Nella programmazione non esiste mai un solo modo per risolvere un problema. Un compito può sempre essere espresso in diversi linguaggi e formulato con diversi comandi. I programmatori che seguono KISS devono quindi sempre chiedersi se possono risolvere un determinato problema in modo più semplice.



Evitare le ripetizioni inutili: DRY

DRY (Don't repeat yourself) è, per così dire, una concretizzazione di KISS. Un codice pulito secondo il principio DRY significa che una funzione deve avere una rappresentazione unica e quindi non ambigua all'interno del sistema complessivo. Le duplicazioni devono essere evitate a tutti i costi, in modo da non dover riscrivere più volte lo stesso codice. In questo modo si risparmia tempo di sviluppo e si possono correggere gli errori una sola volta.

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)

Questo principio è presto spiegato: ogni unità di codice deve avere un compito chiaramente definito. Questo aumenta la leggibilità e semplifica la testabilità. 

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.
 Ciò significa che ogni classe ha un solo compito. Questo rende il codice più chiaro e più facile da mantenere.



Single Level of Abstraction (SLA)

Il Single Level of Abstraction (SLA) stabilisce che una funzione dovrebbe operare a un unico livello di astrazione. Mescolare livelli diversi all'interno della stessa funzione può compromettere la leggibilità e rendere il codice difficile da comprendere.

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à

L'ereditarietà è un concetto fondamentale della programmazione orientata agli oggetti, ma un suo utilizzo improprio può rendere il codice rigido e difficile da testare. Quando una classe madre è troppo dipendente dalle sue classi figlie, il codice diventa più fragile e soggetto a problemi di manutenzione.

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

Seguire i principi del Clean Code non è sufficiente se il codice non è coerente. Un codice in cui vengono adottati stili diversi o convenzioni non uniformi diventa difficile da leggere e mantenere. Per garantire coerenza, è importante definire e rispettare standard chiari, che includano:

 
  • 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.
 L'uniformità facilita la comprensione del codice e riduce il tempo necessario per familiarizzare con nuovi progetti.




Conclusione

Scrivere Clean Code non è solo una questione di estetica, ma una pratica fondamentale per garantire che il software sia leggibile, mantenibile e scalabile nel tempo. Seguendo principi come KISS, DRY, SRP, SLA e favorendo la composizione rispetto all’ereditarietà, gli sviluppatori possono migliorare la qualità del loro codice e ridurre i costi di manutenzione.