Gestion des Erreurs en Go : Patterns, Wrapping et Bonnes Pratiques en 2026
Guide complet sur la gestion des erreurs en Go : types personnalisés, sentinel errors, error wrapping avec errors.Is et errors.As, anti-patterns à éviter et traitement concurrent avec errgroup.

La gestion des erreurs constitue l'un des aspects les plus distinctifs du langage Go. Contrairement aux exceptions utilisées dans de nombreux langages, Go adopte une approche explicite où chaque erreur potentielle doit être traitée au moment où elle survient. Cette philosophie, parfois critiquée pour sa verbosité, garantit un code robuste et prévisible lorsqu'elle est correctement appliquée.
En 2026, les patterns de gestion d'erreurs en Go ont considérablement mûri. L'introduction de errors.Is et errors.As dans Go 1.13, combinée aux améliorations successives du runtime, offre désormais un arsenal complet pour gérer les erreurs de manière élégante et maintenable. Cet article explore les patterns essentiels que tout développeur Go doit maîtriser.
La gestion des erreurs représente un sujet fréquemment abordé lors des entretiens Go. Les recruteurs évaluent non seulement la connaissance des APIs standards, mais aussi la capacité à concevoir des stratégies d'erreurs cohérentes à l'échelle d'une application.
L'interface error : fondement du système
L'interface error en Go se distingue par sa simplicité remarquable. Elle ne définit qu'une seule méthode :
type error interface {
Error() string
}Cette simplicité constitue à la fois une force et une responsabilité. N'importe quel type implémentant la méthode Error() string satisfait l'interface. Cette flexibilité permet de créer des types d'erreurs riches en informations contextuelles, tout en maintenant une compatibilité universelle avec l'écosystème Go.
Le pattern idiomatique de retour d'erreur place systématiquement l'erreur en dernière position des valeurs de retour. Cette convention, profondément ancrée dans la culture Go, facilite la lecture du code et permet d'identifier immédiatement les fonctions susceptibles d'échouer.
Types d'erreurs personnalisés
Les types d'erreurs personnalisés permettent d'encapsuler des informations structurées sur la nature de l'erreur. Ils s'avèrent particulièrement utiles lorsque le contexte de l'erreur influence la logique de traitement :
type NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with ID %s not found", e.Resource, e.ID)
}Ce pattern offre plusieurs avantages significatifs. Le code appelant peut extraire les informations structurées (Resource, ID) pour personnaliser sa réponse. Les tests unitaires peuvent vérifier précisément le type et le contenu des erreurs. La documentation du code s'enrichit naturellement des champs exposés par le type.
La convention recommande d'utiliser des pointeurs de structure comme receveurs pour les méthodes Error(). Cette approche évite les allocations inutiles et permet la comparaison par identité lorsque nécessaire.
Sentinel errors : constantes d'erreur
Les sentinel errors représentent des valeurs d'erreur prédéfinies au niveau du package. Elles servent de points de référence pour identifier des conditions d'erreur spécifiques :
var (
ErrNotFound = errors.New("record not found")
ErrUnauthorized = errors.New("unauthorized access")
ErrConflict = errors.New("resource conflict")
)La convention de nommage Err suivie d'un nom descriptif facilite l'identification de ces valeurs dans le code. Les sentinel errors conviennent particulièrement aux cas où l'existence même de l'erreur suffit, sans nécessiter d'informations contextuelles supplémentaires.
L'utilisation judicieuse des sentinel errors améliore la lisibilité du code en établissant un vocabulaire commun au sein d'un package ou d'une application. Les consommateurs du package peuvent ainsi s'appuyer sur ces valeurs exportées pour structurer leur logique de gestion d'erreurs.
Error wrapping : préserver le contexte
L'error wrapping permet d'enrichir une erreur existante avec du contexte additionnel tout en préservant l'erreur originale. Le verbe %w dans fmt.Errorf crée cette relation de wrapping :
func (r *UserRepo) FindByID(ctx context.Context, id string) (*User, error) {
user, err := r.db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", id)
if err != nil {
return nil, fmt.Errorf("UserRepo.FindByID(%s): %w", id, err)
}
return user, nil
}Ce pattern construit une chaîne d'erreurs qui documente le chemin d'exécution ayant mené à l'échec. Chaque couche applicative ajoute son contexte spécifique, créant ainsi une trace exploitable pour le débogage.
La clé réside dans l'équilibre entre information et bruit. Le contexte ajouté doit apporter une valeur diagnostique sans dupliquer les informations déjà présentes dans l'erreur sous-jacente. L'inclusion du nom de la fonction et des paramètres clés constitue généralement une bonne pratique.
Prêt à réussir tes entretiens Go ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
errors.Is : vérification d'identité d'erreur
La fonction errors.Is parcourt la chaîne d'erreurs wrappées pour déterminer si une erreur spécifique s'y trouve. Elle remplace avantageusement les comparaisons directes qui échouaient avec les erreurs wrappées :
func handleGetUser(w http.ResponseWriter, r *http.Request) {
user, err := userService.GetByID(r.Context(), chi.URLParam(r, "id"))
if errors.Is(err, ErrNotFound) {
http.Error(w, "User not found", http.StatusNotFound)
return
}
if err != nil {
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}L'ordre des vérifications importe. Les erreurs les plus spécifiques doivent être testées en premier, suivies des cas généraux. La vérification err != nil en dernier attrape toutes les erreurs non identifiées.
La fonction errors.Is fonctionne correctement avec les sentinel errors et tout type implémentant la méthode Is(error) bool pour une logique de comparaison personnalisée.
errors.As : extraction de type d'erreur
Lorsque le traitement nécessite d'accéder aux champs d'un type d'erreur spécifique, errors.As permet d'extraire et de convertir l'erreur vers ce type :
func errorMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
http.Error(w, "Internal error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func mapErrorToHTTP(err error) int {
var notFound *NotFoundError
if errors.As(err, ¬Found) {
return http.StatusNotFound
}
var validationErr *ValidationError
if errors.As(err, &validationErr) {
return http.StatusBadRequest
}
return http.StatusInternalServerError
}La variable cible doit être un pointeur vers le type recherché. En cas de succès, errors.As assigne l'erreur trouvée à cette variable, permettant ensuite d'accéder à ses champs et méthodes spécifiques.
Ce mécanisme s'avère particulièrement puissant pour centraliser la logique de mapping entre erreurs domaine et réponses HTTP dans un middleware ou une fonction utilitaire.
Erreurs domaine structurées
Les applications d'envergure bénéficient d'un type d'erreur domaine standardisé. Ce type encapsule un code d'erreur, un message lisible et optionnellement l'erreur sous-jacente :
type DomainError struct {
Code string
Message string
Err error
}
func (e *DomainError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}
func (e *DomainError) Unwrap() error {
return e.Err
}La méthode Unwrap permet à errors.Is et errors.As de traverser la chaîne d'erreurs correctement. Sans cette méthode, l'erreur encapsulée dans le champ Err resterait inaccessible aux fonctions d'inspection.
Les codes d'erreur (ALREADY_INACTIVE, VALIDATION_FAILED, etc.) facilitent l'internationalisation et le logging structuré. Ils permettent également aux clients API de réagir programmatiquement aux différentes conditions d'erreur.
Couche service et propagation d'erreurs
La couche service orchestre la logique métier et constitue un point stratégique pour la création et la propagation d'erreurs :
func (s *UserService) Deactivate(ctx context.Context, userID string) error {
user, err := s.repo.FindByID(ctx, userID)
if err != nil {
return fmt.Errorf("deactivating user %s: %w", userID, err)
}
if user.Status == StatusInactive {
return &DomainError{
Code: "ALREADY_INACTIVE",
Message: fmt.Sprintf("user %s is already inactive", userID),
}
}
return s.repo.UpdateStatus(ctx, userID, StatusInactive)
}Cette approche distingue clairement les erreurs techniques (provenant du repository) des erreurs métier (violation de règles domaine). Les premières sont wrappées pour préserver le contexte, tandis que les secondes sont créées explicitement avec les informations pertinentes.
Questions fréquentes en entretien technique
Les entretiens techniques Go abordent régulièrement ces aspects de la gestion d'erreurs :
Quelle différence entre errors.Is et errors.As ?
errors.Is vérifie si une erreur spécifique existe dans la chaîne (comparaison d'identité), tandis que errors.As tente d'extraire une erreur d'un type donné (conversion de type). Le premier répond à "cette erreur est-elle présente ?", le second à "puis-je accéder aux détails de ce type d'erreur ?".
Quand utiliser un type personnalisé versus un sentinel error ? Les types personnalisés conviennent lorsque l'erreur transporte des données structurées nécessaires au traitement. Les sentinel errors suffisent quand seule l'existence de l'erreur importe, sans contexte additionnel.
Comment gérer les erreurs dans les goroutines ?
Les channels ou le package errgroup permettent de collecter et propager les erreurs des goroutines vers la goroutine principale. L'errgroup offre une API plus ergonomique avec annulation automatique du contexte.
Anti-patterns à éviter
Certaines pratiques nuisent à la maintenabilité et à la fiabilité du code. L'ignoration silencieuse des erreurs constitue le piège le plus courant :
// anti-pattern: silent error swallowing
result, _ := riskyOperation()Cette pratique masque des échecs potentiellement critiques. Si l'erreur ne peut être traitée localement, elle doit être propagée au niveau supérieur.
Le double traitement représente un autre anti-pattern fréquent :
// anti-pattern: double handling
if err != nil {
log.Printf("operation failed: %v", err)
return err
}Logger puis retourner l'erreur provoque des logs dupliqués lorsque chaque niveau de la pile d'appels adopte cette approche. La règle recommande de traiter l'erreur (logger, convertir, etc.) ou de la propager, mais pas les deux simultanément.
Traitement concurrent avec errgroup
Le package golang.org/x/sync/errgroup simplifie la gestion des erreurs dans les traitements concurrents. Il coordonne plusieurs goroutines et collecte leurs erreurs :
func (s *OrderService) ProcessBatch(ctx context.Context, orderIDs []string) error {
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10)
for _, id := range orderIDs {
g.Go(func() error {
if err := s.processOrder(ctx, id); err != nil {
return fmt.Errorf("processing order %s: %w", id, err)
}
return nil
})
}
return g.Wait()
}L'errgroup annule automatiquement le contexte dès qu'une goroutine retourne une erreur, permettant aux autres goroutines de s'interrompre proprement. La méthode SetLimit contrôle le parallélisme pour éviter la saturation des ressources.
La méthode Wait bloque jusqu'à la terminaison de toutes les goroutines et retourne la première erreur rencontrée. Pour collecter toutes les erreurs, des patterns alternatifs utilisant des channels ou le type errors.Join de Go 1.20+ s'avèrent nécessaires.
Go 1.24 introduit des améliorations au niveau du runtime pour le traitement des erreurs, notamment une meilleure intégration avec les outils de tracing distribué. Les stack traces attachées aux erreurs wrappées bénéficient également d'optimisations de performance significatives.
Conclusion
La gestion des erreurs en Go repose sur des principes simples mais exigeants. Une application robuste nécessite une stratégie cohérente appliquée à travers toutes les couches du code.
Les points essentiels à retenir :
- L'interface
erroroffre une flexibilité maximale grâce à sa simplicité - Les types personnalisés encapsulent le contexte structuré nécessaire au traitement
- Les sentinel errors établissent un vocabulaire commun pour les conditions d'erreur standard
- L'error wrapping avec
%wpréserve la chaîne causale pour le débogage errors.Iseterrors.Aspermettent l'inspection robuste des erreurs wrappées- L'errgroup simplifie la collecte d'erreurs dans les traitements concurrents
- Éviter l'ignoration silencieuse et le double traitement des erreurs
La maîtrise de ces patterns distingue le code Go professionnel du code amateur. En entretien comme en production, une gestion d'erreurs rigoureuse témoigne d'une compréhension approfondie du langage et de ses idiomes.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Design patterns Go : patterns essentiels et questions d'entretien pour développeurs Go
Maîtrisez les design patterns Go : Functional Options, Strategy, Factory et Observer. Exemples de code concrets, bonnes pratiques idiomatiques et questions d'entretien fréquentes pour développeurs Go.

Top 25 questions d'entretien Go : Guide complet pour développeurs
Préparez vos entretiens Go avec les 25 questions les plus posées. Goroutines, channels, interfaces, concurrence et patterns avancés avec exemples de code.

Go et gRPC en 2026 : Microservices Haute Performance et Questions d'Entretien
Guide complet sur gRPC avec Go en 2026 : Protocol Buffers, RPC unaire et streaming, intercepteurs, patterns de production, mTLS et questions d'entretien pour les ingénieurs backend.