Nhiều giá trị trong ngữ cảnh đơn giá trị


110

Do xử lý lỗi trong Go, tôi thường kết thúc với các hàm nhiều giá trị. Cho đến nay, cách tôi quản lý điều này rất lộn xộn và tôi đang tìm kiếm các phương pháp hay nhất để viết mã sạch hơn.

Giả sử tôi có chức năng sau:

type Item struct {
   Value int
   Name string
}

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

  return item, nil
}

Làm cách nào để tôi có thể gán một biến mới cho một item.Valuecách thanh lịch. Trước khi giới thiệu cách xử lý lỗi, hàm của tôi vừa trả về itemvà tôi chỉ cần thực hiện điều này:

val := Get(1).Value

Bây giờ tôi làm điều này:

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

Không có cách nào để truy cập trực tiếp vào biến được trả về đầu tiên?


3
itemthường sẽ niltrong trường hợp có lỗi. Nếu không kiểm tra lỗi trước, mã của bạn sẽ bị lỗi trong trường hợp đó.
Thomas

Câu trả lời:


83

Trong trường hợp hàm trả về nhiều giá trị, bạn không thể tham chiếu đến các trường hoặc phương thức của một giá trị cụ thể của kết quả khi gọi hàm.

Và nếu một trong số chúng là một error, thì đó là một lý do (đó là hàm có thể bị lỗi) và bạn không nên bỏ qua nó vì nếu bạn làm vậy, mã tiếp theo của bạn có thể cũng thất bại thảm hại (ví dụ: dẫn đến hoảng loạn thời gian chạy).

Tuy nhiên, có thể có những tình huống mà bạn biết mã sẽ không bị lỗi trong bất kỳ trường hợp nào. Trong những trường hợp này, bạn có thể cung cấp một hàm trợ giúp (hoặc phương thức) sẽ loại bỏerror (hoặc nâng cao một hoảng loạn chạy nếu nó vẫn xảy ra).
Đây có thể là trường hợp nếu bạn cung cấp các giá trị đầu vào cho một hàm từ mã và bạn biết chúng hoạt động.
Các ví dụ tuyệt vời về điều này là các gói templateregexp: nếu bạn cung cấp mẫu hoặc regexp hợp lệ tại thời điểm biên dịch, bạn có thể chắc chắn rằng chúng luôn có thể được phân tích cú pháp mà không có lỗi trong thời gian chạy. Vì lý do này, templategói cung cấp Must(t *Template, err error) *Templatechức năng và regexpgói cung cấp s vì mục đích sử dụng của chúng là nơi đầu vào được đảm bảo là hợp lệ.MustCompile(str string) *Regexp chức năng: chúng không trả vềerror

Ví dụ:

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

Quay lại trường hợp của bạn

NẾU bạn có thể chắc chắn Get()sẽ không sản xuất errorcho một số giá trị đầu vào nhất định, bạn có thể tạo một Must()hàm trợ giúp sẽ không trả về errornhưng gây hoảng loạn thời gian chạy nếu nó vẫn xảy ra:

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

Nhưng bạn không nên sử dụng điều này trong mọi trường hợp, chỉ khi bạn chắc chắn rằng nó thành công. Sử dụng:

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

Thay thế / Đơn giản hóa

Bạn thậm chí có thể đơn giản hóa nó hơn nữa nếu bạn kết hợp Get() cuộc gọi vào chức năng trợ giúp của mình, hãy gọi nó là MustGet:

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

Sử dụng:

val := MustGet(1).Value

Xem một số câu hỏi thú vị / liên quan:

cách phân tích cú pháp nhiều lần trả về trong golang

Trả lại bản đồ như 'ok' trong Golang trên các chức năng bình thường


7

Không, nhưng đó là một điều tốt vì bạn nên luôn xử lý lỗi của mình.

Có những kỹ thuật mà bạn có thể sử dụng để trì hoãn việc xử lý lỗi, hãy xem Lỗi là giá trị của 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
}

Trong ví dụ này từ bài đăng trên blog, anh ấy minh họa cách bạn có thể tạo một errWriterkiểu xác định việc xử lý lỗi cho đến khi bạn gọi xong write.


6

Có, có.

Ngạc nhiên hả? Bạn có thể nhận một giá trị cụ thể từ nhiều lần trả về bằng một mutehàm đơn giản :

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-

Lưu ý cách bạn chọn số giá trị giống như cách bạn làm từ một lát / mảng và sau đó chọn kiểu để nhận giá trị thực.

Bạn có thể đọc thêm về khoa học đằng sau đó từ bài báo này . Tín dụng cho tác giả.


5

Không, bạn không thể truy cập trực tiếp giá trị đầu tiên.

Tôi cho rằng một thủ thuật cho điều này sẽ là trả về một mảng giá trị thay vì "item" và "err", và sau đó chỉ cần thực hiện item, _ := Get(1)[0] nhưng tôi không khuyến nghị điều này.


3

Làm thế nào về cách này?

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]
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.