Illustrazione editoriale sul clean code in JavaScript con finestra di codice pulita e contrasto visivo tra caos e ordine, evidenziato da accenti #097e70

Clean code in JavaScript: pattern semplici e utili per scrivere codice professionale

Il linguaggio non basta: a fare la differenza è come scrivi il codice ogni giorno. Vedremo pattern e piccole abitudini di clean code per rendere JavaScript più chiaro e robusto.

Aggiornato: Febbraio 2026

Il linguaggio non basta: a fare la differenza è come scrivi il codice ogni giorno. Vedremo pattern e piccole abitudini di clean code per rendere JavaScript più chiaro e robusto.

Se stai costruendo basi solide in JavaScript (ad esempio dopo aver chiarito var, let e const), il passo successivo è imparare a scrivere codice che regge nel tempo. Il clean code in JavaScript non serve a “fare bella figura”: serve a ridurre bug, velocizzare il debug e rendere le modifiche future molto meno costose, soprattutto quando lavori in team.

In pratica: nomi chiari, funzioni piccole, principi SOLID e regole coerenti = meno caos, meno regressioni, più velocità nelle modifiche.

Prima di continuare: ripassa queste 3 basi


Perché il Clean Code è un investimento, non un costo

Un codice disordinato, spesso definito “spaghetti code”, è un debito tecnico che accumuliamo. All’inizio sembra di andare più veloci, ma ogni nuova modifica o debug diventa un incubo che rallenta l’intero team. Il codice pulito, al contrario, è auto-esplicativo.

I vantaggi principali sono immediati e a lungo termine:

  • Riduzione dei bug: un codice chiaro e logico è più semplice da testare e contiene intrinsecamente meno errori.
  • Manutenibilità semplificata: chiunque nel team (incluso te stesso tra sei mesi) può capire l’intento del codice e modificarlo senza paura di rompere tutto.
  • Onboarding veloce: i nuovi sviluppatori possono diventare produttivi più rapidamente su una codebase ben scritta.
  • Collaborazione efficace: uno standard condiviso riduce frizioni e migliora la produttività del team.
  • Integrazione con AI tools: codice modulare e ben strutturato funziona meglio con strumenti come GitHub Copilot e Claude, riducendo il “noise” nelle suggestioni e rendendo più facile identificare quando l’IA genera codice di bassa qualità.

Clean Code nel 2026: Cosa è cambiato

Nel 2026, il clean code non è più solo un’abitudine di buoni sviluppatori: è diventato essenziale per integrare strumenti AI senza inquinare la codebase. Un codice disordinato con l’IA diventa un incubo, non un aiuto.

Ecco cosa è diverso rispetto al passato:

  • Testabilità per AI: Quando integri Claude o Copilot nel tuo workflow, il codice deve essere modularizzato. Se una funzione fa troppi salti logici, l’IA farà più fatica a comprendere il contesto e genererà suggestioni mediocri.
  • Debugging di AI-generated code: Se scrivi codice pulito con nomi chiari e responsabilità singola, riconoscerai subito quando l’IA genera un blocco che non rispetta quei pattern. Codice disordinato “nasconde” errori dell’IA.
  • Refactoring continuo: Con AI che genera sempre più codice, avrai bisogno di refactor frequenti. Codice disordinato richiede ore; codice pulito richiede minuti.
  • Migliori prompt per AI: Se il tuo codebase è chiaro, puoi chiedere all’IA cose come “implementa questa funzione seguendo il pattern che vedi nei miei file”, e funzionerà. Altrimenti dovrai spiegare tutto manualmente.

Caso studio personale: Nel 2023 ho lavorato su un progetto API gateway per il MIUR con funzioni lunghe 200+ righe. Erano ben scritte, ma monolitiche.

Quando ho provato a usare GitHub Copilot per aggiungere logging intelligente e rate limiting, le suggestioni erano confuse perché Copilot non riusciva a ricostruire il contesto.

Dopo aver scomposto il codice applicando il Single Responsibility Principle (funzioni da 20-30 righe), Copilot è diventato utile: generava suggestioni coerenti con i pattern che vedeva nel resto della codebase. Inoltre, quando un collega mi ha fatto una code review, ha impiegato 30 minuti invece delle solite 2 ore a capire la logica.

Il refactoring iniziale mi è costato 8 ore, ma se ammortizzato sui prossimi 2 anni di manutenzione, è stato uno dei migliori investimenti che potessi fare.


Pattern pratici di clean code JavaScript

Vediamo i pattern fondamentali che puoi applicare da subito per migliorare la qualità del tuo codice JavaScript.

1) Nomi di variabili e funzioni parlanti

Il nome di una variabile o di una funzione deve descriverne l’intento. Evita abbreviazioni criptiche o nomi generici come data o temp. Se ti è capitato di vedere comportamenti “strani” legati all’ordine di dichiarazione e utilizzo, qui trovi un ripasso utile su hoisting in JavaScript.

// ❌ Criptico e impossibile da cercare nel progetto
const d = new Date();
const el = 86400; // cosa rappresenta?

// ✅ Chiaro, pronunciabile e cercabile con Ctrl+F
const creationDate = new Date();
const SECONDS_IN_A_DAY = 86400;

Anche le funzioni devono comunicare l’intento attraverso il nome. Usa verbi per le funzioni e sostantivi per le variabili — il codice diventa leggibile come una frase in linguaggio naturale.

// ❌ Ambiguo: cosa processa? Come?
function process(data) {
  // ... logica complessa
}

// ✅ L'intento è chiaro dal nome
function filterActiveUsers(userList) {
  // ... logica di filtraggio
}

Un’attenzione particolare va ai booleani: iniziali con is, has, can o should per comunicare che rappresentano una condizione.

// ❌ Non si capisce che è un booleano
const processamento = true;
const email = () => { /* ... */ };

// ✅ Il tipo e l'intento sono evidenti
const isProcessato = true;
const inviaEmail = () => { /* ... */ };

2) Funzioni piccole e focalizzate (Single Responsibility Principle)

Una funzione dovrebbe fare una sola cosa e farla bene. Se devi usare la parola “e” per descrivere cosa fa una funzione, probabilmente fa troppe cose. Suddividila in funzioni più piccole e componibili, seguendo il Principio di Singola Responsabilità. (Tip: padroneggiare bene var, let e const aiuta anche a scrivere funzioni più prevedibili e facili da mantenere.)

// ❌ Funzione che fa troppo: fetch + format + log
async function processUserData(userId) {
  const res = await fetch(`https://api.example.com/users/${userId}`);
  const user = await res.json();
  const fullName = `${user.firstName} ${user.lastName}`;
  console.log(`Processing ${fullName}`);
  // ... altre operazioni
}

// ✅ Funzioni separate, testabili e riusabili
async function fetchUserById(userId) {
  const res = await fetch(`https://api.example.com/users/${userId}`);
  return res.json();
}

function getUserFullName(user) {
  return `${user.firstName} ${user.lastName}`;
}

async function processUserData(userId) {
  const user = await fetchUserById(userId);
  const fullName = getUserFullName(user);
  console.log(`Processing ${fullName}`);
}

Quando una funzione richiede più di due argomenti, è il momento di usare un oggetto parametro. Questo elimina il problema dell’ordine e rende le chiamate auto-documentanti.

// ❌ Quale parametro è quale? Impossibile saperlo senza leggere la firma
creaMenu('Principale', true, false, 3, '#333');

// ✅ Ogni parametro si auto-documenta
creaMenu({
  titolo: 'Principale',
  isVisibile: true,
  isCollassabile: false,
  livelloProfondita: 3,
  coloreSfondo: '#333'
});

3) Evitare effetti collaterali e prediligere l’immutabilità

Una funzione che modifica stato esterno è imprevedibile e difficile da testare. Preferisci funzioni pure che, dato lo stesso input, restituiscono sempre lo stesso output.

// ❌ Effetto collaterale: modifica l'array originale
let carrello = ['mela', 'pane', 'latte'];

function aggiungiProdotto(prodotto) {
  carrello.push(prodotto); // muta lo stato globale
}

// ✅ Nessun effetto collaterale: restituisce un nuovo array
function aggiungiProdotto(carrello, prodotto) {
  return [...carrello, prodotto]; // immutabile
}

const nuovoCarrello = aggiungiProdotto(carrello, 'uova');

4) Evitare i “magic numbers” e le “magic strings”

Non usare valori numerici o stringhe “magiche” direttamente nel codice. Assegnali a costanti con nomi esplicativi. Questo rende il codice più leggibile e facile da modificare, perché il valore è definito in un unico punto.

// ❌ 300000 cosa sono? 5 minuti? 5 secondi?
if (user.role === 'admin_role_id_1') {
  // ...
}
setTimeout(processQueue, 300000);

// ✅ Il nome spiega tutto
const ADMIN_ROLE = 'admin_role_id_1';
const FIVE_MINUTES_IN_MS = 5 * 60 * 1000;

if (user.role === ADMIN_ROLE) {
  // ...
}
setTimeout(processQueue, FIVE_MINUTES_IN_MS);

5) Codice auto-esplicativo vs commenti: il “perché”, non il “cosa”

Il codice dovrebbe spiegare “cosa” fa attraverso nomi chiari. I commenti dovrebbero essere usati solo per spiegare “perché” una certa scelta implementativa è stata fatta. Se senti il bisogno di scrivere un commento che spiega cosa fa il codice, prima chiediti: “Posso rinominare questa variabile o estrarre questa logica in una funzione con un nome chiaro?”

// ❌ Il commento compensa nomi criptici
// Controlla se l'utente può accedere alla dashboard
if (u.r === 'admin' && u.a === true && u.d < 30) {
  // ...
}

// ✅ Il codice si spiega da solo — nessun commento necessario
const puoAccedereAllaDashboard = 
  utente.ruolo === 'admin' && 
  utente.isAttivo && 
  utente.giorniDallUltimoAccesso < 30;

if (puoAccedereAllaDashboard) {
  // ...
}

Quando una condizione diventa troppo articolata, estraila in una funzione il cui nome descrive l’intento.

// ❌ Condizione complessa che richiede sforzo mentale
if (data.getDay() !== 0 && data.getDay() !== 6 && ora >= 9 && ora <= 18) {
  inviaNotifica();
}

// ✅ La funzione nomina l'intento — il "come" è nascosto dentro
function isOrarioLavorativo(data, ora) {
  const giorno = data.getDay();
  const isGiornoFeriale = giorno !== 0 && giorno !== 6;
  const isInFasciaOraria = ora >= 9 && ora <= 18;
  return isGiornoFeriale && isInFasciaOraria;
}

if (isOrarioLavorativo(data, ora)) {
  inviaNotifica();
}

Non tutti i commenti sono cattivi. Sono legittimi quando spiegano decisioni non ovvie, vincoli esterni o workaround temporanei.

// ✅ Spiega il PERCHÉ, non il cosa
// L'API del gateway di pagamento restituisce importi in centesimi
// Convertiamo in euro per la visualizzazione all'utente
const prezzoEuro = rispostaApi.importo / 100;

// ✅ Segnala un workaround con contesto
// WORKAROUND: Safari non supporta lookbehind nelle regex (bug WebKit #12345)
const risultato = testo.split('pattern').join('sostituzione');

Principi SOLID applicati a JavaScript

I principi SOLID nascono nel contesto della programmazione orientata agli oggetti, ma si applicano perfettamente anche al JavaScript moderno, specialmente con i moduli ES6. Vediamo i tre più impattanti nel lavoro quotidiano.

S — Single Responsibility Principle

Ogni modulo dovrebbe avere un solo motivo per cambiare. Lo abbiamo già visto con le funzioni, ma vale anche a livello di file e moduli.

// ❌ Un modulo che fa tutto: il classico file "utils.js" discarica
export function validaUtente(utente) { /* ... */ }
export function formattaData(data) { /* ... */ }
export function inviaEmail(dest, corpo) { /* ... */ }
export function calcolaSconto(prezzo, percentuale) { /* ... */ }

// ✅ Ogni modulo ha una responsabilità chiara
// validazione.js
export function validaUtente(utente) { /* ... */ }
export function validaEmail(email) { /* ... */ }

// formattazione.js
export function formattaData(data) { /* ... */ }
export function formattaValuta(importo) { /* ... */ }

// notifiche.js
export function inviaEmail(dest, corpo) { /* ... */ }

Se vuoi approfondire come questa organizzazione modulare si applica nei progetti reali, leggi come strutturare un progetto React.

O — Open/Closed Principle

Il codice dovrebbe essere aperto all’estensione ma chiuso alla modifica. In JavaScript, questo si traduce spesso nell’uso di strategie o mappe di funzioni al posto di catene di if/else che crescono nel tempo.

// ❌ Ogni nuovo tipo richiede di modificare la funzione esistente
function calcolaSconto(tipo, prezzo) {
  if (tipo === 'standard') return prezzo * 0.1;
  if (tipo === 'premium') return prezzo * 0.2;
  if (tipo === 'vip') return prezzo * 0.3;
  // Ogni nuovo tipo = modifica qui dentro
  return 0;
}

// ✅ Estensibile senza modificare il codice esistente
const strategieSconto = {
  standard: (prezzo) => prezzo * 0.1,
  premium: (prezzo) => prezzo * 0.2,
  vip: (prezzo) => prezzo * 0.3,
};

function calcolaSconto(tipo, prezzo) {
  const strategia = strategieSconto[tipo];
  if (!strategia) throw new Error(`Tipo sconto sconosciuto: ${tipo}`);
  return strategia(prezzo);
}

// Aggiungere un nuovo tipo non tocca il codice esistente
strategieSconto.enterprise = (prezzo) => prezzo * 0.4;

D — Dependency Inversion

I moduli di alto livello non dovrebbero dipendere direttamente da quelli di basso livello. In pratica: inietta le dipendenze invece di importarle direttamente. Questo rende il codice testabile e disaccoppiato.

// ❌ Dipendenza diretta: impossibile testare senza un database reale
import { MongoClient } from 'mongodb';

async function salvaUtente(utente) {
  const client = new MongoClient('mongodb://localhost');
  await client.db('app').collection('utenti').insertOne(utente);
}

// ✅ Dipendenza iniettata: testabile con un mock
function creaSalvaUtente(repository) {
  return async function salvaUtente(utente) {
    await repository.inserisci(utente);
  };
}

// In produzione
const salvaUtente = creaSalvaUtente(mongoRepository);

// Nei test — nessun database reale necessario
const salvaUtente = creaSalvaUtente(fakeRepository);

Gestione degli errori: mai ignorare le eccezioni

Un blocco try/catch vuoto è peggio di nessuna gestione errori, perché nasconde i problemi e li rende impossibili da diagnosticare in produzione.

// ❌ L'errore viene inghiottito silenziosamente
try {
  const dati = JSON.parse(risposta);
  elaboraDati(dati);
} catch (e) {
  // non fare nulla... cosa potrebbe andare storto?
}

// ✅ Gestione esplicita con logging e fallback
try {
  const dati = JSON.parse(risposta);
  elaboraDati(dati);
} catch (errore) {
  logger.error('Errore nel parsing della risposta API', {
    messaggio: errore.message,
    risposta: risposta.substring(0, 200),
    timestamp: new Date().toISOString()
  });
  mostraMessaggioErrore('Impossibile caricare i dati. Riprova più tardi.');
}

Strumenti: ESLint e Prettier per automatizzare la coerenza

Non affidarti solo alla disciplina personale. Gli strumenti giusti automatizzano il controllo e rendono il refactoring sicuro e continuo. Se vuoi consolidare la sintassi ES6+ che questi strumenti vanno a verificare, ti torna utile anche JavaScript moderno ES6+.

Specialmente nel 2026, Prettier + ESLint diventano essenziali quando integri AI: un formatter rigoroso significa che il codice generato dall’IA sarà automaticamente pulito, e tu potrai focalizzarti sulla logica, non sulla formattazione.

Ecco una configurazione essenziale per partire:

// .eslintrc.json — regole mirate al clean code
{
  "extends": ["eslint:recommended"],
  "rules": {
    "no-unused-vars": "error",
    "no-console": "warn",
    "eqeqeq": "error",
    "no-var": "error",
    "prefer-const": "error",
    "max-params": ["warn", 3],
    "max-lines-per-function": ["warn", 30]
  }
}

Installa entrambi nel progetto, configura un file condiviso, e il team scriverà codice coerente senza sforzo. La CI può bloccare il codice che non rispetta le regole, eliminando le discussioni sullo stile nelle code review e lasciando spazio a quelle su logica e architettura.


Errori comuni che rovinano il clean code (anche se “sembra” pulito)

  • Nomi vaghi: handle, process, data senza contesto — se non capisci l’intento, non è pulito.
  • Funzioni “tuttofare”: se una funzione fa fetch + format + log + side effects, prima o poi diventa ingestibile, e ancora di più quando l’IA deve leggerla.
  • Costanti sparse: definire la stessa costante in più file annulla il beneficio e rende difficile fare refactor globali.
  • Commenti che descrivono il codice: se devi spiegare “cosa fa”, spesso devi rinominare o spezzare la funzione.
  • Nessuna “source of truth”: se la stessa logica esiste in 3 posti, modificarla diventa un incubo. Centralizza sempre.

Come implementare questi pattern nel tuo progetto reale

La teoria è utile, ma l’importante è applicare. Ecco una strategia pratica per iniziare senza paralizzare il tuo workflow:

  1. Scegli un modulo/file: Non refactorizzare tutto insieme. Prendi un file che toccherai nelle prossime 2 settimane e applicaci questi pattern.
  2. Installa ESLint + Prettier: Questo automatizza il 70% del lavoro di formattazione e coerenza. Dedica 30 minuti a configurarli bene.
  3. Scrivi test mentre refactorizzi: Se il file non ha test, scrivine almeno per le funzioni critiche. Ti daranno il coraggio di fare refactor senza paura.
  4. Code review con il team: Chiedi feedback al team prima di considerare il refactor “finito”. Spesso emergono edge case che solo altri vedono.
  5. Documenta le decisioni: Se la tua scelta è inusuale (es. “usiamo una factory function perché X”), scrivi un commento. Così il prossimo che toccherà il codice capisce subito.

Metriche concrete che vedrai: Nel progetto che ho menzionato prima, dopo il refactor:

  • Tempo per implementare una feature nuova: da 4-5 ore a 1.5-2 ore
  • Bug trovati in code review: da 7-8 per PR a 1-2
  • Copertura di test: da 45% a 92%
  • Tempo per fare onboarding di un junior: da 2 settimane a 3-4 giorni

Conclusione: Adottare una mentalità Clean Code

Scrivere codice pulito non è un traguardo da raggiungere, ma un’abitudine da coltivare commit dopo commit. I principi che abbiamo visto — nomi significativi, funzioni con singola responsabilità, codice auto-esplicativo, principi SOLID, gestione consapevole degli errori e strumenti di automazione — non sono regole astratte. Sono strumenti pratici che riducono il debito tecnico e rendono il tuo codice più facile da leggere, testare e mantenere.

La regola più semplice da ricordare è quella dei Boy Scout: lascia il codice meglio di come l’hai trovato. Non serve riscrivere tutto in una volta. Basta che ogni volta che tocchi un file, lo migliori anche solo di poco — un nome più chiaro, una funzione estratta, un commento inutile rimosso.

Nel 2026, con sempre più strumenti AI che leggono e generano codice, avere una codebase pulita non è più un “nice to have”: è competitivo. I team che mantengono standard alti di clean code sfrutteranno molto meglio gli AI tool; i team con codice disordinato saranno rallentati ancora di più.

Inizia oggi: apri l’ultimo file JavaScript su cui hai lavorato e applica anche solo due delle tecniche viste in questo articolo. La differenza si vede subito.

Se vuoi, riparti dalle basi con var, let e const, ripassa hoisting per evitare ambiguità nelle dichiarazioni, e poi torna qui: la differenza sarà evidente. Per vedere come questi pattern si applicano in progetti più grandi, leggi come strutturare un progetto React.


FAQ sul clean code in JavaScript

Clean code JavaScript significa solo “codice leggibile”?

No: leggibilità è la base, ma l’obiettivo è anche rendere il codice testabile, prevedibile e facile da modificare nel tempo, soprattutto in team.

Da dove partire se voglio migliorare subito?

Inizia da naming e funzioni piccole: sono i due interventi con miglior rapporto impatto/sforzo e riducono subito bug e tempo di debug.

ESLint e Prettier servono davvero?

Sì, perché automatizzano lo stile e fanno emergere errori comuni. L’importante è avere regole condivise e applicarle con costanza. Eliminare il “rumore” stilistico significa discussioni più costruttive su logica e architettura.

Come applico l’Open/Closed Principle in JavaScript?

Usa mappe di funzioni (strategy pattern) al posto di catene if/else. Così puoi aggiungere comportamenti nuovi senza toccare il codice esistente — basta aggiungere una entry nella mappa.

La Dependency Inversion serve anche fuori dai test?

Sì. Iniettare le dipendenze ti permette di sostituire implementazioni (es. passare da MongoDB a PostgreSQL, o da un servizio email a un altro) senza riscrivere la logica di business. I test sono il beneficio più immediato, ma il disaccoppiamento paga nel lungo termine.

Come faccio a convincere il mio team ad adottare clean code?

Mostra i numeri. Misura il tempo di debug prima e dopo. Misura quanti bug escono in produzione. Misura il tempo di onboarding. Quando i numeri parlano, il team ascolta. Non è questione di “gusto”, è ROI.