web-dev-qa-db-fra.com

Plusieurs valeurs dans un contexte à valeur unique

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?

90
Spearfisher

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 errors 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]+\]$`)

Retour à votre cas

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

67
icza

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.

7
jmaloney

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.

4
Antimony

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.

3
Kesarion

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]
    }
}
2
Jaehoon