代码块与作用域:如何保证变量不会被遮蔽?
- 什么是变量遮蔽呢?
package main import ( "fmt" "github.com/google/uuid" "github.com/sirupsen/logrus" ) func main() { fmt.Println("hello, world") logrus.Println(uuid.NewString()) var ( a int = 10 b int8 = 6 s string = "hello" c rune = 'A' t bool = true ) var res int = a * int(b) fmt.Println(res) fmt.Println(s) var res2 rune = rune(res) / c fmt.Println(res2) fmt.Println(t) var a1 = 11 fmt.Println("a1 =", a1) foo(5) fmt.Println("after calling foo, a1 =", a1) } func foo(n int) { a1 := 1 a1 += n }
- 输出如下:
hello, world time="2023-08-17T16:04:43+08:00" level=info msg=712d41da-1efa-42eb-91f0-5a3ee017cc0e 60 hello 0 true a1 = 11 after calling foo, a1 = 11
- 在这段代码中,函数 foo 调用前后,包级变量 a1 的值都没有发生变化。这是因为,虽然 foo 函数中也使用了变量 a1,但是 foo 函数中的变量 a1 遮蔽了外面的包级变量 a1,这使得包级变量 a1 没有参与到 foo 函数的逻辑中,所以就没有发生变化了。
- 变量遮蔽是 Go 开发人员在日常开发工作中最容易犯的编码错误之一,它低级又不容易查找,常常会让你陷入漫长的调试过程。
代码块与作用域
- Go 语言中的代码块是包裹在一对大括号内部的声明和语句序列,如果一对大括号内部没有任何声明或其他语句,我们就把它叫做空代码块。Go 代码块支持嵌套,我们可以在一个代码块中嵌入多个层次的代码块。
- 每个标识符都有自己的作用域,而一个标识符的作用域就是指这个标识符在被声明后可以被有效使用的源码区域。
- 声明于外层代码块中的标识符,其作用域包括所有内层代码块。
- 包顶层声明中的常量、类型、变量或函数(不包括方法)对应的标识符的作用域是包代码块。
- 当一个包 A 导入另外一个包 B 后,包 A 仅可以使用被导入包包 B 中的导出标识符(Exported Identifier)。
- 在函数 / 方法体中,标识符作用域划分原则更为简单,因为我们可以凭借肉眼可见的、配对的大括号来明确界定一个标识符的作用域范围。
- 函数内部声明的常量或变量对应的标识符的作用域范围开始于常量或变量声明语句的末尾,并终止于其最内部的那个包含块的末尾。
避免变量遮蔽
- Go 官方提供了 go vet 工具可以用于对 Go 源码做一系列静态检查,在 Go 1.14 版以前默认支持变量遮蔽检查,Go 1.14 版之后,变量遮蔽检查的插件就需要我们单独安装了:
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
。- 一旦安装成功,我们就可以通过 go vet 扫描代码并检查这里面有没有变量遮蔽的问题了。
- 执行检查的命令如下:
go vet -vettool=$(which shadow) -strict xxx.go
。 - 工具确实可以辅助检测,但也不是万能的,不能穷尽找出代码中的所有问题,所以还是要深入理解代码块与作用域的概念,尽可能在日常编码时就主动规避掉所有遮蔽问题。