我们提到了为什么定义返回值参数在某些情况下可能很有用。然而,由于这些返回值参数被初始化为零值,如果我们不够小心,有时会导致细微的错误。本节将说明这种情况。
让我们使用从给定地址返回纬度和经度的方法来增强我们之前讨论的一个示例。当我们返回两个 float32
,我们将使用定义返回值参数来明确纬度和经度。此函数将首先验证给定地址,然后获取坐标。在此期间,它将检查输入上下文以确保它没有被取消,还要验证它的截止日期没有过期。
Note 我们将在 Misunderstanding Go contexts 中深入研究 Go 中的上下文概念。如果您还不熟悉上下文,让我们简单地说一下上下文可以携带取消信号或截止日期。我们可以通过调用
Err
方法并检查返回的错误是否为 nil 来检查这些。
这是 getCoordinates
方法的新实现。你能发现这段代码有什么问题吗?
func (l loc) getCoordinates(ctx context.Context, address string) (lat, lng float32, err error) {
isValid := l.validateAddress(address)
if !isValid {
return 0, 0, errors.New("invalid address")
}
if ctx.Err() != nil {
return 0, 0, err
}
// Get and return coordinates
}
乍一看,错误可能并不那么明显。在这里,if ctx.Err() != nil
作用域内返回的错误是 err
。然而,我们还没有给它赋值。它仍然分配给 error
类型的零值:nil。因此,此代码将始终返回 nil 错误。
此外,此代码可以编译,因为 err
由于定义返回值参数而被初始化为零值。如果不附加名称,我们将得到以下编译错误:
Unresolved reference 'err'
一种可能的解决方法是将 ctx.Err()
分配给 err
,如下所示:
if err := ctx.Err(); err != nil {
return 0, 0, err
}
我们不断返回 err
,但我们首先将其分配给 ctx.Err()
的结果。请注意,此示例中的 err
将隐藏结果变量。
Note 但是,它会打破我们不应该将裸返回和返回与参数混合的规则。在这种情况下,我们可能应该坚持第一个选项。请记住,使用定义返回值参数并不一定意味着使用裸返回。有时,我们可以只使用定义返回值参数来使定义更清晰。
另一种选择可能是使用裸返回语句:
if err = ctx.Err(); err != nil {
return
}
我们可以通过强调在某些情况下(例如,多次返回相同类型)来总结定义返回值参数,它可以提高代码的可读性并且在其他情况下非常方便。然而,我们必须记住,每个参数都被初始化为零值。正如我们在本节中所看到的,它可能会导致可能在阅读代码时不容易发现的细微错误。因此,让我们在使用定义返回值参数时保持谨慎以避免潜在的副作用。
在下一节中,我们将讨论 Go 开发人员在函数返回接口时常犯的错误。