Tre tipi di errori
Il codice Go usa tre forme di errore, dalla più semplice alla più ricca:
- Un errore ad hoc:
errors.New("...")ofmt.Errorf("...")creato sul momento. Chi lo riceve può solo leggerne il messaggio. - Un errore sentinella: una variabile a livello di package come
io.EOF. Chi lo riceve può verificarlo conerrors.Is. - Un tipo di errore: una struct con un metodo
Error()che trasporta dei campi. Chi lo riceve può estrarlo conerrors.Ase leggerne i campi.
Scegli la forma più semplice che permette ai chiamanti di fare ciò che serve loro.
Errori sentinella
Una sentinella è un valore di errore dichiarato una volta e confrontato per identità. Per convenzione i nomi iniziano con Err.
Output:
mug bought
cap is out of stock, notify me later
buy "hat": not found
Due chiamate a errors.New con lo stesso testo producono errori diversi: errors.New("x") == errors.New("x") vale false. Per questo una sentinella deve essere un'unica variabile condivisa.
Le sentinelle diventano parte dell'API del tuo package. Una volta che i chiamanti controllano ErrNotFound, non puoi smettere di restituirlo senza rompere il loro codice. Esporta solo quelle su cui i chiamanti hanno davvero bisogno di prendere decisioni.
Tipi di errore personalizzati
Quando il chiamante ha bisogno di dettagli (quale campo, quale codice di stato, quale attesa prima di riprovare), definisci un tipo.
Output:
register: age: must be between 0 and 150
bad field: age
Il metodo ha un receiver puntatore e la funzione restituisce &ValidationError{...}, quindi la destinazione per errors.As è un *ValidationError e ne passi l'indirizzo (&ve, un **ValidationError). Sbagliare questo livello è l'errore classico con errors.As: con un receiver per valore restituiresti ValidationError{...} e dichiareresti var ve ValidationError. go vet rileva la svista più comune, passare ve invece di &ve: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type.
Wrapping con %w
fmt.Errorf con %w restituisce un errore che ricorda quello che avvolge. Ogni livello aggiunge contesto e mantiene intatta la catena.
Output:
start: connect db: timeout
*fmt.wrapError: start: connect db: timeout
*fmt.wrapError: connect db: timeout
*errors.errorString: timeout
true
false
Con %v il messaggio è lo stesso ma la catena viene interrotta, quindi errors.Is restituisce false. Scegli %w quando i chiamanti devono poter vedere la causa, %v quando la causa è un dettaglio di implementazione da cui non vuoi che dipendano.
Da Go 1.20, una chiamata può avvolgere più errori: fmt.Errorf("%w; %w", err1, err2). errors.Is trova allora una corrispondenza con l'uno o con l'altro.
errors.Join: più errori insieme
La validazione e la pulizia producono spesso più di un errore. errors.Join (Go 1.20) li raggruppa.
Output:
name: required
email: required
age: negative
---
true
true
errors.Join restituisce nil quando tutti gli argomenti sono nil, quindi la funzione qui sopra non ha bisogno di un caso speciale per "nessun errore".
I metodi Unwrap e Is
errors.Is ed errors.As trovano gli errori avvolti chiamando un metodo Unwrap. Un tipo personalizzato che contiene una causa dovrebbe esporla:
Un tipo che avvolge più errori implementa invece Unwrap() []error.
Un tipo può anche definire Is(target error) bool per decidere da sé l'uguaglianza, per esempio per considerare uguale qualsiasi *HTTPError con lo stesso codice di stato. Ti servirà di rado; avvolgere una sentinella e restituirla da Unwrap di solito basta.
La trappola del nil tipizzato
Non dichiarare mai una variabile di errore con il tuo tipo concreto per poi restituirla tramite error:
func check() error {
var err *ValidationError // nil pointer
// ... no problem found
return err // non-nil error! Its type is *ValidationError
}
L'if err != nil del chiamante risulta vero, perché un'interfaccia che contiene un puntatore nil tipizzato non è nil. Restituisci un nil letterale in caso di successo e dichiara le variabili di errore locali con il tipo error. La pagina sulle interfacce spiega il perché.
Quale tipo scegliere
| I chiamanti devono | Fornisci |
|---|---|
| solo registrare o mostrare il fallimento | fmt.Errorf("...: %w", err) |
| decidere in base a una condizione specifica | una sentinella var ErrX = errors.New(...) |
| leggere dettagli sul fallimento | un tipo di errore con campi |
| vedere più fallimenti indipendenti | errors.Join |
Errori comuni
- Confrontare errori avvolti con
==. Usaerrors.Is. - Passare un valore che non è un puntatore a
errors.As. Serve un puntatore a una variabile del tipo di destinazione. - Creare una "sentinella" dentro una funzione.
return errors.New("not found")crea un nuovo valore a ogni chiamata; i chiamanti non possono confrontarlo. - Esportare ogni errore. Ogni sentinella o tipo esportato è una promessa dell'API.
- Confrontare
err.Error(). Le stringhe sono per le persone.
Domande frequenti
Come creo un errore personalizzato in Go?
Per una condizione fissa, dichiara una sentinella a livello di package: var ErrNotFound = errors.New("not found"). Per un errore che trasporta dati, definisci un tipo con un metodo Error() string: type ValidationError struct { Field string } e func (e *ValidationError) Error() string { return e.Field + " is invalid" }.
Come si avvolge un errore in Go?
Usa fmt.Errorf con il verbo %w: return fmt.Errorf("load user %d: %w", id, err). Il messaggio del nuovo errore include quello vecchio, e errors.Unwrap, errors.Is ed errors.As possono raggiungere l'originale. Da Go 1.20 una sola chiamata a Errorf può avvolgere più errori con più verbi %w.
Qual è la differenza tra errors.Is ed errors.As?
errors.Is(err, target) risponde alla domanda "questo preciso valore di errore si trova da qualche parte nella catena?", per sentinelle come io.EOF. errors.As(err, &target) risponde a "c'è un errore di questo tipo nella catena?" e, se c'è, lo memorizza in target così puoi leggerne i campi.
Cosa fa errors.Join?
errors.Join(errs...) (Go 1.20) combina più errori in uno solo. Il suo messaggio è formato dai singoli messaggi separati da a capo, gli errori nil vengono scartati e restituisce nil se sono tutti nil. errors.Is ed errors.As trovano una corrispondenza se uno qualsiasi degli errori uniti corrisponde.