Principi del codice pulito

Come i principi del codice pulito trasformano la tua esperienza di programmazione

Il codice si scrive una volta e si legge centinaia di volte. Raramente lo sviluppatore che scrive una funzione è colui che la debugga sei mesi dopo, la estende per soddisfare un nuovo requisito o deve comprenderne i casi limite durante un problema in produzione. Ogni decisione presa durante la scrittura del codice, il nome di una variabile, la lunghezza di una funzione, la struttura di una classe, rende il lavoro di chi lo leggerà in seguito più facile o più difficile. Il codice pulito è la disciplina che permette di semplificarlo sistematicamente.

Il concetto è stato formalizzato da Robert C. Martin, detto "Uncle Bob", in Clean Code: A Handbook of Agile Software Craftsmanship (2008), che rimane il testo di riferimento canonico sull'argomento. Refactoring: Improving the Design of Existing Code di Martin Fowler affronta il problema complementare: cosa fare quando il codice esistente non è pulito e necessita di esserlo. Insieme, queste due opere definiscono le basi intellettuali della pratica del codice pulito. Questa guida illustra i loro principi chiave con esempi di codice funzionanti nei linguaggi più diffusi, tra cui RPG e PL/I, per gli sviluppatori che si occupano della manutenzione di sistemi legacy aziendali.

Violazioni del codice pulito che non puoi vedere

SMART TS XL Individua complessità, duplicazioni e codice inutilizzato in linguaggi come COBOL, Java, Python, RPG e altri.

Maggiori Informazioni

Che cos'è il Clean Code?

Il codice pulito è un codice sorgente facile da leggere, facile da comprendere, facile da testare e facile da modificare. In questa definizione, il termine "facile" assume un significato preciso. Il codice accettato da un compilatore non è necessariamente codice pulito. Il codice che supera tutti i test non è necessariamente codice pulito. Il codice è pulito quando un altro sviluppatore, qualcuno che non lo ha scritto, in un contesto non previsto al momento della sua stesura, può comprenderne rapidamente l'intento, modificarlo con sicurezza ed estenderlo senza conseguenze inattese.

La definizione più citata è quella di Martin Fowler: "Chiunque può scrivere codice comprensibile a un computer. I bravi programmatori scrivono codice comprensibile agli esseri umani". La misura della pulizia del codice è la comprensibilità da parte dell'uomo, non l'esecuzione automatica.

Il codice pulito non riguarda l'estetica. Non si tratta di seguire una particolare guida di stile fine a se stessa. Riguarda le proprietà strutturali del codice che determinano quanto impegno richiederà ogni futura modifica. Una codebase che viola i principi del codice pulito accumula costantemente debito tecnico, il costo cumulativo delle scorciatoie passate, fino a quando ogni nuova funzionalità richiede la comprensione e un'attenta gestione della complessità esistente prima di aggiungere qualsiasi cosa di nuovo.

Principi del codice pulito: Guida rapida

I principi del codice pulito di Robert C. Martin possono essere riassunti in un insieme di linee guida pratiche. La tabella seguente associa ciascun principio alla sua regola principale e al problema che previene:

PrincipioRegola fondamentaleProblema che impedisce
Nomi significativiI nomi dovrebbero rivelare l'intento, le variabili, le funzioni, le classiDecodifica cognitiva dall'alto cosa x, tmp, o obj rappresenta effettivamente
Piccole funzioniLe funzioni svolgono una sola funzione; si adattano a una sola schermataMonoliti inverificabili e illeggibili che mescolano le responsabilità.
Responsabilità unicaOgni classe/modulo ha un motivo per cambiareClassi divine che si interrompono quando cambia una qualsiasi di una dozzina di cose non correlate
ASCIUTTO (non ripetere te stesso)Ogni elemento di conoscenza ha una rappresentazioneCorrezioni di bug applicate in una copia ma non nelle altre; deriva logica
KISS (Keep It Simple, ovvero "Mantienilo semplice")Preferisci la soluzione più semplice che funzionaCodice eccessivamente complesso che risolve problemi che nessuno ha
YAGNI (Non ne avrai bisogno)Non implementare funzionalità finché non sono necessarie.Codice speculativo inutile che aggiunge complessità senza alcun beneficio
Principio aperto/chiusoAperto per estensione, chiuso per modificaCodice che interrompe il comportamento esistente quando si aggiunge un nuovo comportamento
Separazione degli interessiResponsabilità diverse in luoghi diversiCodice intricato in cui la modifica di un elemento compromette elementi non correlati.
Evita di commentare chiedendoti cosa; usa i commenti chiedendoti perché.Il codice spiega cosa; i commenti spiegano il perché.Commenti obsoleti che traggono più in inganno che informazione
Regola degli scoutLascia il codice più pulito di come lo hai trovatoDegrado graduale della qualità dovuto a negligenza progressiva

I principi fondamentali del codice pulito spiegati

ASCIUTTO, non ripeterti

Il principio DRY (Don't Repeat Yourself) afferma che ogni informazione deve avere una rappresentazione unica, univoca e autorevole all'interno di un sistema. Quando la stessa logica compare in più punti, queste rappresentazioni inevitabilmente divergono. La modifica di una regola aziendale si trasforma in un'operazione di ricerca e sostituzione in più file, e la mancanza di una sola copia genera un bug.

Il principio DRY (Don't Repeat Yourself) non si applica solo al codice copiato e incollato. Si applica alla configurazione, alla documentazione e agli schemi dei dati. Se le stesse informazioni devono essere mantenute in due posizioni diverse, si viola il principio DRY, indipendentemente dal fatto che il codice sia stato copiato letteralmente.

KISS, Keep It Simple

Il principio KISS sostiene che i sistemi funzionano al meglio quando sono mantenuti semplici e che la semplicità dovrebbe essere un obiettivo primario nella progettazione. La complessità non è indice di sofisticazione, bensì indica che qualcosa avrebbe potuto essere espresso in modo più chiaro.

L'implicazione pratica: quando due soluzioni risolvono lo stesso problema, è preferibile quella più semplice. La soluzione più complessa potrebbe gestire casi limite che non hai ancora incontrato. Renderà sicuramente il codice più difficile da comprendere per chiunque lo incontri.

YAGNI, non ne avrai bisogno

Il principio YAGNI scoraggia l'aggiunta di funzionalità finché non sono effettivamente necessarie. Rappresenta una risposta all'impulso comune di costruire sistemi flessibili ed estensibili per esigenze future che potrebbero non concretizzarsi mai. Ogni astrazione, interfaccia e opzione di configurazione esistente per un ipotetico caso d'uso futuro rappresenta un sovraccarico cognitivo per ogni sviluppatore che legge il codice oggi.

Nomi significativi

I nomi sono il principale meccanismo di comunicazione nel codice sorgente. Una funzione denominata process() non comunica nulla su cosa elabora, quando o cosa restituisce. Una funzione denominata calculateMonthlyInterest() comunica con precisione.

I criteri fondamentali per la scelta di un nome: Riesci a capire cosa rappresenta senza leggerne l'implementazione? Il nome ne rivela lo scopo, i parametri e il valore di ritorno? Avresti bisogno di un commento per spiegarne la funzione?

Piccole funzioni

La regola empirica di Robert C. Martin: le funzioni dovrebbero essere piccole, più piccole di quanto si ritenga necessario. Una funzione che svolge una sola operazione può essere denominata chiaramente, testata in isolamento e compresa senza leggerne l'implementazione. Una funzione che svolge diverse operazioni richiede invece la comprensione simultanea di tutte.

Il principio di responsabilità unica applicato alle funzioni: una funzione dovrebbe fare una sola cosa, farla bene e farla soltanto. Se hai bisogno della congiunzione "e" per descrivere cosa fa una funzione, probabilmente fa troppe cose.

Codice pulito in Java

La verbosità di Java rende la disciplina del codice pulito particolarmente importante. Il codice standard richiesto dal linguaggio può nascondere l'intento del codice se non gestito con attenzione.

Giava

// Before: unclear names, mixed responsibilities, magic numbers
public double calc(int x, int y) {
    double r = 0;
    if (y > 1000) {
        r = x * y * 0.1;
    } else {
        r = x * y * 0.05;
    }
    return r;
}

// After: meaningful names, single responsibility, named constants
private static final double PREMIUM_DISCOUNT_RATE = 0.10;
private static final double STANDARD_DISCOUNT_RATE = 0.05;
private static final int PREMIUM_THRESHOLD = 1000;

public double calculateDiscount(int quantity, int unitPrice) {
    double subtotal = quantity * unitPrice;
    return isPremiumOrder(unitPrice) 
        ? subtotal * PREMIUM_DISCOUNT_RATE 
        : subtotal * STANDARD_DISCOUNT_RATE;
}

private boolean isPremiumOrder(int unitPrice) {
    return unitPrice > PREMIUM_THRESHOLD;
}

Le principali pratiche di codice pulito per Java includono: privilegiare la composizione rispetto all'ereditarietà, utilizzare gli stream per l'elaborazione dei dati invece di cicli prolissi, estrarre i valori numerici magici in costanti denominate e mantenere le classi focalizzate su una singola responsabilità. I ​​principi di codice pulito di Robert C. Martin per Java includono la regola che le classi dovrebbero essere piccole, misurate non in righe ma in responsabilità.

Giava

// Clean Java: streams over imperative loops
List<String> activeUserEmails = users.stream()
    .filter(User::isActive)
    .map(User::getEmail)
    .collect(Collectors.toList());

Codice pulito in Python

La filosofia di progettazione di Python, secondo cui l'esplicito è meglio dell'implicito e il semplice è meglio del complesso, si allinea naturalmente con i principi del codice pulito. PEP 8 è la guida di stile ufficiale di Python e il punto di riferimento per un codice Python pulito.

python

# Before: vague names, long function, no separation
def do_stuff(d):
    res = []
    for i in d:
        if i['a'] > 18:
            res.append(i['n'].upper())
    return res

# After: meaningful names, separated concerns, Pythonic style
def get_adult_names_uppercase(users: list[dict]) -> list[str]:
    return [
        user["name"].upper()
        for user in users
        if user["age"] > 18
    ]

python

# Clean Python: context managers for resource handling
# Before: manual, error-prone
f = open("data.txt")
data = f.read()
f.close()

# After: guaranteed cleanup, self-documenting intent
with open("data.txt") as f:
    data = f.read()

Il codice pulito in stile Python utilizza list comprehension al posto dei cicli manuali per le trasformazioni semplici, suggerimenti di tipo per firme di funzioni autoesplicative, gestori di contesto per la gestione delle risorse e classi di dati o tuple nominate invece di semplici dizionari per i dati strutturati.

Codice pulito in JavaScript e TypeScript

La flessibilità di JavaScript è al tempo stesso il suo punto di forza e la principale sfida per la scrittura di codice pulito. Senza disciplina, i codebase JavaScript accumulano schemi incoerenti, conversioni di tipo implicite e catene di callback intricate.

javascript

// Before: var, callback hell, no error handling
function getUser(id, cb) {
    db.query('SELECT * FROM users WHERE id = ' + id, function(err, rows) {
        if (err) cb(err);
        cb(null, rows[0]);
    });
}

// After: async/await, parameterized query, proper error handling
async function getUserById(userId: number): Promise<User | null> {
    const [rows] = await db.execute(
        'SELECT * FROM users WHERE id = ?',
        [userId]
    );
    return rows[0] ?? null;
}

dattiloscritto

// Clean TypeScript: explicit types replace implicit any
// Before
function process(data) {
    return data.map(x => x.v * 2);
}

// After
interface DataPoint {
    value: number;
    label: string;
}

function doubleValues(dataPoints: DataPoint[]): number[] {
    return dataPoints.map(point => point.value * 2);
}

JavaScript pulito utilizza const per impostazione predefinita, let quando è necessario rilegare, e mai varLe funzioni pure, in cui lo stesso input produce sempre lo stesso output e non presentano effetti collaterali, rappresentano il fondamento più pulito per la logica JavaScript.

Pulisci codice in C#

C# offre potenti funzionalità per scrivere codice pulito ed espressivo. L'evoluzione del linguaggio, con LINQ, record, pattern matching e tipi di riferimento nullable, si muove costantemente verso una sintassi più dichiarativa e leggibile.

nitido

// Before: magic numbers, verbose loop, mutable state
public double CalculateTotal(List<OrderItem> items)
{
    double total = 0;
    foreach (var item in items)
    {
        if (item.Quantity > 10)
            total += item.UnitPrice * item.Quantity * 0.9;
        else
            total += item.UnitPrice * item.Quantity;
    }
    return total;
}

// After: named constant, LINQ, single expression
private const double BulkDiscountRate = 0.9;
private const int BulkDiscountThreshold = 10;

public double CalculateTotal(IEnumerable<OrderItem> items) =>
    items.Sum(item => item.Quantity > BulkDiscountThreshold
        ? item.UnitPrice * item.Quantity * BulkDiscountRate
        : item.UnitPrice * item.Quantity);

Principi di codice pulito C#: utilizzare proprietà anziché campi pubblici per l'incapsulamento, sfruttare LINQ per operazioni dichiarative sui dati, utilizzare record per strutture dati immutabili, preferire le interfacce ai tipi concreti nelle firme dei metodi e utilizzare tipi di riferimento nullable (string? vs string) per rendere esplicita la sicurezza nulla.

Codice pulito in Kotlin

Il design di Kotlin riduce specificamente il codice ripetitivo che rende difficile mantenere pulito il codice Java, aggiungendo al contempo funzionalità, classi di dati, funzioni di estensione e gestione dei valori nulli, che supportano naturalmente la scrittura di codice pulito.

Kotlin

// Before: verbose Java-style Kotlin
class User {
    var name: String = ""
    var email: String = ""
    var age: Int = 0
}

fun processUsers(users: List<User>): List<String> {
    val result = mutableListOf<String>()
    for (user in users) {
        if (user.age >= 18) {
            result.add(user.email)
        }
    }
    return result
}

// After: idiomatic clean Kotlin
data class User(val name: String, val email: String, val age: Int)

fun getAdultEmails(users: List<User>): List<String> =
    users.filter { it.age >= 18 }.map { it.email }

Kotlin's data class Fornisce automaticamente i metodi equals, hashCode, copy e toString, eliminando il codice ripetitivo che rende le classi dati Java prolisse e soggette a errori. Le funzioni di estensione consentono di aggiungere metodi di utilità puliti alle classi esistenti senza ereditarietà.

Codice pulito in RPG e PL/I: linguaggi legacy, principi moderni

I principi del codice pulito si applicano a ogni linguaggio, compresi i linguaggi aziendali che gestiscono sistemi finanziari, piattaforme assicurative e applicazioni governative in tutto il mondo. RPG (Report Program Generator) e PL/I sono ancora attivamente mantenuti in molte organizzazioni, e la stessa disciplina che rende Java o Python "puliti" rende RPG e PL/I sostenibili.

Codice pulito in RPG (ILE RPG) :

rpg

     // Before: cryptic two-character names, magic numbers
     C                   EVAL      D = Q * P * 1.05
     C                   IF        Q > 100
     C                   EVAL      D = Q * P * 0.95
     C                   ENDIF

     // After: meaningful names, named constants, clear intent
      /free
       dcl-c BULK_DISCOUNT_THRESHOLD 100;
       dcl-c BULK_DISCOUNT_RATE      0.95;
       dcl-c STANDARD_RATE          1.05;

       dcl-proc CalculateOrderTotal;
         dcl-pi *N packed(15:2);
           quantity  packed(7:0) value;
           unitPrice packed(9:2) value;
         end-pi;

         if quantity > BULK_DISCOUNT_THRESHOLD;
           return quantity * unitPrice * BULK_DISCOUNT_RATE;
         else;
           return quantity * unitPrice * STANDARD_RATE;
         endif;
       end-proc;
      /end-free

Pratiche chiave per giochi di ruolo puliti: utilizzare il formato libero di ILE RPG (/free) sintassi piuttosto che formato fisso per leggibilità, denominare le procedure in modo descrittivo, sostituire i valori hardcoded con costanti denominate utilizzando dcl-ce scomporre i programmi lunghi in procedure mirate utilizzando dcl-proc.

Codice pulito in PL/I :

pli

/* Before: single-letter names, no structure */
CALC: PROC(X, Y) RETURNS(FLOAT);
  DCL (X, Y, R) FLOAT;
  IF Y > 1000 THEN R = X * Y * 0.1;
  ELSE R = X * Y * 0.05;
  RETURN(R);
END CALC;

/* After: meaningful names, named constants, clear intent */
DCL PREMIUM_THRESHOLD  FIXED DECIMAL(7) INIT(1000);
DCL PREMIUM_RATE       FLOAT           INIT(0.10);
DCL STANDARD_RATE      FLOAT           INIT(0.05);

CALCULATE_DISCOUNT: PROC(QUANTITY, UNIT_PRICE) RETURNS(FLOAT);
  DCL (QUANTITY, UNIT_PRICE) FLOAT;
  DCL SUBTOTAL              FLOAT;

  SUBTOTAL = QUANTITY * UNIT_PRICE;

  IF UNIT_PRICE > PREMIUM_THRESHOLD
    THEN RETURN(SUBTOTAL * PREMIUM_RATE);
    ELSE RETURN(SUBTOTAL * STANDARD_RATE);

END CALCULATE_DISCOUNT;

I principi di codice pulito di PL/I rispecchiano quelli dei linguaggi moderni: identificatori significativi (il limite di 31 caratteri di PL/I è sufficiente per nomi descrittivi), costanti con nome anziché numeri magici, procedure focalizzate su un singolo compito e gestione esplicita degli errori tramite il sistema di condizioni ON.

Strumenti per la pulizia del codice e analisi statica

Applicare manualmente i principi del codice pulito tramite la revisione del codice è necessario, ma non sufficiente su larga scala. Gli strumenti di analisi statica applicano automaticamente le metriche del codice pulito, segnalando le violazioni prima che vengano integrate.

ChiavettaLingue disponibiliCosa impone
SonarQube / SonarCloud30+ lingueComplessità, duplicazione, odori di codice, sicurezza
Stile di controlloJavaConvenzioni di denominazione, formattazione, struttura
PMDJava, ApexCodice duplicato, variabili inutilizzate, complessità
ESLint + typescript-eslintJavaScript, dattiloscrittoStile, complessità, codice inutilizzato, modelli asincroni
Pylint + RadonPythonPEP 8, complessità, indice di manutenibilità
ReShaperC#Stile del codice, codice ridondante, suggerimenti per il refactoring
ClippyRuggineModelli idiomatici, errori comuni
SMART TS XLCOBOL, RPG, PL/I, Java, Python e altro ancoraComplessità tra linguaggi diversi, duplicazione, codice morto

Le metriche di codice pulito più comuni misurate dagli strumenti sono: complessità ciclomica (numero di rami decisionali, sopra 10 è un avviso, sopra 20 è critico), complessità cognitiva (quanto è difficile comprendere il codice, una metrica specifica di SonarQube che migliora la complessità ciclomica per la misurazione della leggibilità) e tasso di duplicazione (percentuale di codice duplicato, sopra il 3% richiede attenzione).

Come SMART TS XL Garantisce un codice pulito in tutte le codebase aziendali.

Gli strumenti per la pulizia del codice sopra elencati operano all'interno di un singolo linguaggio. Negli ambienti aziendali in cui coesistono servizi Java, pipeline Python, programmi batch COBOL, moduli RPG e flussi di job JCL, è necessario misurare simultaneamente le violazioni della pulizia del codice di ciascun linguaggio, e le relazioni tra i componenti di linguaggi diversi sono importanti tanto quanto la qualità all'interno di un singolo file.

SMART TS XL'S analisi statica del codice Applica metriche di codice pulito a tutti i linguaggi presenti nell'ambiente contemporaneamente. La complessità ciclomica, i tassi di duplicazione, l'identificazione del codice morto e le metriche di accoppiamento strutturale vengono calcolate per i programmi COBOL utilizzando la stessa metodologia impiegata per le classi Java, producendo misurazioni di qualità comparabili e unificate per l'intero portfolio di applicazioni.

La funzionalità di analisi dell'impatto rende le violazioni del codice pulito gestibili a livello architetturale: quando un paragrafo COBOL presenta un forte accoppiamento con decine di altri programmi, l'analisi dell'impatto mostra esattamente quali componenti sono interessati prima ancora di iniziare qualsiasi refactoring. Questo trasforma la paralisi da refactoring che i team sperimentano in codebase di grandi dimensioni in un programma di correzione strutturato con un ambito definito.

La mappatura delle dipendenze delle applicazioni identifica le violazioni architetturali del principio di codice pulito a livello di sistema: quali componenti sono diventati classi onnipotenti a livello aziendale, dove le dipendenze circolari violano la separazione delle responsabilità tra i diversi linguaggi di programmazione e dove la stessa logica di business è stata implementata indipendentemente in più sistemi senza che nessuna copia fosse a conoscenza dell'altra. Questa violazione del principio DRY (Don't Repeat Yourself) tra linguaggi diversi, ovvero la stessa regola di business mantenuta separatamente in un programma COBOL, un servizio Java e una pipeline Python, rappresenta la categoria più costosa di violazione del principio di codice pulito nei sistemi aziendali ed è invisibile a qualsiasi strumento monolingue.

Per i team che applicano i principi del codice pulito durante modernizzazione dell'eredità programmi, SMART TS XL Fornisce l'analisi preliminare al refactoring che rende il lavoro gestibile: il codice obsoleto viene escluso dall'ambito prima dell'inizio della conversione, i componenti più complessi vengono identificati per un'attenzione prioritaria e il grafico completo delle dipendenze è disponibile prima di apportare qualsiasi modifica.

Il codice pulito è una disciplina di squadra, non una pratica individuale.

I principi di questa guida sono più facili da enunciare che da mantenere. Ogni singolo sviluppatore può scrivere una funzione pulita in isolamento. La sfida sta nel mantenere un codice pulito all'interno di un team, nel tempo e in una codebase che cresce man mano che i suoi collaboratori cambiano. Ciò richiede tre cose: standard condivisi che tutti conoscono e hanno concordato, strumenti che impongano automaticamente tali standard e una cultura di miglioramento incrementale, la regola dello scout applicata in modo coerente, che rende ogni parte del codice leggermente più pulita di come è stata trovata.

La formulazione di Robert C. Martin rimane la più utile: "Il codice pulito sembra sempre scritto da qualcuno che ci tiene". La prova che qualcuno ci tiene non è l'assenza di bug, ma la presenza di nomi leggibili, funzioni mirate, una struttura chiara e l'assenza di sorprese. È questa la prova che rende un codebase degno di essere utilizzato, a cui vale la pena contribuire e che vale la pena di essere mantenuto nel corso degli anni e dei cambiamenti di team che qualsiasi sistema di successo accumulerà.