Gestion des erreurs en Go

Le langage Go fournit un mécanisme de gestion des erreurs très simple via son interface error intégrée.

La gestion des erreurs de Go utilise une approche de retour d'erreur explicite plutôt qu'un mécanisme traditionnel de gestion des exceptions. Cette conception rend la logique du code plus claire et permet aux développeurs de gérer plus facilement les erreurs explicitement pendant la compilation ou l'exécution.

La gestion des erreurs de Go repose principalement sur les mécanismes suivants :

  1. errorinterfaceLa représentation standard de l'erreur.
  2. Valeur de retour expliciteLes erreurs sont renvoyées via la valeur de retour de la fonction.
  3. Erreur personnalisée: Les erreurs peuvent être créées via la bibliothèque standard ou de manière personnalisée.
  4. panicetrecoverGérer les erreurs fatales irrécupérables.

interface error

La bibliothèque standard Go définit une interface error, représentant une abstraction d'une erreur.

Le type error est un type interface. Voici sa définition :

type error interface {
    Error() string
}
  • Implémentationerrorinterface: Tout ce qui implémenteError()Tout type qui implémente la méthode Error() peut être utilisé comme une erreur.
  • Error()La méthode retourne une chaîne décrivant l'erreur.

Utiliser le package errors pour créer des erreurs

En programmation, nous pouvons générer des informations d'erreur en implémentant le type interface error.

Créer une erreur simple :

Exemple

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("this is an error")
    fmt.Println(err) // Sortie : this is an error
}

Les fonctions renvoient généralement les informations d'erreur comme dernière valeur de retour. Vous pouvez utiliser errors.New pour renvoyer un message d'erreur :

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

Dans l'exemple suivant, nous passons un nombre négatif à Sqrt, puis obtenons un objet d'erreur non nil. La comparaison de cet objet avec nil renvoie true, donc fmt.Println est appelé pour afficher l'erreur (lorsque le package fmt gère une erreur, il appelle la méthode Error). Voir le code d'appel d'exemple ci-dessous :

result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}

Renvoyer explicitement des erreurs

En Go, les erreurs sont généralement renvoyées comme valeurs de retour des fonctions, et les développeurs doivent les vérifier et les gérer explicitement.

Renvoyer explicitement des erreurs :

Exemple

package main

import (
        "errors"
        "fmt"
)

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, errors.New("division by zero")
        }
        return a / b, nil
}

func main() {
        result, err := divide(10, 0)
        if err != nil {
                fmt.Println("Error:", err)
        } else {
                fmt.Println("Result:", result)
        }
}

Sortie :

Error: division by zero

Erreur personnalisée

En définissant des types personnalisés, vous pouvez étendre l'interface error.

Types d'erreur personnalisés :

Exemple

package main

import (
        "fmt"
)

type DivideError struct {
        Dividend int
        Divisor  int
}

func (e *DivideError) Error() string {
        return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, &DivideError{Dividend: a, Divisor: b}
        }
        return a / b, nil
}

func main() {
        _, err := divide(10, 0)
        if err != nil {
                fmt.Println(err) // Sortie : cannot divide 10 by 0
        }
}

Le package fmt et le formatage des erreurs.

fmtLe package fournit une prise en charge de la sortie formatée pour les erreurs :

  • %v: Format par défaut.
  • %+v: Si pris en charge, afficher les informations détaillées sur l'erreur.
  • %sSortie sous forme de chaîne.

Exemple

package main

import (
    "fmt"
)

// Définir une struct DivideError
type DivideError struct {
    dividee int
    divider int
}

// Implémenter l'interface `error`
func (de *DivideError) Error() string {
    strFormat := `
    Impossible de continuer, le diviseur est zéro.
    dividee: %d
    divider: 0
`

    return fmt.Sprintf(strFormat, de.dividee)
}

// Définir une fonction pour la division de type `int`
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
            dData := DivideError{
                    dividee: varDividee,
                    divider: varDivider,
            }
            errorMsg = dData.Error()
            return
    } else {
            return varDividee / varDivider, ""
    }

}

func main() {

    // Cas normal
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
    }
    // Renvoie un message d'erreur lorsque le diviseur est zéro
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
    }

}

Exécutez le programme ci-dessus, la sortie est :

100/10 =  10
errorMsg is:  
    Cannot proceed, the divider is zero.
    dividee: 100
    divider: 0


Utilisation de errors.Is et errors.As

À partir de Go 1.13,errorsLe paquet introduiterrors.Iseterrors.AsUtilisé pour gérer les chaînes d'erreurs :

errors.Is

Vérifie si une erreur est une erreur spécifique ou a été enveloppée à partir de cette erreur.

Exemple

package main

import (
        "errors"
        "fmt"
)

var ErrNotFound = errors.New("not found")

func findItem(id int) error {
        return fmt.Errorf("erreur de base de données : %w", ErrNotFound)
}

func main() {
        err := findItem(1)
        if errors.Is(err, ErrNotFound) {
                fmt.Println("Élément introuvable")
        } else {
                fmt.Println("Autre erreur :", err)
        }
}

errors.As

Convertir l'erreur en un type spécifique pour un traitement ultérieur.

Exemple

package main

import (
        "errors"
        "fmt"
)

type MyError struct {
        Code int
        Msg  string
}

func (e *MyError) Error() string {
        return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}

func getError() error {
        return &MyError{Code: 404, Msg: "Not Found"}
}

func main() {
        err := getError()
        var myErr *MyError
        if errors.As(err, &myErr) {
                fmt.Printf("Custom error - Code: %d, Msg: %s\n", myErr.Code, myErr.Msg)
        }
}

panic et recover

Le panic de Go est utilisé pour gérer les erreurs irrécupérables, et recover est utilisé pour récupérer d'un panic.

panic:

  • Provoque le plantage du programme et affiche les informations de pile.
  • Souvent utilisé lorsque le programme ne peut pas continuer à s'exécuter.

recover:

  • catchpanicéviter les plantages du programme.

Exemple

package main

import "fmt"

func safeFunction() {
        defer func() {
                if r := recover(); r != nil {
                        fmt.Println("Récupéré après une panique :", r)
                }
        }()
        panic("quelque chose s'est mal passé")
}

func main() {
        fmt.Println("Démarrage du programme...")
        safeFunction()
        fmt.Println("Le programme a continué après la panique")
}

Exécutez le code ci-dessus, la sortie est :

Starting program...
Recovered from panic: something went wrong
Program continued after panic
autres extensions