Go Techniques de gestion des erreurs [fermé]


108

Je ne fais que commencer avec Go. Mon code commence à en avoir beaucoup:

   if err != nil {
      //handle err
   }

ou ca

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

Existe-t-il de bonnes idiomes / stratégies / bonnes pratiques pour vérifier et gérer les erreurs dans Go?

EDIT pour clarifier: je ne suis pas en train de faire du mal ou de suggérer que l'équipe Go propose quelque chose de mieux. Je demande si je fais les choses correctement ou si j'ai raté une technique que la communauté a inventée. Merci a tous.


4
Non, il n'y en a pas vraiment. C'est un sujet souvent discuté et un sujet sensible. Il y avait aussi de nombreuses propositions d'évolution. La réponse de l'équipe semble être que cela ne devrait pas être un problème dans un code bien écrit.
Denys Séguret


Notez que cette question connexe n'est pas vraiment la même que celle-ci. Les réponses sont trop précises.
Denys Séguret

Il y a aussi une justification à ce désagrément: cela rend plus difficile l'écriture rapide d'un programme, mais cela rend également plus difficile de créer des bogues en renvoyant simplement les erreurs.
Denys Séguret

Vous pouvez trouver Andrew Gerrand et Brad Fitzpatrick qui écrivent les débuts d'un client HTTP / 2 dans Go de façon plus ou moins similaire youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Réponses:


61

Votre code est idiomatique et à mon avis, c'est la meilleure pratique disponible. Certains seraient en désaccord avec certitude, mais je dirais que c'est le style que l'on retrouve dans toutes les bibliothèques standard de Golang . En d'autres termes, les auteurs de Go écrivent la gestion des erreurs de cette manière.


12
"Les auteurs de Go écrivent la gestion des erreurs de cette manière." Cela me semble bien.
gmoore

«Certains ne seraient pas d'accord» : je ne suis pas sûr que quelqu'un dirait que ce n'est pas la meilleure pratique disponible aujourd'hui. Certains demandent du sucre de syntaxe ou d'autres changements, mais aujourd'hui je ne pense pas qu'un codeur sérieux vérifierait les erreurs autrement.
Denys Séguret

@dystroy: OK, certains disent " it sux ", d'autres l'appellent "les erreurs sont gérées dans les valeurs de retour. Style des années 70". , et ainsi de suite ;-)
zzzz

2
@jnml Gérer les erreurs de cette façon est une question de conception du langage, qui est un sujet très controversé. Heureusement, il existe des dizaines de langues parmi lesquelles choisir.
fuz le

4
Ce qui me tue, c'est la façon dont le même modèle est utilisé pour chaque appel de fonction. Cela rend le code plutôt bruyant à certains endroits et crie simplement pour que le sucre syntaxique simplifie le code sans perdre aucune information, ce qui est essentiellement la définition de la concision (qui, à mon avis, est un attribut supérieur à la verbosité, mais c'est sans doute un sujet controversé point). Le principe est sain, mais la syntaxe laisse beaucoup à désirer à mon humble avis. Cependant, il est interdit de se plaindre, alors je vais juste boire mon kool-aid maintenant ;-)
Thomas

30

Six mois après cette question, Rob Pike a écrit un article de blog intitulé Les erreurs sont des valeurs .

Là-dedans, il fait valoir que vous n'avez pas besoin de programmer de la manière présentée par l'OP, et mentionne plusieurs endroits dans la bibliothèque standard où ils utilisent un modèle différent.

Bien sûr, une déclaration courante impliquant une valeur d'erreur est de tester si elle est nulle, mais il y a d'innombrables autres choses que l'on peut faire avec une valeur d'erreur, et l'application de certaines de ces autres choses peut améliorer votre programme, éliminant une grande partie du passe-partout. cela se produit si chaque erreur est vérifiée avec une instruction rote if.

...

Utilisez le langage pour simplifier votre gestion des erreurs.

Mais rappelez-vous: quoi que vous fassiez, vérifiez toujours vos erreurs!

C'est une bonne lecture.


Merci! Je vais vérifier cela.
gmoore

L'article est génial, il présente essentiellement un objet qui peut être en état d'échec, et si c'est le cas, il ignorera tout ce que vous en faites et restera en état d'échec. Pour moi, cela ressemble à presque monade.
Waterlink

@Waterlink Votre déclaration n'a aucun sens. Tout ce qui a un état est presque monade, si vous louchez un peu. Le comparer à en.wikipedia.org/wiki/Null_Object_pattern est plus utile, je pense.
user7610

@ user7610, merci pour vos commentaires. Je ne peux qu'être d'accord.
Waterlink

2
Pike: "Mais rappelez-vous: quoi que vous fassiez, vérifiez toujours vos erreurs!" - c'est tellement des années 80. Des erreurs peuvent survenir n'importe où, cessez de surcharger les programmeurs et adoptez des exceptions pour le bien de Pete.
Slawomir

22

Je serais d'accord avec la réponse de jnml selon laquelle ils sont tous deux du code idiomatique, et j'ajouterais ce qui suit:

Votre premier exemple:

if err != nil {
      //handle err
}

est plus idiomatique lorsqu'il s'agit de plusieurs valeurs de retour. par exemple:

val, err := someFunc()
if err != nil {
      //handle err
}
//do stuff with val

Votre deuxième exemple est un bon raccourci lorsqu'il ne traite que de la errvaleur. Cela s'applique si la fonction renvoie uniquement un error, ou si vous ignorez délibérément les valeurs renvoyées autres que le error. À titre d'exemple, ceci est parfois utilisé avec les fonctions Readeret Writerqui renvoient un intdu nombre d'octets écrits (informations parfois inutiles) et un error:

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

Le deuxième formulaire est appelé à utiliser une instruction d'initialisation if .

Donc en ce qui concerne les meilleures pratiques, pour autant que je sache (sauf pour l'utilisation du package "errors" pour créer de nouvelles erreurs lorsque vous en avez besoin), vous avez couvert à peu près tout ce dont vous avez besoin pour savoir sur les erreurs dans Go!

EDIT: Si vous trouvez que vous ne pouvez vraiment pas vivre sans exceptions, vous pouvez les imiter avec defer, panic&recover .


4

J'ai créé une bibliothèque pour une gestion simplifiée des erreurs et un acheminement via une file d'attente de fonctions Go.

Vous pouvez le trouver ici: https://github.com/go-on/queue

Il a une variante syntaxique compacte et verbeuse. Voici un exemple de syntaxe courte:

import "github.com/go-on/queue/q"

func SaveUser(w http.ResponseWriter, rq *http.Request) {
    u := &User{}
    err := q.Q(                      
        ioutil.ReadAll, rq.Body,  // read json (returns json and error)
    )(
        // q.V pipes the json from the previous function call
        json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
    )(
        u.Validate,               // validate the user (returns error)
    )(
        u.Save,                   // save the user (returns error)
    )(
        ok, w,                    // send the "ok" message (returns no error)
    ).Run()

    if err != nil {
       switch err {
         case *json.SyntaxError:
           ...
       }
    }
}

Sachez qu'il y a une légère surcharge de performance, car elle utilise la réflexion.

Ce n'est pas non plus du code go idiomatique, vous voudrez donc l'utiliser dans vos propres projets ou si votre équipe accepte de l'utiliser.


3
Ce n'est pas parce que vous pouvez le faire que c'est une bonne idée. Cela ressemble au modèle de la chaîne de responsabilité , sauf peut-être plus difficile à lire (opinion). Je suggérerais que ce n'est pas "Go idiomatique". Intéressant, cependant.
Steven Soroka

2

Une «stratégie» pour gérer les erreurs en golang et dans d'autres langages est de propager continuellement les erreurs dans la pile d'appels jusqu'à ce que vous soyez suffisamment haut dans la pile d'appels pour gérer cette erreur. Si vous avez essayé de gérer cette erreur trop tôt, vous finirez probablement par répéter le code. Si vous le gérez trop tard, vous casserez quelque chose dans votre code. Golang rend ce processus très facile car il indique très clairement si vous gérez une erreur à un emplacement donné ou si vous la propagez.

Si vous allez ignorer l'erreur, un simple _ révélera ce fait très clairement. Si vous le gérez, le cas exact de l'erreur que vous gérez est clair car vous le vérifierez dans l'instruction if.

Comme les gens l'ont dit ci-dessus, une erreur n'est en fait qu'une valeur normale. Cela le traite comme tel.


2

Les dieux de Go ont publié un "projet de conception" pour la gestion des erreurs dans Go 2. Il vise à changer l'idiome des erreurs:

Vue d'ensemble et conception

Ils veulent des commentaires des utilisateurs!

Wiki de commentaires

En bref, cela ressemble à:

func f() error {
   handle err { fmt.Println(err); return err }
   check mayFail()
   check canFail()
}

MISE À JOUR: Le projet de conception a reçu beaucoup de critiques, j'ai donc rédigé les exigences à prendre en compte pour la gestion des erreurs Go 2 avec un menu de possibilités pour une solution éventuelle.


1

La plupart dans l'industrie, suivent les règles standard mentionnées dans la documentation golang Gestion des erreurs et Go . Et cela facilite également la génération de documents pour le projet.


Ceci est essentiellement une réponse de lien seulement. Je vous suggère d'ajouter du contenu à la réponse afin que si le lien devenait invalide, votre réponse serait toujours utile.
Neo

merci pour le précieux commentaire.
pschilakanti

0

Voici mon point de vue sur la réduction de la gestion des erreurs sur Go, un exemple est pour obtenir des paramètres d'URL HTTP:

(Modèle de conception dérivé de https://blog.golang.org/errors-are-values )

type HTTPAdapter struct {
    Error *common.AppError
}

func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
    requestUUID := uuid.Parse(mux.Vars(r)[param])
    if requestUUID == nil { 
        adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
            possibleError, http.StatusBadRequest)
    }
    return requestUUID
}

l'appeler pour plusieurs paramètres possibles serait comme ci-dessous:

    adapter := &httphelper.HTTPAdapter{}
    viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
    messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
    if adapter.Error != nil {
        return nil, adapter.Error
    }

Ce n'est pas une solution miracle, l'inconvénient est que si vous avez eu plusieurs erreurs, vous ne pouvez obtenir que la dernière erreur.

Mais dans ce cas, il est relativement répétitif et à faible risque, je peux donc simplement obtenir la dernière erreur possible.


-1

Vous pouvez nettoyer votre code de gestion des erreurs pour des erreurs similaires (puisque les erreurs sont des valeurs auxquelles vous devez faire attention ici) et écrire une fonction que vous appelez avec l'erreur transmise pour gérer l'erreur. Vous n'aurez pas à écrire "if err! = Nil {}" à chaque fois. Encore une fois, cela ne fera que nettoyer le code, mais je ne pense pas que ce soit la manière idiomatique de faire les choses.

Encore une fois, ce n'est pas parce que vous pouvez le faire .


-1

goerr permet de gérer les erreurs avec les fonctions

package main

import "github.com/goerr/goerr"
import "fmt"

func ok(err error) {
    if err != nil {
        goerr.Return(err)
        // returns the error from do_somethingN() to main()
        // sequence() is terminated
    }
}

func sequence() error {
    ok(do_something1())
    ok(do_something2())
    ok(do_something3())

    return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
    fmt.Println("DOING 3")
    return nil
}

func main() {
    err_do_something := goerr.OR1(sequence)

    // handle errors

    fmt.Println(err_do_something)
}

Ick. Compliquer / masquer la logique de gestion des erreurs comme celle-ci n'est pas une bonne idée IMO. Le code résultant (qui nécessite un prétraitement source par goerr) est plus difficile à lire / raisonner que le code Go idiomatique.
Dave C

-4

Si vous voulez un contrôle précis des erreurs, ce n'est peut-être pas la solution, mais pour moi, la plupart du temps, toute erreur est un frein.

Donc, j'utilise des fonctions à la place.

func Err(err error) {
    if err!=nil {
        fmt.Println("Oops", err)
        os.Exit(1)
    }
}

fi, err := os.Open("mmm.txt")
Err(err)

De tels messages devraient aller à stderrplutôt que stdout, alors utilisez simplement log.Fatal(err)ou log.Fatalln("some message:", err). Puisque presque rien d'autre que mainne devrait prendre une telle décision pour mettre fin à tout le programme (c'est-à-dire renvoyer des erreurs de fonctions / méthodes, ne pas abandonner) dans de rares cas, c'est ce que vous voulez faire, c'est plus propre et mieux vaut le faire explicitement (c'est-à-dire if err := someFunc(); err != nil { log.Fatal(err) }) plutôt que via une fonction "helper" qui n'est pas claire sur ce qu'elle fait (le nom "Err" n'est pas bon, il ne donne aucune indication qu'il peut terminer le programme).
Dave C

J'ai appris une nouvelle chose! Merci @DaveC
Gon
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.