Évitez de vérifier si l’erreur est nulle répétition?

Je suis actuellement en train d’apprendre et certaines de mes codes ressemblent à ceci:

a, err := doA() if err != nil { return nil, err } b, err := doB(a) if err != nil { return nil, err } c, err := doC(b) if err != nil { return nil, err } ... and so on ... 

Cela me semble un peu faux car la vérification des erreurs prend la plupart des lignes. Existe-t-il un meilleur moyen de traiter les erreurs? Est-ce que je peux peut-être éviter ceci avec un certain refactoring?

MISE À JOUR: Merci pour toutes les réponses. Veuillez noter que dans mon exemple, doB dépend de a, doC dépend de b et ainsi de suite. Par conséquent, la plupart des refactorings suggérés ne fonctionnent pas dans ce cas. Toute autre suggestion?

C’est une plainte courante, et il y a plusieurs réponses.

Voici quelques exemples courants:

1 – Ce n’est pas si grave

C’est une réaction très courante à ces plaintes. Le fait que vous ayez quelques lignes de code supplémentaires dans votre code n’est pas si mauvais. C’est juste un peu de typage bon marché, et très facile à gérer du côté de la lecture.

2 – C’est en fait une bonne chose

Ceci est basé sur le fait que taper et lire ces lignes supplémentaires est un très bon rappel que, en fait, votre logique pourrait s’échapper à ce stade et que vous devez annuler toute gestion de ressources que vous avez mise en place dans les lignes qui la précèdent. Ceci est généralement présenté en comparaison avec les exceptions, ce qui peut briser le stream de logique de manière implicite, obligeant le développeur à toujours garder à l’esprit le chemin de l’erreur cachée. Il y a quelque temps, j’ai écrit un article plus détaillé à ce sujet ici .

3 – Utiliser la panique / récupérer

Dans certaines circonstances spécifiques, vous pouvez éviter une partie de ce travail en utilisant la panic avec un type connu, puis en utilisant recover juste avant que le code de votre package ne soit diffusé dans le monde, le transformant en une erreur correcte et le renvoyant. Cette technique est généralement utilisée pour dérouler une logique récursive telle que les marshalers.

Je m’efforce personnellement de ne pas trop en abuser, car je corrèle plus étroitement avec les points 1 et 2.

4 – Réorganiser un peu le code

Dans certaines circonstances, vous pouvez réorganiser légèrement la logique pour éviter la répétition.

Comme exemple sortingvial, ceci:

 err := doA() if err != nil { return err } err := doB() if err != nil { return err } return nil 

peut également être organisé comme:

 err := doA() if err != nil { return err } return doB() 

5 – Utiliser les résultats nommés

Certaines personnes utilisent des résultats nommés pour supprimer la variable err de l’instruction return. Je vous déconseille de le faire, car cela économise très peu, réduit la clarté du code et rend la logique sujette à des problèmes subtils lorsqu’un ou plusieurs résultats sont définis avant la déclaration de retour.

6 – Utilisez la déclaration avant la condition if

Comme Tom Wilde l’a bien rappelé dans le commentaire ci-dessous, if déclarations de Go acceptent une simple déclaration avant la condition. Vous pouvez donc faire ceci:

 if err := doA(); err != nil { return err } 

Ceci est un bon idiome de Go, et utilisé souvent.

Dans certains cas spécifiques, je préfère éviter d’inclure cette affirmation de cette manière, simplement pour la rendre propre à des fins de clarté, mais c’est une chose subtile et personnelle.

Vous pouvez utiliser des parameters de retour nommés pour raccourcir un peu les choses

Lien terrain de jeux

 func doStuff() (result ssortingng, err error) { a, err := doA() if err != nil { return } b, err := doB(a) if err != nil { return } result, err = doC(b) if err != nil { return } return } 

Après avoir programmé dans Go a, vous comprendrez que le fait de devoir vérifier l’erreur pour chaque fonction vous fait penser à ce que cela signifie réellement si cette fonction se passe mal et comment vous devriez la gérer.

Si vous avez plusieurs de ces situations récurrentes où vous avez plusieurs de ces vérifications d’erreur, vous pouvez vous définir comme suit:

 func validError(errs ...error) error { for i, _ := range errs { if errs[i] != nil { return errs[i] } } return nil } 

Cela vous permet de sélectionner l’une des erreurs et de retourner s’il y en a une qui est non nulle.

Exemple d’utilisation ( version complète en jeu ):

 x, err1 := doSomething(2) y, err2 := doSomething(3) if e := validError(err1, err2); e != nil { return e } 

Bien sûr, cela ne peut être appliqué que si les fonctions ne dépendent pas les unes des autres, mais ceci est une condition préalable générale pour résumer la gestion des erreurs.

Vous avez peut-être tort parce que vous avez l’habitude de ne pas traiter les erreurs sur le site de l’appel. Ceci est assez idiomatique pour go mais ressemble beaucoup à un passe-partout si vous n’y êtes pas habitué.

Cela présente cependant des avantages.

  1. Vous devez réfléchir à la manière appropriée de gérer cette erreur sur le site où l’erreur a été générée.
  2. Il est facile de lire le code pour voir tous les points auxquels le code sera annulé et de revenir plus tôt.

Si cela vous ennuie vraiment, vous pouvez être créatif avec les boucles et les fonctions anonymes, mais cela devient souvent compliqué et difficile à lire.

Vous pouvez créer un type de contexte avec une valeur de résultat et une erreur.

 type Type1 struct { a int b int c int err error } func (t *Type1) doA() { if t.err != nil { return } // do something if err := do(); err != nil { t.err = err } } func (t *Type1) doB() { if t.err != nil { return } // do something b, err := t.doWithA(a) if err != nil { t.err = err return } tb = b } func (t *Type1) doC() { if t.err != nil { return } // do something c, err := do() if err != nil { t.err = err return } tc = c } func main() { t := Type1{} t.doA() t.doB() t.doC() if t.err != nil { // handle error in t } } 

Vous pouvez passer une erreur en tant qu’argument de fonction

 func doA() (A, error) { ... } func doB(a A, err error) (B, error) { ... } c, err := doB(doA()) 

J’ai remarqué certaines méthodes dans le package “html / template” pour cela, par exemple

 func Must(t *Template, err error) *Template { if err != nil { panic(err) } return t }