返回结构体或错误的惯用方式是什么?

22

我有一个返回 Card(一个结构体类型)或错误的函数。

问题是,当发生错误时,如何从该函数返回? nil 对于结构体无效,而我的 Card 类型也没有有效的零值。

func canFail() (card Card, err error) {
    // return nil, errors.New("Not yet implemented"); // Fails
    return Card{Ace, Spades}, errors.New("not yet implemented"); // Works, but very ugly
}

我找到的唯一解决方法是使用*Card而不是Card,当出现错误时使其为nil或在没有错误发生时使其指向实际的Card,但这相当笨拙。

func canFail() (card *Card, err error) {
    return nil, errors.New("not yet implemented");
}

有更好的方法吗?

编辑:我找到了另一种方法,但不知道它是否符合惯用语或良好的风格。

func canFail() (card Card, err error) {
    return card, errors.New("not yet implemented")
}

由于card是一个具有名称的返回值,因此我可以在不初始化它的情况下使用它。它按照自己的方式被清零,我并不关心这一点,因为调用函数不应该使用这个值。

7个回答

20
func canFail() (card Card, err error) {
    return card, errors.New("not yet implemented")
}

我认为这个例子也可以。通常情况下,如果一个函数返回了一个错误,除非文档明确说明,否则不能指望其他的返回值有意义。因此,在这里返回一个可能没有意义的结构体值是可以接受的。


4
返回指针以便可以使用nil是相当习惯的。这有助于避免错误,并使得在不使用命名返回值时更容易进行早期返回。为了实现这一点,需要对代码进行适当的调整和优化。 - Jeremy Wall
2
虽然返回nil而不是指针确实很方便,但我不会仅因为这个原因而使用指针。返回非nil的错误已足以告诉您不要使用“card”中的数据。您应该检查错误而不是值。否则,返回错误就没有什么意义了... - weberc2

8
例如,
type Card struct {
}

func canFail() (card Card, err error) {
    return Card{}, errors.New("not yet implemented")
}

2
在这个示例中,返回值甚至不需要被命名。(你可能仍然希望它们被命名出其他的原因,或者你可能觉得函数没有名称的杂乱更清晰。) - Sonia
1
阅读问题和我的答案。这是一个存根函数,稍后将实现——空的“struct”和“尚未实现”的消息。由于我们不知道将使用什么代码来实现该函数,因此唯一安全的方法是无论以后使用什么代码,都返回定义良好的值。 - peterSO

3
func canFail() (card Card, err error) {
        if somethingWrong {
                err = errors.New("Not yet implemented")
                return
        }

        if foo {
                card = baz
                return
        }

        ... 

        // or 
        return Card{Ace, Spades}, nil
}

1
谢谢,这与我的最终解决方案在语义上非常接近,尽管在语法方面有些不同。 - Fabien

1

peterSO的答案最接近,但并不是我会使用的最好方案。我认为这是最好的:

func canFail() (Card, error) {
   return Card{}, errors.New("not yet implemented")
}

首先,它不是只为了使用nil而不使用指针来返回值。我认为这是一个很好的技巧,但除非你实际需要将struct作为指针(用于修改或其他原因),否则返回值更好。另外,我认为返回值不应该命名,除非你要利用它们,就像这样:
func canFail() (card Card, err error) {
   return
}

这是有问题的,原因有两个。首先,你并不总是处于可以简单地让返回值成为变量当前的值的情况下。其次,如果你有一个更大的函数,你将无法在更深层次上使用裸返回,因为你会得到变量遮蔽错误。

最后,使用Card{}而不是nilcard更加冗长,但它更好地传达了你正在做什么。如果你使用其中任何一个:

return
return card, err

没有上下文的情况下不清楚函数是否成功,而这个:

return Card{}, err

很明显该函数失败了。与原始类型使用相同的模式:
return false, err
return 0, err
return '\x00', err
return "", err
return []byte{}, err

https://github.com/golang/go/wiki/CodeReviewComments#pass-values


1
对我来说,我更喜欢你的第二个选项。
func canFail() (card *Card, err error) {
    return nil, errors.New("not yet implemented");
}

这样做可以确保当出现错误时,canFail() 的调用者无法使用 card,因为它是 nil。我们不能确保调用者会先检查错误。

0

如果您的函数在其签名中的行为与其他人的假设不同,例如:如果发生错误,我应该忽略它的值

就像任何io.Reader一样,它可能会返回n>0和一个error

那么,您应该简单地记录下来,向用户解释关于返回值和错误的内容应该如何考虑。

对于这种情况,更改签名,从而更改一般API关系,并不是正确的方法。

相反,您应该充分记录函数的行为。


0
作为返回结构体的可能替代方案,您可以考虑让调用者分配它并让函数设置参数。
func canFail(card *Card) (err error) {
    if someCondition {
        // set one property
        card.Suit = Diamond

        // set all at once
        *card = Card{Ace, Spade}
    } else {
        err = errors.New("something went wrong")
    }

    return
}

如果你不喜欢假装 Go 支持 C++ 风格的引用,那么你也应该检查 card 是否为 nil

https://play.golang.org/p/o-2TYwWCTL


请考虑早期返回。https://play.golang.org/p/fXG7H5OG6eH - user4466350

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接