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
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("config %s: %w", path, err)
}Quattro principi:
errorè l'ultimo return.- Controlla immediatamente con
if err != nil. - Aggiungi contesto prima di propagare (con
fmt.Errorf+%w). - Non ignorare mai un errore: o lo gestisci, o lo logghi, o lo restituisci.
_ = 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:
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:
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:
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:
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:
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 richiedeerrors.Join). - L'argomento DEVE essere un
error, non una stringa. - Aggiungi contesto, non duplicare il messaggio originale.
// 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:
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:
- Bug irrecuperabile: invariante violata in un punto che "non può accadere" (
panic("unreachable")). - Inizializzazione fallita in
init()/main()di un programma standalone (manca config critica → crash veloce). - 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
Wrappa l'errore di os.ReadFile con fmt.Errorf usando il verbo %w e aggiungendo il contesto 'config:' (con il path).
Soluzione disponibile dopo 3 tentativi
Usa errors.Is per verificare se err nella catena di wrap corrisponde alla sentinella ErrNotFound e stampa 'manca!' in tal caso.
Soluzione disponibile dopo 3 tentativi
Quando è idiomatico usare panic in una libreria Go?
func F(x int) {
// ??? panic(...)
}