



有了包级变量的知识做铺垫,我们再来讲解局部变量就容易多了。与包级变量相比,局部变量多了一种短变量声明形式,这也是局部变量采用最多的一种声明形式。下面我们来详细看看。
1. 对于延迟初始化的局部变量声明,采用带有var关键字的声明形式
比如标准库strings包中byteReplacer的方法Replace中的变量buf:
// $GOROOT/src/strings/replace.go
func (r *byteReplacer) Replace(s string) string {
var buf []byte // 延迟分配
for i := 0; i < len(s); i++ {
b := s[i]
if r[b] != b {
if buf == nil {
buf = []byte(s)
}
buf[i] = r[b]
}
}
if buf == nil {
return s
}
return string(buf)
}
另一种常见的采用带var关键字声明形式的变量是error类型的变量err(将error类型变量实例命名为err也是Go的一个惯用法),尤其是当defer后接的闭包函数需要使用err判断函数/方法退出状态时。示例代码如下:
func Foo() {
var err error
defer func() {
if err != nil {
...
}
}()
err = Bar()
...
}
2. 对于声明且显式初始化的局部变量,建议使用短变量声明形式
短变量声明形式是局部变量最常用的声明形式,它遍布Go标准库代码。对于接受默认类型的变量,可以使用下面的形式:
a := 17 f := 3.14 s := "hello, gopher!"
对于不接受默认类型的变量,依然可以使用短变量声明形式,只是在“:=”右侧要进行显式转型:
a := int32(17)
f := float32(3.14)
s := []byte("hello, gopher!")
3. 尽量在分支控制时应用短变量声明形式
这应该是Go中短变量声明形式应用最广泛的场景了。在编写Go代码时,我们很少单独声明在分支控制语句中使用的变量,而是通过短变量声明形式将其与if、for等融合在一起,就像下面这样:
// $GOROOT/src/net/net.go
func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) {
// 笔者注:在if循环控制语句中使用短变量声明形式
if wv, ok := w.(buffersWriter); ok {
return wv.writeBuffers(v)
}
// 笔者注:在for条件控制语句中使用短变量声明形式
for _, b := range *v {
nb, err := w.Write(b)
n += int64(nb)
if err != nil {
v.consume(n)
return n, err
}
}
v.consume(n)
return n, nil
}
这样的应用方式体现出“就近原则”,让变量的作用域最小化了。
由于良好的函数/方法设计讲究的是“单一职责”,因此每个函数/方法规模都不大,很少需要应用var块来聚类声明局部变量。当然,如果你在声明局部变量时遇到适合聚类的应用场景,你也应该毫不犹豫地使用var块来声明多个局部变量。比如:
// $GOROOT/src/net/dial.go
func (r *Resolver) resolveAddrList(ctx context.Context, op, network,
addr string, hint Addr) (addrList, error) {
...
var (
tcp *TCPAddr
udp *UDPAddr
ip *IPAddr
wildcard bool
)
...
}
或是:
// $GOROOT/src/reflect/type.go
// 笔者注:这是一个非常长的函数,因此将所有var声明都聚合在函数的开始处了
func StructOf(fields []StructField) Type {
var (
hash = fnv1(0, []byte("struct {")...)
size uintptr
typalign uint8
comparable = true
hashable = true
methods []method
fs = make([]structField, len(fields))
repr = make([]byte, 0, 64)
fset = map[string]struct{}{}
hasPtr = false
hasGCProg = false
)
...
}
使用一致的变量声明是Go语言的一个最佳实践,我们用图8-1来对变量声明形式做个形象的小结。
从图8-1中我们看到,要想做好代码中变量声明的一致性,需要明确要声明的变量是包级变量还是局部变量、是否要延迟初始化、是否接受默认类型、是否为分支控制变量,并结合聚类和就近原则。
图8-1 变量声明形式使用决策流程图