Go Best Practices - Traitement des erreurs

C’est le premier article d’une série de leçons que j’ai apprises au cours des deux années avec lesquelles j'ai travaillé dans la production de Go. Nous exploitons bon nombre de services Go en production chez Saltside Technologies (psst, j’engage de nombreux postes à Bangalore pour Saltside) et j’exploite également ma propre entreprise où Go fait partie intégrante.

Nous couvrirons un large éventail de sujets, grands et petits.

Le premier sujet que je voulais aborder dans cette série est la gestion des erreurs. Cela crée souvent de la confusion et de la gêne pour les nouveaux développeurs de Go.

Un peu de fond - l'interface d'erreur

Nous sommes donc sur la même page. Comme vous le savez peut-être, une erreur dans Go concerne simplement tout ce qui implémente l'interface d'erreur. Voici à quoi ressemble la définition d'interface:

interface d'erreur de type {
    Error () string
}

Donc, tout ce qui implémente la méthode de chaîne Error () peut être utilisé comme une erreur.

Vérification des erreurs

Utilisation de structures d'erreur et de vérification de type

Quand j'ai commencé à écrire Go, je faisais souvent des comparaisons entre chaînes de messages d'erreur pour voir quel était le type d'erreur (oui, il est embarrassant d'y penser, mais il faut parfois regarder en arrière pour aller de l'avant).

Une meilleure approche consiste à utiliser des types d'erreur. Ainsi, vous pouvez (bien sûr) créer des structures qui implémentent l'interface d'erreur, puis effectuer une comparaison de type dans une instruction switch.

Voici un exemple d'implémentation d'erreur.

tapez errZeroDivision struct {
    chaîne de message
}
func NewErrZeroDivision (chaîne de message) * ErrZeroDivision {
    return & ErrZeroDivision {
        message: message,
    }
}
func (e * ErrZeroDivision) Erreur () chaîne {
    retour e.message
}

Maintenant, cette erreur peut être utilisée comme ceci.

func main () {
    résultat, err: = diviser (1.0, 0.0)
    si err! = nil {
        commutateur err. (type) {
        case * ErrZeroDivision:
            fmt.Println (err.Error ())
        défaut:
            fmt.Println ("Qu'est-ce que le h * vient de se passer?")
        }
    }
    fmt.Println (résultat)
}
func divide (a, b float64) (float64, erreur) {
    si b == 0,0 {
        return 0.0, NewErrZeroDivision ("Impossible de diviser par zéro")
    }
    retourne a / b, nil
}

Voici le lien Go Play pour un exemple complet. Remarquez le modèle switch err. (Type), qui permet de rechercher différents types d’erreur plutôt qu’un autre type (comme la comparaison de chaînes ou quelque chose de similaire).

Utilisation du package d'erreurs et de la comparaison directe

L'approche ci-dessus peut également être gérée à l'aide du paquetage d'erreurs. Cette approche est recommandée pour les vérifications d'erreur dans le package où vous avez besoin d'une représentation d'erreur rapide.

var errNotFound = errors.New ("Elément introuvable")
func main () {
    err: = getItem (123) // Cela lancerait errNotFound
    si err! = nil {
        switch err {
        case errNotFound:
            log.Println ("Elément demandé introuvable")
        défaut:
            log.Println ("Une erreur inconnue s'est produite")
        }
    }
}

Cette approche est moins efficace lorsque vous avez besoin d’objets d’erreur plus complexes, par exemple avec codes d'erreur, etc. Dans ce cas, vous devez créer votre propre type qui implémente l'interface d'erreur.

Traitement immédiat des erreurs

Parfois, je rencontre un code comme ci-dessous (mais généralement avec plus de duvet ...):

func example1 () error {
    err: = call1 ()
    retourne err
}

Le point ici est que l’erreur n’est pas traitée immédiatement. Cette approche est fragile, car une personne peut insérer du code entre err: = call1 () et return err, ce qui romprait l’intention, ce qui pourrait masquer la première erreur. Deux approches alternatives:

// Réduit le retour et l'erreur.
func example2 () error {
    renvoyer call1 ()
}
// Effectue une gestion d'erreur explicite juste après l'appel.
func example3 () error {
    err: = call1 ()
    si err! = nil {
        retourne err
    }
    retourne zéro
}

Les deux approches ci-dessus me conviennent. Ils réalisent la même chose, qui est; si quelqu'un a besoin d'ajouter quelque chose après call1 (), il doit s'occuper du traitement des erreurs.

C'est tout pour aujourd'hui

Restez à l'écoute pour le prochain article sur Go Best Practices. Allez fort :).

func main () {
    err: = readArticle ("Go Best Practices - Traitement des erreurs")
    si err! = nil {
        ping ("@ sebdah")
    }
}