La gestione degli errori non avviene tramite eccezioni in Go.
Invece, gli errori sono normali valori di tipi che implementano l'interfaccia error predefinita.
L'interfaccia error è molto minimale.
Contiene un solo metodo Error() che restituisce il messaggio di errore come stringa.
type error interface {
Error() string
}
Ogni volta che definisci una funzione in cui potrebbe verificarsi un errore durante l'esecuzione e che deve raggiungere il chiamante, devi includere error tra i tipi restituiti.
Se la funzione ha più valori restituiti, per convenzione error è sempre l'ultimo.
func DoSomething() (int, error) {
// ...
}
Non devi sempre implementare tu stesso l'interfaccia error.
Per creare un errore semplice, puoi usare la funzione errors.New(), che fa parte del pacchetto errors della libreria standard.
L'unica cosa che devi passare è il messaggio di errore come stringa, e errors.New() si occuperà di creare un valore che contiene il messaggio e implementa l'interfaccia error.
Se la funzione restituisce un errore, è buona pratica restituire il valore zero per tutti gli altri valori restituiti:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
Non dovresti dare per scontato che tutte le funzioni restituiscano valori zero per gli altri valori restituiti quando è presente un errore. È buona pratica presumere che non sia sicuro usare nessuno degli altri valori restituiti se si è verificato un errore. Le uniche eccezioni sono i casi in cui la documentazione afferma chiaramente che gli altri valori restituiti sono significativi in caso di errore.
Se vuoi usare un errore semplice in più punti, dovresti dichiarare una variabile per l'errore invece di usare errors.New in linea.
Per convenzione, il nome della variabile dovrebbe iniziare con Err o err (a seconda che sia esportata o meno).
Queste variabili di errore sono spesso chiamate errori sentinella.
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
Restituisci nil per l'errore per segnalare che non ci sono stati errori durante l'esecuzione della funzione:
func Foo() (int, error) {
return 10, nil
}
Se chiami una funzione che restituisce un errore, è comune memorizzare il valore dell'errore in una variabile chiamata err.
Prima di usare il risultato effettivo della funzione, devi controllare che non ci sia stato un errore.
Per evitare di annidare il «percorso felice» del codice, i casi di errore dovrebbero essere gestiti per primi.
Possiamo usare == e != per confrontare l'errore con nil e sappiamo che c'è stato un errore quando err non è nil.
func processUserFile() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
Il più delle volte, l'errore verrà restituito risalendo lo stack delle funzioni, come mostrato nell'esempio sopra. Un altro modo per gestire l'errore potrebbe essere registrarlo nei log e continuare con un'altra operazione. È buona pratica restituire o registrare l'errore, mai entrambe le cose.
Poiché la maggior parte delle funzioni in Go include un errore tra i valori restituiti, vedrai e userai il pattern if err != nil dappertutto nel codice Go.
Se vuoi che un errore includa più informazioni della sola stringa del messaggio di errore, puoi creare un tipo di errore personalizzato.
Come accennato prima, tutto ciò che implementa l'interfaccia error (ovvero ha un metodo Error() string) può fungere da errore in Go.
Di solito, si usa una struct per creare un tipo di errore personalizzato.
Per convenzione, i nomi dei tipi di errore personalizzati dovrebbero terminare con Error.
Inoltre, è meglio definire il metodo Error() string con un ricevitore puntatore; guarda questo commento su Stack Overflow per capire il motivo.
Nota che questo significa che devi restituire un puntatore al tipo di errore personalizzato, altrimenti non conterà come error, perché il valore non puntatore non fornisce il metodo Error() string.
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}
Il giorno che hai aspettato tanto è finalmente arrivato: ora sei il fiero proprietario di una bellissima fattoria sulle Alpi.
Continui a non sopportare di svegliarti molto presto la mattina per dare da mangiare alle mucche.
Dato che sei un ottimo ingegnere, costruisci un distributore di mangime, il FEED-M-ALL.
L'ultima cosa che serve per completare il progetto è un pezzo di codice che calcoli la quantità di foraggio che ogni mucca dovrebbe ricevere. È importante che ogni mucca ne riceva la stessa quantità: bisogna evitare i conflitti. Le mucche sono molto sensibili.
Per fortuna, non devi ricavare da solo tutte le formule per calcolare le quantità di foraggio. Usi una misteriosa libreria esterna che hai trovato su internet. Dovrebbe dare come risultato le mucche più felici. La libreria espone un tipo che soddisfa la seguente interfaccia. Ti affiderai a questa nel codice che scriverai.
type FodderCalculator interface {
FodderAmount(int) (float64, error)
FatteningFactor() (float64, error)
}
Man mano che lavori al codice, migliorerai la gestione degli errori per renderla più robusta e più facile da analizzare in seguito, quando la userai nella tua vita quotidiana in fattoria.
Per prima cosa, ti concentri sullo scrivere il codice necessario a calcolare la quantità di foraggio per ogni mucca.
Implementa una funzione DivideFood che accetta come argomenti un FodderCalculator e un numero di mucche come intero.
Per questo compito, puoi assumere che il numero di mucche passato sia sempre maggiore di zero.
La funzione dovrebbe restituire la quantità di cibo per mucca come float64, oppure un errore se se ne verifica uno.
Per fare il calcolo, devi prima recuperare la quantità totale di foraggio per tutte le mucche.
Lo si fa chiamando il metodo FodderAmount e passando il numero di mucche.
Inoltre, ti serve un fattore per cui moltiplicare questa quantità.
Ottieni questo fattore chiamando il metodo FatteningFactor.
Con questi due valori e il numero di mucche, puoi ora calcolare la quantità di cibo per mucca (come float64).
È questo che la funzione DivideFood dovrebbe restituire.
Se uno dei metodi che chiami restituisce un errore, l'esecuzione dovrebbe fermarsi e quell'errore dovrebbe essere restituito (così com'è) dalla funzione DivideFood.
// For this example, we assume FodderAmount returns 50
// and FatteningFactor returns 1.5.
DivideFood(fodderCalculator, 5)
// => 15 <nil>
// Now assuming FodderAmount returns an error with message "something went wrong".
DivideFood(fodderCalculator, 5)
// => 0 "something went wrong"
Mentre lavoravi al primo compito, ti sei reso conto che la libreria esterna che usi non è di così alta qualità come pensavi. Per esempio, non riesce a gestire correttamente input non validi. Vuoi aggirare questo limite aggiungendo un controllo sul valore di input direttamente nel codice.
Scrivi una funzione ValidateInputAndDivideFood che ha la stessa firma di DivideFood qui sopra.
DivideFood e restituire i risultati di quella chiamata."invalid number of cows".ValidateInputAndDivideFood(fodderCalculator, 5)
// => 15 <nil>
ValidateInputAndDivideFood(fodderCalculator, -2)
// => 0 "invalid number of cows"
Controllare il numero di mucche prima di passarlo era una buona mossa, ma non sei del tutto soddisfatto del messaggio di errore poco specifico.
Decidi di fare meglio creando un tipo di errore personalizzato chiamato InvalidCowsError.
L'errore personalizzato dovrebbe contenere il numero di mucche (int) e un messaggio personalizzato (string), e il metodo Error dovrebbe serializzare i dati nel seguente formato:
{number of cows} cows are invalid: {custom message}
Ora che hai il tuo errore personalizzato, implementa una funzione ValidateNumberOfCows che accetta il numero di mucche come intero e restituisce un errore (oppure nil).
InvalidCowsError con il messaggio personalizzato impostato su "there are no negative cows".InvalidCowsError con il messaggio personalizzato impostato su "no cows don't need food".nil per indicare che la validazione è andata a buon fine.err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"
Dopo il duro lavoro per impostare questa funzione di validazione, ti accorgi che è già sera e lasci la scrivania per goderti il tramonto sulle montagne. Rimandi a un altro giorno il compito di aggiungere davvero la nuova funzione di validazione al codice.
Iscriviti a Exercism per imparare e padroneggiare Go con 34 concetti166 esercizi e il mentoring di persone reali, tutto gratis.