Gestion des erreurs en Go : try/catch et au-delà : niveau avancé
Les développeurs qui viennent à Go depuis des langages avec exceptions (Java, Python, C#) se demandent souvent : « Où est le try/catch ? ». La réponse est simple : il n'y a pas d'exceptions au sens classique en Go. Au lieu de cela, le langage propose un modèle de gestion des erreurs explicite, prévisible et efficace. À première vue, cela peut sembler un pas en arrière, mais en réalité, c'est l'un des principes clés de la philosophie de Go : simplicité et fiabilité. Dans cet article, nous ne nous contenterons pas d'expliquer comment remplacer try/catch, mais nous aborderons des techniques avancées de gestion des erreurs : des types personnalisés à l'encapsulation et à la panique.
Pourquoi n'y a-t-il pas de try/catch en Go ?
L'absence de try/catch est une décision architecturale délibérée. Les exceptions entraînent souvent des flux d'exécution implicites difficiles à suivre. Le programme peut « exploser » à tout moment si vous oubliez de gérer une exception. Go propose une autre approche : l'erreur est simplement une valeur (error is a value).
Cela signifie qu'une fonction qui peut échouer renvoie une valeur d'erreur dans le cadre de son résultat. Le programmeur doit vérifier cette valeur. Cette approche rend le flux d'erreurs visible dans le code et évite les situations où une exception est « avalée » ou traitée trop loin de son point d'origine.
// Idiome typique de Gofunc ReadFile(path string) ([]byte, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("impossible de lire le fichier %s : %w", path, err) } return data, nil}
// Utilisationdata, err := ReadFile("config.json")if err != nil { log.Fatal(err) // ou return err}C'est précisément le remplacement de try/catch. Au lieu du bloc try { ... } catch (Exception e) { ... }, nous utilisons if err != nil. C'est tout aussi expressif, mais beaucoup plus explicite.
Techniques avancées : erreurs personnalisées et encapsulation
1. Types d'erreurs personnalisés
Le type standard error est une interface avec une seule méthode Error() string. Mais pour une gestion avancée, vous avez souvent besoin de plus : un code d'erreur, un caractère temporaire de la panne, des données supplémentaires. Créez votre propre type implémentant l'interface error.
type MyError struct { Code int Message string IsTemp bool}
func (e *MyError) Error() string { return fmt.Sprintf("code %d : %s (temporaire : %t)", e.Code, e.Message, e.IsTemp)}
// Utilisationfunc DoSomething() error { return &MyError{Code: 503, Message: "service indisponible", IsTemp: true}}
// Vérificationerr := DoSomething()var myErr *MyErrorif errors.As(err, &myErr) { if myErr.IsTemp { // Réessayer }}Conseil : Utilisez errors.As pour une conversion sécurisée vers votre type, plutôt qu'une assertion de type directe (type assertion), afin d'éviter une panique.
2. Encapsulation des erreurs (Wrapping)
L'une des fonctionnalités les plus puissantes de Go 1.13+ est la possibilité d'encapsuler les erreurs avec fmt.Errorf et la directive %w. Cela crée une chaîne d'erreurs qui peut être déroulée à l'aide de errors.Is et errors.As.
func OpenConfig() error { _, err := os.Open("config.yaml") if err != nil { // Encapsule l'erreur en ajoutant du contexte return fmt.Errorf("ouverture du config : %w", err) } return nil}
func LoadApp() error { err := OpenConfig() if err != nil { // Encapsule à nouveau return fmt.Errorf("chargement de l'application : %w", err) } return nil}
// Quelque part dans mainerr := LoadApp()if errors.Is(err, os.ErrNotExist) { fmt.Println("Fichier de configuration introuvable")}Notez que errors.Is parcourt toute la chaîne et trouve