Vai al contenuto
eLearner.app
Modulo 10 · Lezione 2 di 547/50 nel corso~14 min
Lezioni del modulo (2/5)

Gestione errori idiomatica

La gestione degli errori in Go è esplicita: niente eccezioni, niente try/catch. Ogni funzione che può fallire ritorna error come ultimo valore, e il chiamante decide. Questo capitolo raccoglie i pattern idiomatici per produrre, propagare e ispezionare errori in modo robusto.

La regola d'oro

Go
data, err := os.ReadFile(path)
if err != nil {
    return fmt.Errorf("config %s: %w", path, err)
}

Quattro principi:

  1. error è l'ultimo return.
  2. Controlla immediatamente con if err != nil.
  3. Aggiungi contesto prima di propagare (con fmt.Errorf + %w).
  4. Non ignorare mai un errore: o lo gestisci, o lo logghi, o lo restituisci.
Go
_ = file.Close() // ESPLICITAMENTE ignorato (sai cosa stai facendo)

Errori sentinel

Un error sentinel è una variabile globale var ErrXxx = errors.New("...") esportata, che il chiamante può confrontare:

Go
package store

var ErrNotFound = errors.New("not found")

func Get(key string) (string, error) {
    if !exists(key) {
        return "", ErrNotFound
    }
    // ...
}

Il chiamante usa errors.Is (NON ==), che attraversa la catena di wrap:

Go
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
    // gestisci 404
}

Esempi standard: io.EOF, sql.ErrNoRows, os.ErrNotExist.

Errori custom con tipo

Per portare dati strutturati (codice HTTP, campo invalido, ecc.), definisci un tipo:

Go
type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}

Il chiamante usa errors.As per fare downcast sicuro lungo la catena:

Go
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("campo invalido:", ve.Field)
}

errors.As cammina la catena di Unwrap() finché trova un errore assegnabile al puntatore passato.

Wrapping con %w

fmt.Errorf con il verbo %w produce un errore che avvolge quello originale, preservandolo per errors.Is/errors.As:

Go
if err := db.Query(); err != nil {
    return fmt.Errorf("load users: %w", err)
}

Senza %w (usando %v o %s) ottieni una stringa più ricca ma perdi la catena: il chiamante non potrà più riconoscere errori specifici.

Regole su %w:

  • Uno solo per chiamata fmt.Errorf (multi-wrap richiede errors.Join).
  • L'argomento DEVE essere un error, non una stringa.
  • Aggiungi contesto, non duplicare il messaggio originale.
Go
// male: duplica
return fmt.Errorf("error: %w", err) // "error: file not found"

// bene: contesto utile
return fmt.Errorf("load config %s: %w", path, err)

errors.Join per errori multipli

Da Go 1.20, errors.Join(errs...) combina più errori in uno solo che li mantiene tutti rilevabili da errors.Is/As:

Go
var errs []error
for _, item := range items {
    if err := process(item); err != nil {
        errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
    }
}
if len(errs) > 0 {
    return errors.Join(errs...)
}

Quando usare panic

Mai per errori previsti. Solo in tre casi:

  1. Bug irrecuperabile: invariante violata in un punto che "non può accadere" (panic("unreachable")).
  2. Inizializzazione fallita in init()/main() di un programma standalone (manca config critica → crash veloce).
  3. API di libreria che documenta esplicitamente: es. regexp.MustCompile (panica se il pattern è invalido — accettabile perché il pattern è costante a build time).

Tutto il resto è error. Non usare panic come scorciatoia per propagare in alto: rompe la composizione, salta i defer Close() e travolge il chiamante.

Esercizi

Esercizio#go.m10.l2.e1
Tentativi: 0Caricamento…

Wrappa l'errore di os.ReadFile con fmt.Errorf usando il verbo %w e aggiungendo il contesto 'config:' (con il path).

Caricamento editor…

Soluzione disponibile dopo 3 tentativi

Esercizio#go.m10.l2.e2
Tentativi: 0Caricamento…

Usa errors.Is per verificare se err nella catena di wrap corrisponde alla sentinella ErrNotFound e stampa 'manca!' in tal caso.

Caricamento editor…

Soluzione disponibile dopo 3 tentativi

Quiz#go.m10.l2.e3
Pronto

Quando è idiomatico usare panic in una libreria Go?

Go
func F(x int) {
  // ??? panic(...)
}
Opzioni di risposta