Contexte du problème :
Avant Go 1.13, l'erreur était une simple interface. Pour un contexte supplémentaire, il était courant de créer des types d'erreur spécifiques, mais il n'y avait pas de mécanisme structuré d'encapsulation, comme dans la Java ou le C# modernes. Avec l'arrivée de Go 1.13, un moyen standard d'encapsulation (wrapping) des erreurs a été introduit via les fonctions fmt.Errorf et pour identifier la cause profonde (errors.Is/errors.As).
Problème :
Dans des applications complexes, où des erreurs peuvent survenir à différents niveaux de la pile, il est essentiel de ne pas perdre le contexte lors du retour d'erreurs de la profondeur du code. Sinon, le débogage devient difficile et il est impossible de comprendre où dans la chaîne l'échec s'est produit.
Solution :
Go propose d'encapsuler les erreurs dans de nouveaux objets d'erreur qui contiennent la cause. Pour cela, %w est utilisé dans fmt.Errorf, et pour vérifier les causes profondes — errors.Is ou errors.As du package errors.
Exemple de code :
import ( "errors" "fmt" ) var ErrNotFound = errors.New("not found") func getData() error { return fmt.Errorf("service database: %w", ErrNotFound) } func main() { err := getData() if errors.Is(err, ErrNotFound) { fmt.Println("Cause trouvée : non trouvé") } else { fmt.Println("Autre erreur :", err) } }
Caractéristiques clés :
%w pour l'encapsulation d'erreurs via fmt.Errorf.errors.Is et errors.As.Quel opérateur de formatage est nécessaire pour encapsuler des erreurs via fmt.Errorf ?
Il faut utiliser %w, et non %v — seul %w permet de prendre en charge le déballage.
Exemple de code :
fmt.Errorf("erreur : %w", err)
Peut-on créer manuellement des chaînes d'erreurs sans fmt.Errorf et tout de même les identifier via errors.Is ?
Non, il faut implémenter l'interface Unwrap, sinon les fonctions standard ne déballeront pas la chaîne.
Exemple de code de l'interface :
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 }
Les erreurs retournent-elles des valeurs nil après un wrapping, si l'erreur imbriquée était nil ?
Non, si l'erreur imbriquée est nil, alors l'erreur encapsulée est également nil seulement si elle est correctement utilisée. Mais si l'on crée manuellement une structure wrapper où le champ d'erreur est nil, elle ne sera pas nil. Cela conduit souvent à de la confusion lors de la vérification.
%w, mais seulement %v — cela entraîne la perte de la chaîne d'analyse.Unwrap() pour vos structures d'erreur.errors.Is/As, mais simplement comparer directement l'erreur.Dans l'application, une nouvelle erreur était simplement retournée avec un message à chaque niveau :
return errors.New("erreur d'écriture dans la BDD")
Avantages :
Inconvénients :
Utilisation de l'encapsulation des erreurs avec %w et analyse via errors.Is :
if errors.Is(err, ErrNotFound) { return fmt.Errorf("erreur de niveau service : %w", err) }
Avantages :
Inconvénients :