Introduzione
Quando si parla di best practice e di codice pulito in JavaScript, spesso si pensa subito a nomi chiari, gestione degli errori o modularizzazione. Un sotto-argomento molto pratico, però, è spesso trascurato: scrivere funzioni piccole con una sola responsabilità.
Questo approccio rende il codice più facile da leggere, testare e modificare. In pratica, invece di creare funzioni “tuttofare” che validano, trasformano, filtrano e salvano dati nello stesso punto, conviene dividere il lavoro in passaggi piccoli e ben definiti. È una tecnica semplice, ma ha un impatto enorme sulla qualità del progetto.
In questo tutorial vedremo come applicare questo principio in JavaScript con un esempio realistico: la gestione di un ordine in un e-commerce. Partiremo da una versione poco chiara e poi la rifattorizzeremo in modo pulito.
Codice completo
// Esempio: gestione di un ordine in un e-commerce
// Obiettivo: calcolare il totale finale in modo leggibile e manutenibile
const products = [
{ id: 1, name: "Mouse", price: 25, category: "accessori" },
{ id: 2, name: "Tastiera", price: 50, category: "accessori" },
{ id: 3, name: "Laptop", price: 1200, category: "hardware" }
];
const cart = [
{ productId: 1, quantity: 2 },
{ productId: 3, quantity: 1 }
];
const discountRules = {
hardware: 0.1, // 10% di sconto sui prodotti hardware
bulk: 0.05 // 5% di sconto se la quantità supera 1
};
function findProductById(products, id) {
return products.find(product => product.id === id);
}
function calculateItemSubtotal(product, quantity) {
return product.price * quantity;
}
function calculateItemDiscount(product, quantity, discountRules) {
let discount = 0;
if (product.category === "hardware") {
discount += product.price * quantity * discountRules.hardware;
}
if (quantity > 1) {
discount += product.price * quantity * discountRules.bulk;
}
return discount;
}
function calculateCartTotal(cart, products, discountRules) {
let total = 0;
for (const item of cart) {
const product = findProductById(products, item.productId);
if (!product) {
console.warn(`Prodotto non trovato: ${item.productId}`);
continue;
}
const subtotal = calculateItemSubtotal(product, item.quantity);
const discount = calculateItemDiscount(product, item.quantity, discountRules);
total += subtotal - discount;
}
return total;
}
function formatCurrency(amount) {
return new Intl.NumberFormat("it-IT", {
style: "currency",
currency: "EUR"
}).format(amount);
}
const total = calculateCartTotal(cart, products, discountRules);
console.log("Totale ordine:", formatCurrency(total)); Spiegazione
Il cuore di questa tecnica è semplice: ogni funzione deve fare una cosa sola. Non significa che debba fare pochissimo, ma che il suo compito debba essere chiaro e limitato.
1. Separare le responsabilità
Nel codice sopra, ogni funzione ha un ruolo preciso:
- findProductById cerca un prodotto in base all’ID.
- calculateItemSubtotal calcola il subtotale di un singolo articolo.
- calculateItemDiscount calcola lo sconto applicabile.
- calculateCartTotal coordina il processo e somma i totali.
- formatCurrency si occupa solo della formattazione finale.
Questa separazione rende il codice più leggibile perché il lettore può capire il flusso senza dover entrare subito nei dettagli di implementazione.
2. Ridurre la complessità cognitiva
Una funzione lunga con molte condizioni, variabili temporanee e logiche miste è difficile da comprendere. Al contrario, funzioni piccole abbassano il carico mentale: per capire il programma, basta leggere i nomi delle funzioni e il loro ordine di esecuzione.
Ad esempio, calculateCartTotal non calcola direttamente prezzi, sconti e formattazione. Si limita a orchestrare i passaggi. Questo è un ottimo segno: la funzione principale coordina, le funzioni secondarie eseguono.
3. Favorire il riuso
Quando una funzione ha una sola responsabilità, diventa facile riutilizzarla in altri punti del progetto. Per esempio, formatCurrency può essere usata in una pagina carrello, in una ricevuta o in un riepilogo ordine. Lo stesso vale per findProductById, che potrebbe servire anche in una schermata di dettaglio prodotto.
4. Rendere i test più semplici
Le funzioni piccole sono molto più facili da testare. Se una funzione fa una sola cosa, i casi di test necessari sono più chiari e il debug diventa più veloce.
Per esempio, puoi verificare separatamente che:
- calculateItemSubtotal restituisca il valore corretto dato prezzo e quantità;
- calculateItemDiscount applichi le regole giuste;
- calculateCartTotal sommi correttamente i risultati.
5. Evitare funzioni “Dio”
Un errore comune è creare una funzione enorme che fa di tutto: recupera dati, li valida, li trasforma, applica regole di business, salva risultati e formatta l’output. Questo tipo di funzione è fragile, difficile da estendere e spesso genera bug nascosti.
Invece, il codice pulito tende a distribuire il lavoro in blocchi piccoli e prevedibili.
Best practice
- Scrivi funzioni brevi e mirate: se una funzione cresce troppo, chiediti se sta facendo più cose del necessario.
- Usa nomi descrittivi: il nome deve raccontare l’intento della funzione, non solo il meccanismo.
- Separa logica di business e presentazione: il calcolo del totale non dovrebbe occuparsi del formato valuta.
- Evita effetti collaterali inutili: una funzione che calcola un valore dovrebbe restituire un risultato, non modificare anche dati esterni senza motivo.
- Componi funzioni semplici: un flusso complesso può essere costruito unendo più funzioni piccole e leggibili.
- Rifattorizza quando una funzione cresce: se vedi troppi rami condizionali o troppe variabili temporanee, probabilmente è il momento di dividerla.
- Lascia le funzioni orchestratrici al livello alto: una funzione principale può coordinare il processo, ma non dovrebbe contenere tutti i dettagli.
Riepilogo
Scrivere funzioni piccole con responsabilità singola è una delle abitudini più efficaci per produrre codice JavaScript pulito e professionale. Ti aiuta a:
- leggere meglio il codice;
- ridurre la complessità;
- riusare la logica in più punti;
- testare con più facilità;
- evitare funzioni troppo grandi e difficili da mantenere.
Il principio è semplice: ogni funzione deve avere un solo motivo per cambiare. Se una funzione fa troppo, diventa un punto debole del progetto. Se invece è piccola e focalizzata, il codice risulta più stabile, chiaro e facile da evolvere.
Approfondisci con risorse ufficiali
- MDN Web Docs - JavaScript: documentazione completa e affidabile sul linguaggio.
- MDN Web Docs - Array.prototype.find(): utile per comprendere la ricerca di elementi in un array.
- MDN Web Docs - Intl.NumberFormat: riferimento ufficiale per formattare numeri e valute.
- JavaScript.info: ottima risorsa didattica per approfondire concetti di programmazione in JavaScript.
- Clean Code di Robert C. Martin: non è una risorsa JavaScript-specifica, ma offre principi ancora molto utili per scrivere codice leggibile.
