Go 语言中的变量作用域范围

Note

本文摘录自《Go语言趣学指南》第 4 章, 请访问 gpwgcn.com 以获取更多相关信息。

代码清单 4-6 展示的程序能够生成并显示一个随机的日期(这个日期也许就是我们启程去火星的日期)。 除此之外,这个程序还演示了 Go 中的几种不同的作用域,并阐明了在声明变量时考虑作用域的重要性。

代码清单 4-6 变量作用域规则: scope-rules.go

package main

import (
    "fmt"
    "math/rand"
)

var era = "AD"      // era 变量在整个包都是可用的。

func main() {
    year := 2018    // era 变量和 year 变量都处于作用域之内。

    switch month := rand.Intn(12) + 1; month {  // 变量 era 、year 和 month 都处于作用域之内。
    case 2:
        day := rand.Intn(28) + 1                // 变量 era 、year 、month 和 day 都处于作用域之内。
        fmt.Println(era, year, month, day)
    case 4, 6, 9, 11:
        day := rand.Intn(30) + 1                // 这两个 day 变量是全新声明的变量,跟上面声明的同名变量并不相同。
        fmt.Println(era, year, month, day)
    default:
        day := rand.Intn(31) + 1                // 这两个 day 变量是全新声明的变量,跟上面声明的同名变量并不相同。
        fmt.Println(era, year, month, day)
    }   // month 变量和 day 变量不再处于作用域之内。
}       // year 变量不再处于作用域之内。

因为 era 变量的声明位于 main 函数之外的 包作用域 中,所以它对于 main 包中的所有函数都是可见的。

Note

因为包作用域不允许使用简短声明, 所以我们无法在这个作用域中使用 era := "AD" 来进行声明。

year 变量只在 main 函数中可见。 如果包中还存在着其他函数,那么它们将会看见 era 变量,但是却无法看到 year 变量。 函数作用域 比包作用域要狭窄,它起始于 func 关键字,并终结于函数声明的右大括号。

month 变量在整个 switch 语句的任何位置都可见,不过一旦 switch 语句结束, month 就不再处于作用域之内了。 switch 语句的作用域始于 switch 关键字,并终结于 switch 语句的右大括号。

因为 switch 的每个 case 分支都拥有自己独立的作用域,所以三个分支分别拥有三个独立的 day 变量。 在每个分支结束之后,该分支声明的 day 变量将不再处于作用域之内。 switch 分支的作用域是唯一一种无需使用大括号标识的作用域。

代码清单 4-6 中的代码距离完美还有相当远的一段距离。 变量 monthday 狭窄的作用域导致 Println 重复出现了三次,这种代码重复可能会引发修改,并因此导致错误。 比如说,我们可能会决定不再在每个分支中都打印 era 变量,但是却忘记了修改某个分支。 在某些情况下,出现代码重复是正常的,但这种情况通常被认为是代码的 坏味道 ,需要谨慎地处理。

为了消除重复并简化代码,我们需要将代码清单 4-6 中的某些变量声明移动到范围更宽广的函数作用域中,使得这些变量可以在 switch 语句结束之后继续为程序所用。 为此,我们需要对代码实施 重构 ,也即是在不改变代码行为的基础上对代码进行修改。 重构得出的代码清单 4-7 跟之前的代码行为完全相同,它们都可以选取并打印出随机的日期。

代码清单 4-7 重构后的随机日期选取程序: random-date.go

package main

import (
    "fmt"
    "math/rand"
)

var era = "AD"

func main() {
    year := 2018
    month := rand.Intn(12) + 1
    daysInMonth := 31

    switch month {
    case 2:
        daysInMonth = 28
    case 4, 6, 9, 11:
        daysInMonth = 30
    }

    day := rand.Intn(daysInMonth) + 1
    fmt.Println(era, year, month, day)
}

尽管狭窄的作用域有助于减少脑力负担,但代码清单 4-6 的例子也表明了过分约束变量将 损害 代码的可读性。 在遇到这种问题的时候,我们应该根据具体情况逐步实施重构,直到代码的可读性能够满足我们的要求为止。