8.2 局部变量的声明形式
有了包级变量的知识做铺垫,我们再来讲解局部变量就容易多了。与包级变量相比,局部变量多了一种短变量声明形式,这也是局部变量采用最多的一种声明形式。下面我们来详细看看。
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 变量声明形式使用决策流程图