Wiele wartości w kontekście pojedynczej wartości

110

Ze względu na obsługę błędów w Go często otrzymuję funkcje wielu wartości. Jak dotąd sposób, w jaki sobie z tym radziłem, był bardzo chaotyczny i szukam najlepszych praktyk pisania czystszego kodu.

Powiedzmy, że mam następującą funkcję:

type Item struct {
   Value int
   Name string
}

func Get(value int) (Item, error) {
  // some code

  return item, nil
}

Jak mogę item.Valueelegancko przypisać nową zmienną . Przed wprowadzeniem obsługi błędów moja funkcja właśnie wróciła itemi mogłem po prostu zrobić to:

val := Get(1).Value

Teraz robię to:

item, _ := Get(1)
val := item.Value

Czy nie ma sposobu na bezpośredni dostęp do pierwszej zwróconej zmiennej?

Włócznik
źródło
3
itemzwykle nilw przypadku błędu. Bez uprzedniego sprawdzenia, czy nie wystąpił błąd, w takim przypadku kod ulegnie awarii.
Thomas

Odpowiedzi:

83

W przypadku funkcji zwracającej wielowartościowe wywołanie funkcji nie może odwoływać się do pól lub metod o określonej wartości wyniku.

A jeśli jeden z nich jest to error, że to nie dla rozumu (który jest funkcja może nie) i należy nie obwodnica, bo jeśli nie, twój kolejny kod może również nie zdało egzaminu (np powodując wykonawczego paniki).

Jednak mogą wystąpić sytuacje, w których wiesz, że kod nie zawiedzie w żadnych okolicznościach. W takich przypadkach można zapewnić funkcję (lub metodę) pomocniczą, która odrzuci error(lub podniesie panikę w czasie wykonywania, jeśli nadal występuje).
Może się tak zdarzyć, jeśli podasz wartości wejściowe funkcji z kodu i wiesz, że działają.
Świetnymi przykładami tego są pakiety templatei regexp: jeśli podasz prawidłowy szablon lub wyrażenie regularne w czasie kompilacji, możesz być pewien, że zawsze można je przeanalizować bez błędów w czasie wykonywania. Z tego powodu templatepakiet zapewnia Must(t *Template, err error) *Templatefunkcję, a regexppakiet zapewnia MustCompile(str string) *Regexpfunkcję: nie zwracająerrors, ponieważ ich zamierzone użycie jest tam, gdzie dane wejściowe są gwarantowane.

Przykłady:

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

Wracając do twojej sprawy

JEŚLI możesz być pewien, Get()że nie wygeneruje errorpewnych wartości wejściowych, możesz utworzyć funkcję pomocniczą, Must()która nie zwróci, errorale wywoła panikę w czasie wykonywania, jeśli nadal występuje:

func Must(i Item, err error) Item {
    if err != nil {
        panic(err)
    }
    return i
}

Ale nie powinieneś tego używać we wszystkich przypadkach, tylko wtedy, gdy masz pewność, że się powiedzie. Stosowanie:

val := Must(Get(1)).Value

Alternatywa / uproszczenie

Możesz nawet jeszcze bardziej to uprościć, jeśli włączysz Get()wywołanie do funkcji pomocniczej, nazwijmy to MustGet:

func MustGet(value int) Item {
    i, err := Get(value)
    if err != nil {
        panic(err)
    }
    return i
}

Stosowanie:

val := MustGet(1).Value

Zobacz kilka interesujących / powiązanych pytań:

jak analizować wielokrotne zwroty w golang

Zwróć mapę jak „ok” w Golang na normalnych funkcjach

icza
źródło
7

Nie, ale to dobrze, ponieważ zawsze powinieneś radzić sobie z błędami.

Istnieją techniki, które możesz zastosować, aby odroczyć obsługę błędów, zobacz Błędy to wartości Roba Pike'a.

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
}

W tym przykładzie z wpisu na blogu ilustruje, w jaki sposób można utworzyć errWritertyp, który odracza obsługę błędów, dopóki nie skończysz dzwonić write.

jmaloney
źródło
6

Tak jest.

Zaskakujące, co? Możesz uzyskać określoną wartość z wielokrotnego zwrotu za pomocą prostej mutefunkcji:

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-

Zwróć uwagę, jak wybierasz numer wartości, tak jak w przypadku wycinka / tablicy, a następnie typ, aby uzyskać rzeczywistą wartość.

Możesz przeczytać więcej o nauce stojącej za tym z tego artykułu . Podziękowania dla autora.

Kesarion
źródło
5

Nie, nie możesz bezpośrednio uzyskać dostępu do pierwszej wartości.

Przypuszczam, że dobrym pomysłem byłoby zwrócenie tablicy wartości zamiast „item” i „err”, a następnie zrobienie tego, item, _ := Get(1)[0] ale nie polecam tego.

Antymon
źródło
3

A może w ten sposób?

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]
    }
}
Jaehoon
źródło