ProgrammationDéveloppeur Go

Quelles sont les particularités de la gestion des erreurs (errors) en Go, comment créer et traiter correctement les erreurs, et comment implémenter ses propres types d'erreurs ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

Go a été conçu dès le départ autour du retour explicite des erreurs au lieu des exceptions. Cela permet de développer un code prévisible et d'éviter les pièges cachés courants des constructions try/catch d'autres langages (comme Java ou C++). Une erreur en Go est une interface qui implémente la méthode Error(), ce qui permet de créer des erreurs simples ainsi que des erreurs composées/emballées avec un contexte.

Les problèmes surviennent lors d'un traitement incorrect des erreurs (par exemple, si elles sont ignorées via _ ou non enveloppées d'informations supplémentaires pour le débogage) ou de la création d'erreurs "magiques" qui ne respectent pas l'interface error. Il est également important de savoir retourner des erreurs depuis des fonctions publiques et de les vérifier à chaque appel.

La solution est d'utiliser des approches standard :

  • Toujours retourner l'erreur comme dernière valeur de la fonction.
  • Utiliser errors.New, fmt.Errorf pour créer des erreurs.
  • Pour des erreurs personnalisées, créer ses propres types qui implémentent Error().
  • Pour des cas complexes, appliquer des erreurs d'emballage (errors.Wrap) afin de ne pas perdre le contexte.

Exemple de code :

import ( "errors" "fmt" ) type NotFoundError struct { Resource string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s introuvable", e.Resource) } func GetUser(id int) (string, error) { if id != 1 { return "", &NotFoundError{"Utilisateur"} } return "Steve", nil } func main() { user, err := GetUser(2) if err != nil { if nfe, ok := err.(*NotFoundError); ok { fmt.Println(nfe.Resource, "problème") } else { fmt.Println("erreur :", err) } } fmt.Println("utilisateur :", user) }

Caractéristiques clés :

  • Les erreurs sont toujours retournées explicitement, généralement comme dernier argument de la fonction.
  • Pour des erreurs complexes, des types (struct) peuvent être définis pour implémenter l'interface error.
  • Pour emballer des erreurs et conserver la trace de la pile, on peut utiliser le package "errors" (Go 1.13+ prend en charge errors.Is et errors.As pour travailler avec une chaîne d'erreurs).

Questions piégeuses.

Peut-on faire des comparaisons d'erreurs avec ==, ou faut-il utiliser des méthodes spéciales ?

Il est préférable d'utiliser toujours errors.Is() pour comparer avec des erreurs d'emballage, sinon une comparaison avec == peut ne pas fonctionner en cas d'enveloppement d'erreur.

if errors.Is(err, os.ErrNotExist) { // traitement de l'absence de fichier }

Est-il obligatoire d'implémenter un type structure pour une erreur personnalisée, ou est-il suffisant d'utiliser errors.New("...") ?

Si seul le texte de l'erreur est nécessaire, il suffit d'utiliser errors.New(). Si la conservation du contexte est importante (par exemple, le nom de la ressource), il est préférable de définir une structure avec la méthode Error().

Comment retourner correctement une erreur en cas de succès de la fonction ?

En cas de succès, retournez toujours une erreur nulle.

return result, nil

Erreurs typiques et anti-patrons

  • Ignorer les erreurs retournées (via _ ou omission du traitement)
  • Comparer les erreurs uniquement avec ==, malgré les erreurs imbriquées
  • Chaînes codées en dur au lieu des erreurs
  • Absence de contexte supplémentaire dans les erreurs

Exemple vécu

Cas négatif

Un développeur écrit une fonction retournant une erreur sans explication (juste errors.New("échec")). Lors de l'analyse des journaux, il devient impossible de déterminer la cause.

Avantages :

  • Rapidité de mise en œuvre

Inconvénients :

  • Erreurs peu informatives
  • Complexité du débogage

Cas positif

Un développeur définit un type d'erreur personnalisé NotFoundError et le retourne avec une description détaillée.

Avantages :

  • Commodité du filtrage des journaux
  • Facilité de recherche et de traitement des erreurs

Inconvénients :

  • Besoin de décrire des structures supplémentaires