Go错误集锦 | 函数何时使用带参数名的返回值

2022年2月15日 460点热度 0人点赞 0条评论

01 具名返回值简介

在Go语言中定义方法或函数时,我们不仅可以给函数(或方法)的返回值指定返回类型,而且也可以指定返回参数的名字。如下函数就指定了返回值的名字:

func f(a int) (b int) {
    b = a
return
}

在这种使用方式中,返回值参数(这里是b)首先会被初始化成返回类型的零值(这里int的零值是0)。其次,在return语句中可以不加任何参数,默认会将同名变量b的值返回。


02 何时使用带参数名的返回值

那么,在什么场景下会推荐使用带参数名的返回值呢?首先看一个接口示例:

type locator interface {
getCoordinates(address string) (float32, float32, error)
}

该接口中包含一个根据地址获取经纬度坐标的函数。在返回值中有两个float32类型的值,分别是经度和纬度。那么通过接口的签名你能知道返回值中哪个参数是经度,哪个参数是纬度吗?一般情况下,第一个参数会是纬度,第二个参数会是经度,但最终我们需要通过具体的实现函数来确认。

如果在方法的返回值中加上参数名称,那么通过函数的签名就可以很容易的确认每个返回值的含义了。如下:

type locator interface {
getCoordinates(address string) (lat, lng float32, error)
}

那么,在具体的实现该接口的方法中也需要在返回值中指定参数名吗?

func (l loc) getCoordinates(address string) (lat, lng float32, error) {
    // ...
}

在这个示例中,因为有两个返回值类型是一样的,所以通过给返回值指定参数名可以提高代码的可读性,对于阅读代码的人来说就很容易知道哪个返回值是经度,哪个返回值是维度。

但在有的实现场景中,可以不用给返回值指定参数名,同样也不影响可读性。例如我们下面的代码,是将一个客户存储到数据库中的函数:

func StoreCustomer(customer Customer) (err error) {
    // ...
}

那么,在这里我们给返回值指定了一个参数名就没什么意义。因为通过error类型我们就知道返回值一定是一个错误类型的。所以,在这种场景下,返回值指定了参数名也不会提高可读性,就尽量不要指定参数值名称。因为在指定了参数名的情况下,该参数会被初始化成对应类型的零值,如果不小心就可能会产生副作用。

03 返回值中命名参数的陷阱 

在上面的示例中可以看到,在一些场景下,给返回值指定参数名称会提高可读性。但同时,返回值的参数值在函数一开始会被初始化成对应类型的零值。在业务逻辑中如果处理不当,就会造成错误。我们看下下面的这个例子:

func (l locator) 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
}

在该实现中,首先会通过validateAddress检查地址的合法性。然后会通过Context检查上层调用者是否因超时被取消。大家注意这里,如果ctx.Err()不等于nil,那么在返回err的时候,因为err没有被赋值,同时由于在返回值中指定了参数名被初始化成对应的零值nil,实际返回的err还是nil,不符合要返回具体错误的预期。解决方案应该如下:

if err := ctx.Err(); err != nil {
    return 0, 0, err
}

04 总结

给函数返回值指定具体的参数名时,在某些场景下可以提高可读性,但同时因为返回值的参数在函数调用时会首先被初始化成对应类型的零值,在具体的逻辑处理中,如果不小心也会调入陷阱。


欢迎关注「Go学堂」,让我们再次走入学堂


图片

28210Go错误集锦 | 函数何时使用带参数名的返回值

这个人很懒,什么都没留下

文章评论