Storia della questione:
Fino a Go 1.13, gli errori erano semplici interfacce. Per ulteriori dettagli, si creavano spesso tipi di errori personalizzati, ma non c'era un meccanismo strutturato di incapsulamento, come in Java moderno o C#. Con l'introduzione di Go 1.13 è stato introdotto un modo standard di avvolgere (wrapping) gli errori tramite la funzione fmt.Errorf e di individuare la causa radice (errors.Is/errors.As).
Problema:
In applicazioni complesse, dove gli errori possono sorgere a livelli diversi dello stack, è importante non perdere il contesto quando si restituisce un errore dal profondo del codice. Altrimenti, il debug diventa difficile e non è possibile capire dove nella catena sia avvenuto il fallimento.
Soluzione:
Go offre la possibilità di avvolgere gli errori in nuovi oggetti di errore che contengono la causa. A tal fine, si utilizza %w in fmt.Errorf, mentre per controllare le cause profonde si usano errors.Is o errors.As dal package errors.
Esempio di codice:
import ( "errors" "fmt" ) var ErrNotFound = errors.New("non trovato") func getData() error { return fmt.Errorf("errore nel database del servizio: %w", ErrNotFound) } func main() { err := getData() if errors.Is(err, ErrNotFound) { fmt.Println("Individuata la causa: non trovato") } else { fmt.Println("Altro errore:", err) } }
Caratteristiche chiave:
%w per incapsulare errori tramite fmt.Errorf.errors.Is e errors.As.Quale operatore di formattazione è necessario per l'incapsulamento degli errori tramite fmt.Errorf?
È corretto utilizzare %w, e non %v — solo %w supporta il disimballaggio.
Esempio di codice:
fmt.Errorf("errore: %w", err)
È possibile creare manualmente catene di errori senza fmt.Errorf e comunque identificarle tramite errors.Is?
No, è necessario implementare l'interfaccia Unwrap, altrimenti le funzioni standard non disimballeranno la catena.
Esempio di codice dell'interfaccia:
type wrappedError struct { msg string err error } func (w wrappedError) Error() string { return w.msg + ": " + w.err.Error() } func (w wrappedError) Unwrap() error { return w.err }
Restituiscono gli errori valore nil dopo il wrapping, se l'errore incapsulato era nil?
No, se l'errore incapsulato è nil, allora l'errore avvolto è nil solo se utilizzato correttamente. Ma se si crea manualmente una struttura wrapper dove il campo errore è nil, non sarà nil. Questo porta spesso a confusione durante il controllo.
%w, ma solo %v — si perde la possibilità di analizzare la catena.Unwrap() per le proprie strutture di errore.errors.Is/As, ma solo confrontare l'errore direttamente.Nell'applicazione veniva semplicemente restituito un nuovo errore con testo a ogni livello:
return errors.New("errore di scrittura nel DB")
Vantaggi:
Svantaggi:
È stato utilizzato il wrapping degli errori con %w e l'analisi tramite errors.Is:
if errors.Is(err, ErrNotFound) { return fmt.Errorf("errore livello servizio: %w", err) }
Vantaggi:
Svantaggi: