Go语言中常见100问题-#1 小心变量遮蔽

小心变量遮蔽#

变量的作用域是指一个变量可以被引用的地方 / 范围。换句话说,就是应用程序的一个区域,在该区域内变量是有效的,超出该区域便无效。在 Go 语言中,在代码块中声明的变量可以在内部代码块中重新声明,这种使用方法称为变量遮蔽 / 隐藏,注意在使用时要非常小心,否则很容易出现常见的错误。下面通过一个具体的程序例子说明变量遮蔽 / 隐藏导致的问题,程序中将以两种不同的方式创建 HTTP 客户端,具体采用哪种方式依赖于变量 tracing 的值。


var client *http.Client

if tracing {

client, err := createClientWithTracing()

if err != nil {

return err

}

log.Println(client)

} else {

client, err := createDefaultClient()

if err != nil {

return err

}

log.Println(client)

}

// Use client

上面的程序首先定义了一个客户端变量 client, 然后在两个内部代码块中使用短变量声明运算符 (:=) 赋值,虽然赋值给的变量也是 client,但是它与外面的 client 不是同一个,因此,执行上述程序外部的 client 始终为 nil.

NOTE: 上面的代码可以编译通过,因为内部赋值的 client 变量在 log.Println 中使用到了,否则的话,将出现编译错误,提示 client(内部的)声明但未使用。

如何修复上面代码中存在的问题呢?有两种不同的方法。方法一是在内部代码块中使用一个临时变量保存 client,此临时变量名不要使用 client,然后再将临时变量值赋值给 client, 实现代码如下。这里先将结果保存在临时变量 c 中,c 的作用域在 if 块中,最后再将 c 赋值给客户端变量 client.


var client *http.Client

if tracing {

c, err := createClientWithTracing()

if err != nil {

return err

}

client = c

} else {

// Same logic

}

方法二是使用赋值运算符 (=) 将创建结果直接分配给客户端变量 client,但是需要创建一个错误变量,因为赋值运算符(=)对已声明的变量才能使用。然后直接将创建结果分配给 client,实现如下。


var client *http.Client

var err error

if tracing {

client, err = createClientWithTracing()

if err != nil {

return err

}

} else {

// Same logic

}

上述两种方法都是正确的,主要区别在于方法二种只执行了一个赋值操作,阅读起来可能更容易。此外,使用方法二,可以在 if/else 语句之后统一对错误进行处理。


if tracing {

client, err = createClientWithTracing()

} else {

client, err = createDefaultClient()

}

if err != nil {

// Common error handling

}

总结,在内部代码块中重新声明变量时,会产生变量遮蔽 / 隐藏,通过前面的例子可以看到这种做法很容易出错。所以在编码中,注重代码品味,尽量不要犯变量遮蔽 / 隐藏问题。虽然有时重用现有变量会非常方便,例如在用 err 表示错误时。但是,总体来说,我们应该谨慎小心,否则很容易出现问题,像本文举的例子,接收到值的变量不是我们预期的变量。

本作品采用《CC 协议》,转载必须注明作者和本文链接