代碼塊與作用域:如何保證變量不會被遮蔽?
- 什么是變量遮蔽呢?
package mainimport ("fmt""github.com/google/uuid""github.com/sirupsen/logrus" )func main() {fmt.Println("hello, world")logrus.Println(uuid.NewString())var (a int = 10b int8 = 6s 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) / cfmt.Println(res2)fmt.Println(t)var a1 = 11fmt.Println("a1 =", a1)foo(5)fmt.Println("after calling foo, a1 =", a1) }func foo(n int) {a1 := 1a1 += 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
。 - 工具確實可以輔助檢測,但也不是萬能的,不能窮盡找出代碼中的所有問題,所以還是要深入理解代碼塊與作用域的概念,盡可能在日常編碼時就主動規避掉所有遮蔽問題。