Beberapa nilai dalam konteks nilai tunggal

110

Karena penanganan kesalahan di Go, saya sering berakhir dengan beberapa fungsi nilai. Sejauh ini, cara saya mengaturnya sangat berantakan dan saya mencari praktik terbaik untuk menulis kode yang lebih bersih.

Katakanlah saya memiliki fungsi berikut:

type Item struct {
   Value int
   Name string
}

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

  return item, nil
}

Bagaimana saya dapat menetapkan variabel baru dengan item.Valueelegan. Sebelum memperkenalkan penanganan kesalahan, fungsi saya baru saja kembali itemdan saya cukup melakukan ini:

val := Get(1).Value

Sekarang saya melakukan ini:

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

Apakah tidak ada cara untuk mengakses secara langsung variabel pertama yang dikembalikan?

Spearfisher
sumber
3
itembiasanya akan nilterjadi jika terjadi kesalahan. Tanpa memeriksa kesalahan terlebih dahulu, kode Anda akan macet dalam kasus itu.
Thomas

Jawaban:

83

Dalam kasus fungsi pengembalian multi-nilai, Anda tidak bisa merujuk ke bidang atau metode dari nilai tertentu dari hasil saat memanggil fungsi tersebut.

Dan jika salah satunya adalah error, kode itu ada karena suatu alasan (yaitu fungsinya mungkin gagal) dan Anda tidak boleh melewatinya karena jika Anda melakukannya, kode berikutnya mungkin juga gagal total (misalnya, mengakibatkan panik runtime).

Namun mungkin ada situasi di mana Anda tahu kode tidak akan gagal dalam keadaan apa pun. Dalam kasus ini, Anda bisa menyediakan fungsi helper (atau metode) yang akan membuang error(atau menimbulkan kepanikan runtime jika masih terjadi).
Ini bisa terjadi jika Anda memberikan nilai input untuk fungsi dari kode, dan Anda tahu bahwa fungsi tersebut berfungsi.
Contoh yang bagus dari ini adalah paket templatedan regexp: jika Anda memberikan template yang valid atau regexp pada waktu kompilasi, Anda dapat memastikan bahwa template tersebut selalu dapat diurai tanpa kesalahan saat runtime. Untuk alasan ini templatepaket menyediakan Must(t *Template, err error) *Templatefungsi dan regexppaket menyediakan MustCompile(str string) *Regexpfungsi: mereka tidak mengembalikanerrors karena tujuan penggunaannya adalah tempat input dijamin valid.

Contoh:

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

Kembali ke kasusmu

JIKA Anda yakin Get()tidak akan menghasilkan erroruntuk nilai input tertentu, Anda dapat membuat Must()fungsi pembantu yang tidak akan mengembalikan errortetapi menimbulkan kepanikan runtime jika masih terjadi:

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

Tetapi Anda tidak boleh menggunakan ini dalam semua kasus, hanya jika Anda yakin itu berhasil. Pemakaian:

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

Alternatif / Penyederhanaan

Anda bahkan dapat menyederhanakannya lebih jauh jika Anda memasukkan Get()panggilan ke dalam fungsi pembantu Anda, sebut saja MustGet:

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

Pemakaian:

val := MustGet(1).Value

Lihat beberapa pertanyaan menarik / terkait:

bagaimana mengurai beberapa pengembalian di golang

Kembalikan peta seperti 'ok' di Golang dengan fungsi normal

icza
sumber
7

Tidak, tapi itu bagus karena Anda harus selalu menangani kesalahan Anda.

Ada beberapa teknik yang dapat Anda terapkan untuk menunda penanganan kesalahan, lihat Kesalahan adalah nilai oleh 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
}

Dalam contoh dari posting blog ini dia mengilustrasikan bagaimana Anda bisa membuat errWriterjenis yang menangguhkan penanganan kesalahan sampai Anda selesai menelepon write.

uang sampah
sumber
6

Ya ada.

Mengejutkan, ya? Anda bisa mendapatkan nilai tertentu dari beberapa pengembalian menggunakan mutefungsi sederhana :

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-

Perhatikan bagaimana Anda memilih nomor nilai seperti yang Anda lakukan dari slice / array dan kemudian jenis untuk mendapatkan nilai sebenarnya.

Anda dapat membaca lebih lanjut tentang ilmu di balik itu dari artikel ini . Penghargaan untuk penulis.

Kesarion
sumber
5

Tidak, Anda tidak dapat langsung mengakses nilai pertama.

Saya kira hack untuk ini akan mengembalikan array nilai bukan "item" dan "err", dan kemudian lakukan saja item, _ := Get(1)[0] tetapi saya tidak akan merekomendasikan ini.

Antimony
sumber
3

Bagaimana kalau begini?

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
sumber