Best practice per gestire gli errori in JavaScript in modo pulito

by Anastasia P.
SHARE
Best practice per gestire gli errori in JavaScript in modo pulito
© Guida-HTML5.it

Introduzione

Quando si parla di codice pulito in JavaScript, spesso si pensa alla leggibilità delle variabili, alla modularità o alla qualità delle funzioni. Un aspetto altrettanto importante, però, è la gestione degli errori. Un progetto può avere un’ottima struttura, ma se gli errori vengono trattati in modo confuso, il codice diventa fragile, difficile da debuggare e poco affidabile.

Gestire bene gli errori significa prevedere i casi anomali, comunicare chiaramente cosa è andato storto e mantenere il flusso dell’applicazione sotto controllo. In JavaScript questo vale sia nel codice sincrono sia in quello asincrono: da un semplice try/catch fino alla propagazione di errori personalizzati, fino alla gestione ordinata delle Promise.

In questo tutorial vedremo un sotto-argomento molto pratico e spesso sottovalutato: come scrivere una strategia pulita e coerente per la gestione degli errori in JavaScript. L’obiettivo non è solo evitare crash, ma produrre codice più leggibile, testabile e manutenibile.

Codice completo

// Esempio completo: gestione pulita degli errori in un piccolo flusso di acquisto

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = ´ValidationError´;
  }
}

class PaymentError extends Error {
  constructor(message) {
    super(message);
    this.name = ´PaymentError´;
  }
}

// Validazione input
function validateOrder(order) {
  if (!order) {
    throw new ValidationError(´Ordine mancante.´);
  }

  if (typeof order.customerEmail !== ´string´ || !order.customerEmail.includes(´@´)) {
    throw new ValidationError(´Email cliente non valida.´);
  }

  if (!Array.isArray(order.items) || order.items.length === 0) {
    throw new ValidationError(´L’ordine deve contenere almeno un prodotto.´);
  }

  return true;
}

// Simulazione calcolo totale
function calculateTotal(items) {
  return items.reduce((sum, item) => {
    if (typeof item.price !== ´number´ || item.price <= 0) {
      throw new ValidationError(`Prezzo non valido per il prodotto: ${item.name}`);
    }

    if (typeof item.quantity !== ´number´ || item.quantity <= 0) {
      throw new ValidationError(`Quantità non valida per il prodotto: ${item.name}`);
    }

    return sum + item.price * item.quantity;
  }, 0);
}

// Simulazione pagamento asincrono
async function processPayment(total) {
  // In un caso reale qui chiameresti un provider esterno
  if (total > 1000) {
    throw new PaymentError(´Pagamento rifiutato: importo troppo alto per questa simulazione.´);
  }

  return {
    transactionId: `tx_${Date.now()}`,
    status: ´paid´
  };
}

// Funzione principale
async function placeOrder(order) {
  try {
    validateOrder(order);

    const total = calculateTotal(order.items);
    const payment = await processPayment(total);

    return {
      success: true,
      message: ´Ordine completato con successo.´,
      total,
      payment
    };
  } catch (error) {
    if (error instanceof ValidationError) {
      return {
        success: false,
        type: ´validation´,
        message: error.message
      };
    }

    if (error instanceof PaymentError) {
      return {
        success: false,
        type: ´payment´,
        message: error.message
      };
    }

    return {
      success: false,
      type: ´unknown´,
      message: ´Si è verificato un errore imprevisto.´
    };
  }
}

// Esempio d’uso
(async () => {
  const order = {
    customerEmail: ´[email protected]´,
    items: [
      { name: ´Tastiera´, price: 49.99, quantity: 1 },
      { name: ´Mouse´, price: 25.5, quantity: 2 }
    ]
  };

  const result = await placeOrder(order);
  console.log(result);
})();

Spiegazione

Il codice sopra mostra una strategia semplice ma molto efficace: separare i tipi di errore e gestirli nel punto giusto. Questo approccio rende il flusso più chiaro rispetto a un generico “catch tutto” senza distinzione.

1. Errori personalizzati

Le classi ValidationError e PaymentError estendono Error. Questo è utile perché permette di distinguere la causa del problema senza affidarsi a messaggi testuali fragili. Usare classi dedicate rende il codice più esplicito e più facile da mantenere.

2. Validazione prima della logica principale

La funzione validateOrder controlla subito la struttura dell’input. È una buona pratica perché evita di far proseguire il codice con dati non affidabili. In generale, è meglio fallire presto con un messaggio chiaro piuttosto che lasciare che un errore emerga più avanti in modo confuso.

3. Funzioni con responsabilità chiare

Ogni funzione fa una cosa precisa:

  • validateOrder controlla l’input
  • calculateTotal calcola il totale
  • processPayment simula il pagamento
  • placeOrder coordina il flusso complessivo

Questa separazione aiuta a capire dove nasce un errore e facilita i test unitari.

4. Try/catch nel punto di orchestrazione

Il blocco try/catch non è ovunque, ma solo nella funzione che coordina il processo. Questo è importante: se metti try/catch in ogni funzione in modo indiscriminato, rischi di nascondere gli errori invece di gestirli. Nel codice pulito, il catch deve essere usato con uno scopo preciso: trasformare, classificare o comunicare l’errore in modo utile.

5. Ritorno di un formato coerente

La funzione placeOrder restituisce sempre un oggetto con la stessa struttura di base: success, message e, quando serve, type. Questo rende il consumo del risultato più semplice per chi usa la funzione, soprattutto in un frontend o in un servizio backend.

Best practice

  • Usa errori specifici invece di messaggi generici. Distinguere tra errore di validazione, rete, permessi o logica rende il debug molto più rapido.
  • Valida i dati all’ingresso. Se un input è sbagliato, blocca subito il flusso con un messaggio chiaro.
  • Evita il catch vuoto. Un catch che non fa nulla nasconde problemi seri e rende il bug difficile da individuare.
  • Non abusare del try/catch. Usalo nei punti di aggregazione o dove ha senso gestire davvero il problema, non in ogni funzione.
  • Non usare stringhe come logica di controllo. Meglio controllare error instanceof ValidationError che confrontare testi di errore.
  • Restituisci risultati coerenti. Se una funzione può fallire, definisci un formato di risposta prevedibile per chi la usa.
  • Logga gli errori imprevisti. In produzione è utile registrare gli errori non classificati per analizzarli dopo.
  • Separa errori recuperabili e non recuperabili. Un input sbagliato può essere corretto dall’utente; un errore interno grave potrebbe richiedere un fallback o un blocco del flusso.

Un’altra buona pratica è evitare di mescolare la logica di business con la logica di presentazione dell’errore. Ad esempio, una funzione di dominio dovrebbe lanciare o restituire un errore tecnico, mentre il livello UI dovrebbe tradurlo in un messaggio comprensibile per l’utente.

Riepilogo

Scrivere codice pulito in JavaScript non significa solo usare nomi chiari o dividere il codice in moduli. Significa anche gestire gli errori in modo intenzionale. Una buona strategia di error handling migliora la qualità del progetto perché rende il comportamento dell’applicazione più prevedibile, il debugging più semplice e il codice più facile da estendere.

Il punto chiave è questo: gli errori non vanno nascosti, vanno classificati e trattati nel modo giusto. Usa errori personalizzati, valida presto, mantieni funzioni con responsabilità chiare e centralizza la gestione nei punti di orchestrazione. Così il tuo JavaScript sarà non solo corretto, ma anche più professionale e manutenibile.

Approfondisci con risorse ufficiali

  • MDN Web Docs - Error: documentazione ufficiale sugli oggetti errore in JavaScript.
  • MDN Web Docs - try...catch: guida completa alla gestione delle eccezioni.
  • MDN Web Docs - async function: per capire come gestire gli errori nelle funzioni asincrone.
  • ECMAScript Language Specification: riferimento tecnico ufficiale del linguaggio JavaScript.

Se vuoi, nel prossimo passo posso scrivere un tutorial collegato su come strutturare una strategia di logging pulita in JavaScript oppure su come creare errori personalizzati in progetti reali.

SHARE