Gestion des erreurs en Go : philosophie et pratique
Contrairement à de nombreux langages modernes, Go (Golang) n'utilise pas la construction classique try/catch. Au lieu de cela, les développeurs Go adhèrent à l'approche « les erreurs comme valeurs » (errors as values). Cela rend le code plus explicite, prévisible et sûr. Dans cet article, nous analyserons tous les aspects de la gestion des erreurs en Go : des modèles de base aux techniques avancées avec defer, panic et recover.
1. Les bases : les erreurs sont simplement des valeurs
En Go, une erreur est une interface error avec une seule méthode Error() string. Toute fonction qui peut échouer retourne une erreur comme dernière valeur de retour.
package main
import ( "errors" "fmt")
// Fonction de division avec retour d'erreurfunc divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division par zéro impossible") } return a / b, nil}
func main() { result, err := divide(10, 0) if err != nil { fmt.Println("Erreur :", err) return } fmt.Println("Résultat :", result)}Point clé : la vérification if err != nil est la manière standard de gérer les erreurs en Go. Il n'y a pas d'exceptions cachées qui pourraient « surgir » de manière inattendue.
2. Création et personnalisation des erreurs
Go propose plusieurs façons de créer des erreurs :
errors.New()— une erreur textuelle simple.fmt.Errorf()— une erreur avec formatage (prend en charge%wpour l'encapsulation).- Types personnalisés — pour transmettre un contexte supplémentaire.
Erreur personnalisée avec données supplémentaires
type DivisionError struct { Dividend float64 Divisor float64 Message string}
func (e *DivisionError) Error() string { return fmt.Sprintf("%s : %f / %f", e.Message, e.Dividend, e.Divisor)}
func safeDivide(a, b float64) (float64, error) { if b == 0 { return 0, &DivisionError{ Dividend: a, Divisor: b, Message: "division par zéro", } } return a / b, nil}
func main() { _, err := safeDivide(10, 0) if err != nil { // Assertion de type pour obtenir les champs supplémentaires var divErr *DivisionError if errors.As(err, &divErr) { fmt.Printf("Erreur avec %f sur %f : %s", divErr.Dividend, divErr.Divisor, divErr.Message) } }}L'utilisation de errors.As() permet de vérifier si une erreur est d'un certain type et d'en extraire les données. C'est l'équivalent de instanceof dans d'autres langages.
3. Modèles de gestion des erreurs : du simple au complexe
En Go, il existe plusieurs modèles établis pour travailler avec les erreurs :
3.1. Sentinel errors (erreurs-sentinelles)
Ce sont des erreurs prédéfinies qui peuvent être comparées avec ==. Exemple : io.EOF.
import ( "errors" "fmt")
var ErrNotFound = errors.New("élément non trouvé")
func findUser(id int) (string, error) { if id != 1 { return "", ErrNotFound } return "Alice", nil}
func main() { _, err := findUser(2) if errors.Is(err, ErrNotFound) { fmt.Println("Utilisateur non trouvé, création d'un nouveau...") }}3.2. Encapsulation des erreurs (wrapping)
Depuis Go 1.13, il est possible d'encapsuler les erreurs avec %w dans fmt.Errorf. Cela préserve la chaîne d'erreurs.
func readConfig(path string) error { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("lecture du fichier de config %s : %w", path, err) } // ... traitement de data