Contactez-nous

Créer des erreurs simples (`errors.New`, `fmt.Errorf`)

Apprenez à générer des erreurs personnalisées en Go de manière simple et idiomatique en utilisant `errors.New` pour les messages statiques et `fmt.Errorf` pour les erreurs formatées.

Signaler un problème : Pourquoi créer ses propres erreurs ?

Nous avons vu comment vérifier les erreurs retournées par les fonctions en utilisant le pattern `if err != nil`. Mais que se passe-t-il lorsque c'est *notre propre fonction* qui rencontre un problème et doit le signaler à l'appelant ? Nous devons créer une valeur qui satisfait l'interface `error`.

Bien qu'il soit possible de définir des types structurés complexes pour représenter des erreurs détaillées (avec des codes d'erreur, des contextes, etc.), Go fournit deux fonctions très pratiques dans sa bibliothèque standard pour créer rapidement et facilement des valeurs d'erreur simples pour les cas les plus courants : `errors.New` et `fmt.Errorf`.

Ces fonctions permettent de générer des erreurs contenant un message descriptif, sans nécessiter la définition explicite d'un nouveau type à chaque fois. Elles sont largement utilisées dans l'écosystème Go pour signaler des conditions d'erreur de manière claire et concise.

Erreurs statiques simples : `errors.New`

La fonction `errors.New` du paquet standard `errors` est la manière la plus simple de créer une erreur. Elle prend une unique chaîne de caractères en argument, qui représente le message d'erreur, et retourne une valeur de type `error` contenant ce message.

Syntaxe : `err := errors.New("Description textuelle de l'erreur")`

Utilisez `errors.New` lorsque le message d'erreur est une chaîne statique, connue à l'avance, et ne dépend pas de variables ou de valeurs dynamiques au moment de l'erreur.

Exemple : Une fonction qui valide un âge et retourne une erreur si l'âge est négatif.

package main

import (
    "errors"
    "fmt"
)

// Erreur prédéfinie (bonne pratique pour les erreurs statiques réutilisables)
var ErrAgeNegatif = errors.New("l'âge ne peut pas être négatif")

func ValiderAge(age int) error {
    if age < 0 {
        // Retourne l'erreur prédéfinie
        return ErrAgeNegatif 
    }
    fmt.Println("Age valide.")
    return nil // Pas d'erreur
}

func main() {
    err1 := ValiderAge(30)
    if err1 != nil {
        fmt.Printf("Erreur 1: %v\n", err1)
    }

    err2 := ValiderAge(-5)
    if err2 != nil {
        fmt.Printf("Erreur 2: %v\n", err2) // Affiche: Erreur 2: l'âge ne peut pas être négatif
        // On peut vérifier si c'est exactement notre erreur prédéfinie
        if errors.Is(err2, ErrAgeNegatif) { // errors.Is est pour comparer les erreurs (plus avancé)
             fmt.Println("(C'est bien une erreur d'âge négatif)")
        }
    }
}
Définir des erreurs statiques comme des variables globales (comme `ErrAgeNegatif`) est une bonne pratique, car cela permet aux appelants de vérifier spécifiquement si *cette* erreur particulière s'est produite en utilisant `errors.Is`.

Erreurs dynamiques et formatées : `fmt.Errorf`

Souvent, le message d'erreur doit inclure des informations dynamiques, comme la valeur qui a causé le problème ou le nom d'un fichier spécifique. Dans ce cas, `errors.New` n'est pas suffisant. C'est là qu'intervient `fmt.Errorf` du paquet `fmt`.

`fmt.Errorf` fonctionne exactement comme `fmt.Sprintf` : elle prend une chaîne de formatage (avec des verbes comme `%s`, `%d`, `%v`) et des arguments supplémentaires, puis retourne une valeur de type `error` contenant la chaîne formatée.

Syntaxe : `err := fmt.Errorf("format string avec %v %d", valeur1, valeur2)`

Exemple : Une fonction qui tente d'ouvrir un fichier et retourne une erreur formatée si le fichier n'est pas trouvé.

package main

import (
    "fmt"
    "os" // Pour simuler une erreur d'ouverture de fichier
)

func LireFichierConfig(chemin string) error {
    // Simule une tentative d'ouverture
    _, err := os.Open(chemin) // os.Open retourne une erreur si le fichier n'existe pas
    
    if err != nil {
        // Crée une nouvelle erreur formatée incluant le chemin
        // et l'erreur originale retournée par os.Open
        return fmt.Errorf("impossible d'ouvrir le fichier de config '%s': %v", chemin, err)
    }
    fmt.Printf("Fichier '%s' ouvert avec succès (simulation).\n", chemin)
    return nil
}

func main() {
    err := LireFichierConfig("/chemin/inexistant.conf")
    if err != nil {
        // %v sur une erreur appelle sa méthode Error()
        fmt.Printf("Erreur: %v\n", err) 
        // Sortie possible (le message exact de os.Open peut varier):
        // Erreur: impossible d'ouvrir le fichier de config '/chemin/inexistant.conf': open /chemin/inexistant.conf: no such file or directory
    }
}

Une fonctionnalité importante de `fmt.Errorf` est le verbe de formatage `%w`. Lorsqu'il est utilisé avec une valeur d'erreur existante, il "enrobe" (wraps) cette erreur originale à l'intérieur de la nouvelle erreur créée. Cela permet de conserver la chaîne des erreurs et d'utiliser `errors.Is` ou `errors.As` (plus avancé) pour inspecter la cause sous-jacente. C'est la manière recommandée d'ajouter du contexte à une erreur existante tout en la propageant.

// ... dans LireFichierConfig ...
if err != nil {
    // Utilise %w pour enrober l'erreur 'err' originale
    return fmt.Errorf("échec lecture config '%s': %w", chemin, err)
}
// ...
Utiliser `%w` est préférable à `%v` lorsque vous formatez une autre erreur dans `fmt.Errorf` si vous souhaitez préserver la possibilité d'inspecter l'erreur d'origine.

Quand utiliser `errors.New` vs `fmt.Errorf` ?

Le choix entre les deux est généralement simple :

  • Utilisez `errors.New("message statique")` lorsque le message d'erreur est fixe et ne contient aucune information variable. C'est idéal pour définir des erreurs sentinelles constantes (comme `io.EOF` ou notre `ErrAgeNegatif`).
  • Utilisez `fmt.Errorf("message formaté %v", valeur)` lorsque le message d'erreur doit inclure des détails dynamiques (valeurs de variables, noms de fichiers, etc.) ou lorsque vous souhaitez ajouter du contexte à une erreur existante (en utilisant le verbe `%w` pour l'enrobage).

Les deux fonctions retournent une valeur qui satisfait l'interface `error`, elles sont donc interchangeables du point de vue du type retourné et peuvent être vérifiées avec `if err != nil` de la même manière.

Satisfaire l'interface `error` en toute simplicité

Il est important de réaliser que les valeurs retournées par `errors.New` et `fmt.Errorf` sont des types concrets qui implémentent l'interface `error` (c'est-à-dire qu'ils ont une méthode `Error() string`). Vous n'avez pas besoin de vous soucier des détails de cette implémentation lorsque vous les utilisez ; vous savez simplement que le résultat peut être traité comme n'importe quelle autre valeur `error`.

Cela signifie que vous pouvez retourner directement le résultat de ces fonctions depuis vos propres fonctions qui déclarent un type de retour `error`, comme nous l'avons fait dans les exemples précédents.

Cette simplicité permet de créer et de propager des erreurs de manière très fluide et idiomatique en Go, sans la surcharge de définir des classes d'exception complexes pour chaque situation.

Conclusion : Créer des erreurs claires et utiles

Savoir créer des erreurs est aussi important que savoir les vérifier. Les fonctions `errors.New` et `fmt.Errorf` sont les outils de base fournis par Go pour générer des valeurs d'erreur simples mais descriptives.

`errors.New` est parfait pour les messages statiques et les erreurs sentinelles, tandis que `fmt.Errorf` excelle dans la création d'erreurs contextuelles et dynamiques, avec la possibilité d'enrober les erreurs sous-jacentes grâce au verbe `%w`.

Utiliser ces fonctions de manière appropriée lorsque vos propres fonctions rencontrent des problèmes permet de fournir des informations claires aux appelants, facilitant ainsi le débogage et la gestion des erreurs dans l'ensemble de l'application, conformément à la philosophie explicite de Go en matière d'erreurs.