En raison de la gestion des erreurs dans Go, je me retrouve souvent avec plusieurs fonctions de valeurs. Jusqu'à présent, ma gestion a été très complexe et je recherche les meilleures pratiques pour écrire un code plus propre.
Disons que j'ai la fonction suivante:
type Item struct {
Value int
Name string
}
func Get(value int) (Item, error) {
// some code
return item, nil
}
Comment attribuer une nouvelle variable à item.Value
avec élégance. Avant d'introduire le traitement des erreurs, ma fonction vient de retourner item
et je pouvais simplement le faire:
val := Get(1).Value
Maintenant je fais ceci:
item, _ := Get(1)
val := item.Value
N'y a-t-il pas moyen d'accéder directement à la première variable renvoyée?
Dans le cas d'une fonction de retour à valeurs multiples, vous ne pouvez pas faire référence à des champs ou à des méthodes d'une valeur spécifique du résultat lorsque vous appelez la fonction.
Et si l’un d’eux est un error
, c’est là pour un raison (qui est la fonction pourrait échouer) et vous devriez pas le contourner, car Si vous le faites, votre code suivant pourrait échouera aussi misérablement (par exemple, provoquant une panique à l'exécution).
Cependant, il peut y avoir des situations où vous savez le code n'échouera en aucun cas. Dans ces cas, vous pouvez fournir une fonction (ou méthode) helper qui éliminera le error
(ou provoquera une panique d'exécution si elle se produit toujours).
Cela peut être le cas si vous fournissez les valeurs d'entrée d'une fonction à partir de code et que vous savez qu'elles fonctionnent.
Les exemples template
et regexp
: si vous fournissez un modèle valide ou une expression rationnelle au moment de la compilation, vous pouvez: assurez-vous qu'ils peuvent toujours être analysés sans erreurs au moment de l'exécution. Pour cette raison, le package template
fournit la fonction Must(t *Template, err error) *Template
et le package regexp
fournit la fonction MustCompile(str string) *Regexp
: ils ne renvoient pas error
s car leur utilisation prévue est où l'entrée est garantie d'être valide.
Exemples:
// "text" is a valid template, parsing it will not fail
var t = template.Must(template.New("name").Parse("text"))
// `^[a-z]+\[[0-9]+\]$` is a valid regexp, always compiles
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)
IF vous pouvez être certain que Get()
ne produira pas error
pour certaines valeurs d'entrée, vous pouvez créer une fonction helper Must()
qui ne renverrait pas le error
mais susciterait une panique d'exécution si elle se produisait toujours:
func Must(i Item, err error) Item {
if err != nil {
panic(err)
}
return i
}
Mais vous ne devriez pas utiliser cela dans tous les cas, juste quand vous êtes sûr que ça réussit. Usage:
val := Must(Get(1)).Value
Alternative/Simplification
Vous pouvez même le simplifier davantage si vous intégrez l'appel Get()
à votre fonction d'assistance, appelons-le MustGet
:
func MustGet(value int) Item {
i, err := Get(value)
if err != nil {
panic(err)
}
return i
}
Usage:
val := MustGet(1).Value
Voir des questions intéressantes/connexes:
comment analyser plusieurs déclarations dans golang
Retourne la carte comme "ok" dans Golang sur les fonctions normales
Non, mais c'est une bonne chose, car vous devez toujours gérer vos erreurs.
Il existe des techniques que vous pouvez utiliser pour différer la gestion des erreurs, voir Les erreurs sont des valeurs par Rob Pike.
ew := &errWriter{w: fd} ew.write(p0[a:b]) ew.write(p1[c:d]) ew.write(p2[e:f]) // and so on if ew.err != nil { return ew.err }
Dans cet exemple du billet de blog, il montre comment vous pouvez créer un type errWriter
qui diffère le traitement des erreurs jusqu'à ce que vous ayez fini d'appeler write
.
Non, vous ne pouvez pas accéder directement à la première valeur.
Je suppose qu'un hack pour cela serait de retourner un tableau de valeurs au lieu de "item" et "err", puis de ne faire que item, _ := Get(1)[0]
mais je ne le recommanderais pas.
Oui il y a.
Surprenant, hein? Vous pouvez obtenir une valeur spécifique à partir d'une déclaration multiple à l'aide d'une simple fonction mute
:
package main
import "fmt"
import "strings"
func µ(a ...interface{}) []interface{} {
return a
}
type A struct {
B string
C func()(string)
}
func main() {
a := A {
B:strings.TrimSpace(µ(E())[1].(string)),
C:µ(G())[0].(func()(string)),
}
fmt.Printf ("%s says %s\n", a.B, a.C())
}
func E() (bool, string) {
return false, "F"
}
func G() (func()(string), bool) {
return func() string { return "Hello" }, true
}
https://play.golang.org/p/IwqmoKwVm-
Remarquez comment vous sélectionnez le numéro de valeur, comme vous le feriez dans une tranche/un tableau, puis le type pour obtenir la valeur réelle.
Vous pouvez en savoir plus sur la science derrière cela de cet article . Crédits à l'auteur.
Que diriez-vous de cette façon?
package main
import (
"fmt"
"errors"
)
type Item struct {
Value int
Name string
}
var items []Item = []Item{{Value:0, Name:"zero"},
{Value:1, Name:"one"},
{Value:2, Name:"two"}}
func main() {
var err error
v := Get(3, &err).Value
if err != nil {
fmt.Println(err)
return
}
fmt.Println(v)
}
func Get(value int, err *error) Item {
if value > (len(items) - 1) {
*err = errors.New("error")
return Item{}
} else {
return items[value]
}
}