หลายค่าในบริบทค่าเดียว


110

เนื่องจากข้อผิดพลาดในการจัดการใน Go ฉันมักจะจบลงด้วยฟังก์ชันหลายค่า จนถึงตอนนี้วิธีที่ฉันจัดการมันยุ่งมากและฉันกำลังมองหาแนวทางปฏิบัติที่ดีที่สุดในการเขียนโค้ดที่สะอาดกว่า

สมมติว่าฉันมีฟังก์ชันต่อไปนี้:

type Item struct {
   Value int
   Name string
}

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

  return item, nil
}

ฉันจะกำหนดตัวแปรใหม่ให้สวยitem.Valueหรูได้อย่างไร ก่อนที่จะแนะนำการจัดการข้อผิดพลาดฟังก์ชันของฉันเพิ่งกลับมาitemและฉันสามารถทำได้:

val := Get(1).Value

ตอนนี้ฉันทำสิ่งนี้:

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

ไม่มีวิธีเข้าถึงตัวแปรที่ส่งคืนแรกโดยตรงหรือไม่?


3
itemโดยทั่วไปจะเป็นnilกรณีที่เกิดข้อผิดพลาด หากไม่ตรวจสอบข้อผิดพลาดก่อนรหัสของคุณจะผิดพลาดในกรณีนั้น
โทมัส

คำตอบ:


83

ในกรณีของฟังก์ชันส่งคืนหลายค่าคุณไม่สามารถอ้างถึงเขตข้อมูลหรือวิธีการของค่าเฉพาะของผลลัพธ์เมื่อเรียกใช้ฟังก์ชัน

และถ้าหนึ่งในนั้นเป็นerrorมันก็มีเหตุผล (ซึ่งฟังก์ชันนี้อาจล้มเหลว) และคุณไม่ควรข้ามเพราะถ้าคุณทำเช่นนั้นรหัสที่ตามมาของคุณอาจล้มเหลวอย่างน่าสังเวช (เช่นส่งผลให้รันไทม์ตื่นตระหนก)

อย่างไรก็ตามอาจมีสถานการณ์ที่คุณทราบว่ารหัสจะไม่ล้มเหลวในทุกกรณี ในกรณีเหล่านี้คุณสามารถจัดเตรียมฟังก์ชันตัวช่วย (หรือวิธีการ) ซึ่งจะยกเลิกerror(หรือทำให้รันไทม์ตื่นตระหนกหากยังคงเกิดขึ้น)
กรณีนี้อาจเกิดขึ้นได้หากคุณระบุค่าอินพุตสำหรับฟังก์ชันจากโค้ดและคุณรู้ว่ามันใช้งานได้
ตัวอย่างที่ดีของสิ่งนี้คือtemplateและregexpแพ็กเกจ: หากคุณระบุเทมเพลตหรือ regexp ที่ถูกต้องในเวลาคอมไพล์คุณสามารถมั่นใจได้ว่าสามารถแยกวิเคราะห์ได้ตลอดเวลาโดยไม่มีข้อผิดพลาดในขณะรันไทม์ ด้วยเหตุนี้templateแพ็กเกจจึงมีMust(t *Template, err error) *Templateฟังก์ชันและregexpแพ็กเกจให้MustCompile(str string) *Regexpฟังก์ชัน: พวกเขาไม่ส่งคืนerrors เนื่องจากวัตถุประสงค์การใช้งานเป็นที่ที่รับประกันว่าอินพุตถูกต้อง

ตัวอย่าง:

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

กลับไปที่กรณีของคุณ

หากคุณมั่นใจได้ว่าGet()จะไม่สร้างerrorค่าอินพุตบางค่าคุณสามารถสร้างMust()ฟังก์ชันตัวช่วยซึ่งจะไม่คืนค่าerrorแต่เพิ่มความตื่นตระหนกของรันไทม์หากยังคงเกิดขึ้น:

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

แต่คุณไม่ควรใช้สิ่งนี้ในทุกกรณีเมื่อคุณแน่ใจแล้ว การใช้งาน:

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

ทางเลือก / การทำให้เข้าใจง่าย

คุณสามารถทำให้มันง่ายขึ้นได้อีกหากคุณรวมการGet()โทรเข้าในฟังก์ชันตัวช่วยของคุณขอเรียกว่าMustGet:

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

การใช้งาน:

val := MustGet(1).Value

ดูคำถามที่น่าสนใจ / เกี่ยวข้อง:

วิธีแยกวิเคราะห์ผลตอบแทนหลายรายการใน golang

กลับแผนที่เช่น 'ok' ใน Golang ในฟังก์ชันปกติ


7

ไม่ แต่นั่นเป็นสิ่งที่ดีเนื่องจากคุณควรจัดการกับข้อผิดพลาดอยู่เสมอ

มีเทคนิคที่คุณสามารถใช้เพื่อเลื่อนการจัดการข้อผิดพลาดได้โปรดดูที่ข้อผิดพลาดเป็นค่าของ 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
}

ในตัวอย่างนี้จากบล็อกโพสต์ที่เขาแสดงให้เห็นถึงวิธีการที่คุณสามารถสร้างชนิดที่จัดการข้อผิดพลาดคล้อยตามจนคุณจะทำโทรerrWriterwrite


6

ใช่มี.

น่าแปลกใจใช่มั้ย? คุณสามารถรับค่าเฉพาะจากการส่งคืนหลายรายการโดยใช้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-

สังเกตว่าคุณเลือกตัวเลขค่าเช่นเดียวกับที่คุณทำจากสไลซ์ / อาร์เรย์แล้วเลือกประเภทเพื่อรับค่าจริง

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับวิทยาศาสตร์ที่อยู่เบื้องหลังว่าจากบทความนี้ เครดิตผู้แต่ง.


5

ไม่คุณไม่สามารถเข้าถึงค่าแรกได้โดยตรง

ฉันคิดว่าการแฮ็กสำหรับสิ่งนี้จะส่งคืนอาร์เรย์ของค่าแทน "item" และ "err" จากนั้นก็ทำ item, _ := Get(1)[0] แต่ฉันไม่แนะนำสิ่งนี้


3

มาทางนี้ได้อย่างไร?

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]
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.