Go语言精进之路:从新手到高手的编程思想、方法和技巧(1)
上QQ阅读APP看书,第一时间看更新

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中我们看到,要想做好代码中变量声明的一致性,需要明确要声明的变量是包级变量还是局部变量、是否要延迟初始化、是否接受默认类型、是否为分支控制变量,并结合聚类和就近原则。

078-1

图8-1 变量声明形式使用决策流程图