前言
这是Go常见谬误系列的第11篇:Go语言中意外的变量遮蔽。素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。
本文波及的源代码全副开源在:Go常见谬误源代码,欢送大家关注公众号,及时获取本系列最新更新。
什么是变量遮蔽
变量遮蔽的英文原词是 variable shadowing,咱们来看看维基百科上的定义:
In computer programming, variable shadowing occurs when a variable declared within a certain scope) (decision block, method, or inner class) has the same name as a variable declared in an outer scope. At the level of identifiers) (names, rather than variables), this is known as name masking. This outer variable is said to be shadowed by the inner variable, while the inner identifier is said to mask the outer identifier. This can lead to confusion, as it may be unclear which variable subsequent uses of the shadowed variable name refer to, which depends on the name resolution) rules of the language.
简略来说,如果某个作用域里申明了一个变量,同时在这个作用域的外层作用域又有一个雷同名字的变量,就叫variable shadowing(变量遮蔽)。
func test() { i := -100 for i := 0; i < 10; i++ { fmt.Println(i) } fmt.Println(i)}
比方下面这段代码,在for循环外面和里面都有一个变量i
。
for循环外面fmt.Println(i)
用到的变量i
是for循环外面定义的变量i
,for循环里面的i
在for循环外面是不可见的,被遮蔽了。
常见谬误
对于上面这段代码,大家思考下,看看会有什么问题:
var client *http.Clientif 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
这段代码逻辑分3步:
- 首先定义了一个变量
client
- 在前面的代码逻辑里,依据不同状况创立不同的client
- 最初应用赋值后的client做业务操作
然而,咱们要留神到,在if/else里对client
变量赋值时,应用了:=
。
这个会间接创立一个新的局部变量client
,而不是对咱们最开始定义的client
变量进行赋值,这就是variable shadowing景象。
这段代码带来的问题就是咱们最开始定义的变量client
的值会是nil
,不合乎咱们的预期。
那咱们应该怎么写代码,能力对咱们最开始定义的client
变量赋值呢?有以下2种解决方案。
解决方案1
var client *http.Clientif tracing { c, err := createClientWithTracing() if err != nil { return err } client = c} else { // Same logic}
在if/else里定义一个长期变量c
,而后把c
赋值给变量client
。
解决方案2
var client *http.Clientvar err errorif tracing { client, err = createClientWithTracing() if err != nil { return err }} else { // Same logic}
间接先把error
变量提前定义好,在if/else里间接用=
做赋值,而不必:=
。
计划区别
下面这2种计划其实都能够满足业务需要。我集体比拟举荐计划2,次要理由如下:
- 代码会更精简,只须要间接对最终用到的变量做一次赋值即可。计划1里要做2次赋值,先赋值给长期变量
c
,再赋值给变量client
。 能够对error对立解决。不须要在if/else里对返回的error做判断,计划2里咱们能够间接在if/else里面对error做判断和解决,代码示例如下:
if tracing { client, err = createClientWithTracing()} else { client, err = createDefaultClient()}if err != nil { // Common error handling}
如何主动发现variable shadowing
靠人肉去排查还是容易脱漏的,Go工具链里有一个shadow
命令能够帮忙咱们排查代码里潜在的variable shadowing问题。
第一步,装置
shadow
命令go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
第二步,应用
shadow
查看代码里是否有variable shadowinggo vet -vettool=$(which shadow)
比方,我查看后的后果如下:
$ go vet -vettool=$(which shadow)# example.com/shadow./main.go:9:6: declaration of "i" shadows declaration at line 8
此外,shadow命令也能够独自应用,不须要联合go vet
。shadow前面须要带上package名称或者.go源代码文件名。
$ shadow example.com/shadow11-variable-shadowing/main.go:9:6: declaration of "i" shadows declaration at line 8$ shadow main.go11-variable-shadowing/main.go:9:6: declaration of "i" shadows declaration at line 8
总结
- 遇到variable shadowing的状况,咱们须要小心,避免出现上述例子里的状况。
- 能够联合
shadow
工具做variable shadowing的自动检测。
举荐浏览
- Go面试题系列,看看你会几题?
- Go十大常见谬误第1篇:未知枚举值
- Go十大常见谬误第2篇:benchmark性能测试的坑
- Go十大常见谬误第3篇:go指针的性能问题和内存逃逸
- Go十大常见谬误第4篇:break操作的注意事项
- Go十大常见谬误第5篇:Go语言Error治理
- Go十大常见谬误第6篇:slice初始化常犯的谬误
- Go十大常见谬误第7篇:不应用-race选项做并发竞争检测
- Go十大常见谬误第8篇:并发编程中Context应用常见谬误
- Go十大常见谬误第9篇:应用文件名称作为函数输出
- Go十大常见谬误第10篇:Goroutine和循环变量一起应用的坑
开源地址
文章和示例代码开源在GitHub: Go语言高级、中级和高级教程。
公众号:coding进阶。关注公众号能够获取最新Go面试题和技术栈。
集体网站:Jincheng's Blog。
知乎:无忌。
福利
我为大家整顿了一份后端开发学习材料礼包,蕴含编程语言入门到进阶常识(Go、C++、Python)、后端开发技术栈、面试题等。
关注公众号「coding进阶」,发送音讯 backend 支付材料礼包,这份材料会不定期更新,退出我感觉有价值的材料。
发送音讯「进群」,和同行一起交流学习,答疑解惑。
References
- https://livebook.manning.com/...