乐趣区

关于go:Go常见错误第11篇意外的变量隐藏variable-shadowing

前言

这是 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.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

这段代码逻辑分 3 步:

  • 首先定义了一个变量client
  • 在前面的代码逻辑里,依据不同状况创立不同的 client
  • 最初应用赋值后的 client 做业务操作

然而,咱们要留神到,在 if/else 里对 client 变量赋值时,应用了:=

这个会间接创立一个新的局部变量 client,而不是对咱们最开始定义的client 变量进行赋值,这就是 variable shadowing 景象。

这段代码带来的问题就是咱们最开始定义的变量 client 的值会是nil,不合乎咱们的预期。

那咱们应该怎么写代码,能力对咱们最开始定义的 client 变量赋值呢?有以下 2 种解决方案。

解决方案 1

var client *http.Client
if tracing {c, err := createClientWithTracing()
    if err != nil {return err}
    client = c
} else {// Same logic}

在 if/else 里定义一个长期变量 c,而后把c 赋值给变量client

解决方案 2

var client *http.Client
var err error
if 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 shadowing

    go 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/shadow
11-variable-shadowing/main.go:9:6: declaration of "i" shadows declaration at line 8
$ shadow main.go
11-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/…
退出移动版