error
En Go, la gestion des erreurs ne se fait pas au moyen d'exceptions.
Les erreurs sont au contraire des valeurs ordinaires, dont le type implémente l'interface error intégrée au langage.
Cette interface error est très minimaliste.
Elle ne contient qu'une seule méthode, Error(), qui renvoie le message d'erreur sous forme de string.
type error interface {
Error() string
}
Dès que tu définis une fonction dans laquelle une erreur peut survenir pendant l'exécution et doit remonter jusqu'à l'appelant, tu dois inclure error parmi les types de retour.
Si la fonction a plusieurs valeurs de retour, par convention error est toujours la dernière.
func DoSomething() (int, error) {
// ...
}
Tu n'es pas obligé d'implémenter toi-même l'interface error.
Pour créer une erreur simple, tu peux utiliser la fonction errors.New(), qui fait partie du paquet errors de la bibliothèque standard.
La seule chose à lui passer est le message d'erreur sous forme de string, et errors.New() se charge de créer une valeur qui contient ton message et implémente l'interface error.
Si la fonction renvoie une erreur, il est de bonne pratique de renvoyer la valeur zéro de tous les autres paramètres de retour :
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
Tu ne dois pas partir du principe que toutes les fonctions renvoient des valeurs zéro pour les autres valeurs de retour lorsqu'une erreur est présente. La bonne pratique consiste à considérer qu'il n'est pas sûr d'utiliser les autres valeurs de retour dès qu'une erreur est renvoyée.
Par exemple, imagine une fonction qui essaie de lire un fichier et renvoie un slice d'octets à moitié rempli ainsi qu'une erreur. Si tu réutilises ce slice d'octets dans ton code en partant du principe qu'il est toujours vide puisqu'il y a eu une erreur, tu risques de tomber sur des bugs subtils.
Les seules exceptions sont les cas où la documentation de la fonction indique clairement que d'autres valeurs de retour sont significatives en cas d'erreur.
Regarde la [documentation de Write sur un buffer][buffer-write] pour un exemple.
Veille à ce que le message d'erreur que tu fournis soit aussi précis que possible, car par défaut les erreurs n'incluent aucune trace d'appels. Par convention, le message d'erreur commence par une lettre minuscule et ne se termine pas par un point.
Si tu veux utiliser une telle erreur à plusieurs endroits (ou la rendre disponible pour les utilisateurs de ton paquet), tu dois déclarer une variable pour cette erreur au lieu d'appeler errors.New directement.
Par convention, le nom de la variable commence par Err ou err (selon qu'elle est exportée ou non).
Ces variables d'erreur sont souvent appelées erreurs sentinelles.
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
Pour signaler qu'aucune erreur ne s'est produite pendant l'exécution de la fonction, renvoie nil comme erreur :
func Foo() (int, error) {
return 10, nil
}
Si tu dois construire un message d'erreur plus complexe à partir de certaines variables, tu peux recourir au formatage de strings.
Go dispose d'une fonction spéciale, fmt.Errorf, prévue pour cela.
Elle construit la string du message d'erreur et renvoie une erreur.
input := 123
action := "UPDATE"
err := fmt.Errorf("invalid input %d for action %s", input, action)
err.Error()
// => "invalid input 123 for action UPDATE"
Quand tu appelles une fonction qui renvoie une erreur, il est courant de stocker la valeur de cette erreur dans une variable nommée err.
Avant d'utiliser le résultat proprement dit de la fonction, tu dois vérifier qu'il n'y a pas eu d'erreur.
On peut utiliser == et != pour comparer l'erreur à nil, et on sait qu'il y a eu une erreur lorsque err n'est pas nil.
Pour éviter d'imbriquer le « chemin heureux » de ton code, il vaut mieux traiter les cas d'erreur en premier, généralement par un retour anticipé. Consulte ce célèbre article de Mat Ryer pour plus de détails.
// Do this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
// Not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
// handle err
} else {
// do something with file
}
}
// Also not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err == nil {
// do something with file
}
// handle err
}
La plupart du temps, l'erreur remonte la pile d'appels, comme dans le premier exemple ci-dessus. Une autre façon de gérer l'erreur consiste à la journaliser et à poursuivre avec une autre opération. La bonne pratique est soit de renvoyer l'erreur, soit de la journaliser, mais jamais les deux.
Comme la plupart des fonctions en Go incluent une erreur parmi leurs valeurs de retour, tu verras et utiliseras le motif if err != nil un peu partout dans le code Go.
Si tu veux que ton erreur contienne plus d'informations que le simple message d'erreur, tu peux créer un type d'erreur personnalisé.
Comme mentionné plus haut, tout ce qui implémente l'interface error (c'est-à-dire possède une méthode Error() string) peut faire office d'erreur en Go.
On utilise généralement une struct pour créer un type d'erreur personnalisé.
Par convention, le nom d'un type d'erreur personnalisé se termine par Error.
Il est également préférable de définir la méthode Error() string avec un récepteur pointeur ; consulte ce commentaire Stack Overflow pour comprendre le raisonnement.
Note que cela signifie que tu dois renvoyer un pointeur vers ton erreur personnalisée, sinon elle ne sera pas considérée comme une error, car la valeur non-pointeur ne fournit pas la méthode 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: "...",
}
}