Gli errori non sono eccezionali: perché la verbosità di Go è una funzionalità

Se hai trascorso del tempo nell’ecosistema Go, hai sicuramente visto i meme. Il “muro di if err != nil” è la critica più comune mossa al linguaggio. Per gli sviluppatori provenienti da Java, Python o TypeScript, la gestione degli errori in Go sembra un passo indietro, un ritorno ai tempi dei controlli manuali e del codice boilerplate.

Ma dopo aver costruito sistemi distribuiti che devono sopravvivere al “caos” del cloud, mi sono reso conto che Errors as Values (Errori come Valori) non è una limitazione. È una scelta di design che privilegia la chiarezza rispetto alla magia.

Il Problema delle Eccezioni “Magiche”

Nei linguaggi che si affidano a blocchi try-catch, un errore è un’“eccezione”: un effetto collaterale che interrompe il flusso naturale del tuo codice. Quando una funzione lancia un’eccezione, in sostanza “teletrasporta” il flusso di controllo al blocco catch più vicino.

Per uno sviluppatore senior, questo “teletrasporto” è un incubo per due motivi:

  1. Flusso di Controllo Invisibile: Non puoi guardare una funzione e sapere con certezza dove potrebbe terminare o dove l’errore verrà gestito.
  2. L’Illusione dell’“Happy Path”: Incoraggia gli sviluppatori a scrivere codice come se tutto andasse alla perfezione, spingendo la “brutta” logica degli errori in un blocco separato, lontano dal contesto del fallimento.

Gli Errori sono solo Dati

In Go, trattiamo gli errori come valori. Sono solo un altro pezzo di dati restituito da una funzione. Questo ti costringe a gestire la realtà della situazione esattamente nel punto in cui si verifica.

1
2
3
4
5
data, err := repository.FetchUser(id)
if err != nil {
    // Qui sei costretto a fare una scelta.
    return fmt.Errorf("impossibile ottenere l'utente %d: %w", id, err)
}

Questo controllo esplicito significa che, mentre leggi il codice, vedi la storia completa della richiesta, inclusi i fallimenti. Non c’è alcuna logica nascosta.

Perché questo è importante per i Senior Engineer

1. Programmare lo Stato di Fallimento

Quando gli errori sono valori, puoi trattarli come qualsiasi altra variabile. Puoi avvolgerli, confrontarli o persino definire comportamenti personalizzati in base al loro tipo. Invece di “catturare” un’eccezione generica, stai programmando il percorso di fallimento.

2. La Traccia delle Briciole di Pane

L’uso del verbo %w di Go con fmt.Errorf ti consente di “avvolgere” gli errori con contesto man mano che risalgono lo stack. Quando un errore raggiunge il tuo logger, non ottieni solo una stack trace; ottieni una storia leggibile da un essere umano: "impossibile aggiornare la fatturazione: impossibile raggiungere il database: timeout della connessione"

3. Simpatia Meccanica

Lo srotolamento dello stack (processo alla base delle eccezioni) è costoso. Un semplice controllo nil è incredibilmente economico. Nei servizi Go ad alte prestazioni, questa gestione esplicita mantiene prevedibile il percorso di esecuzione e basso l’utilizzo della memoria.

Quando “Verboso” Diventa “Robusto”

In un ambiente cloud-native, i timeout di rete, i guasti del disco e gli errori delle API non sono “eccezionali”. Sono previsti. Trattando gli errori come cittadini di prima classe, Go si assicura che tu progetterai il tuo sistema per gestire la complessa realtà del mondo reale, anziché far finta che non esista.

Non nascondere i tuoi errori in un blocco catch. Dai loro il rispetto che meritano.