变量声明

初始化

为了保持一致性,当用非零值初始化一个新的变量时,首选:=而不是var

// Good:i := 42// Bad:var i = 42

非指针式零值

下面的声明使用零值

// Good:var (    coords Point    magic  [4]byte    primes []int)

当你想要传递一个空值以供以后使用时,你应该使用零值声明。使用带有显式初始化的复合字面会显得很笨重:

// Bad:var (    coords = Point{X: 0, Y: 0}    magic  = [4]byte{0, 0, 0, 0}    primes = []int(nil))

零值声明的一个常见应用是当使用一个变量作为反序列化时的输出:

// Good:var coords Pointif err := json.Unmarshal(data, &coords); err != nil {

在你的结构体中,如果你需要一个不得复制的锁或其他字段,可以将其设为值类型以利用零值初始化。这确实意味着,现在必须通过指针而不是值来传递包含的类型。该类型的方法必须采用指针接收器。

// Good:type Counter struct {    // This field does not have to be "*sync.Mutex". However,    // users must now pass *Counter objects between themselves, not Counter.    mu   sync.Mutex    data map[string]int64}// Note this must be a pointer receiver to prevent copying.func (c *Counter) IncrementBy(name string, n int64)

对复合体(如结构体和数组)的局部变量使用值类型是可以接受的,即使它们包含这种不可复制的字段。然而,如果复合体是由函数返回的,或者如果对它的所有访问最终都需要获取一个地址,那么最好在一开始就将变量声明为指针类型。同样地,protobufs 也应该被声明为指针类型。

// Good:func NewCounter(name string) *Counter {    c := new(Counter) // "&Counter{}" is also fine.    registerCounter(name, c)    return c}var myMsg = new(pb.Bar) // or "&pb.Bar{}".

这是因为*pb.Something满足proto.Messagepb.Something不满足。

// Bad:func NewCounter(name string) *Counter {    var c Counter    registerCounter(name, &c)    return &c}var myMsg = pb.Bar{}

重要的是: Map 类型在被修改之前必须明确地初始化。然而,从零值 Map 中读取是完全可以的。 对于 map 和 slice 类型,如果代码对性能特别敏感,并且你事先知道大小,请参见size hints部分。

复合字面量

以下是复合字面量的声明:

// Good:var (    coords   = Point{X: x, Y: y}    magic    = [4]byte{'I', 'W', 'A', 'D'}    primes   = []int{2, 3, 5, 7, 11}    captains = map[string]string{"Kirk": "James Tiberius", "Picard": "Jean-Luc"})

当你知道初始元素或成员时,你应该使用复合字面量来声明一个值。

相比之下,与[零值初始化]相比,使用复合字面量声明空或无成员值可能会在视觉上产生噪音

当你需要一个指向零值的指针时,你有两个选择:空复合字面和new。两者都很好,但是new关键字可以提醒读者,如果需要一个非零值,这个复合字面量将不起作用:

// Good:var (  buf = new(bytes.Buffer) // non-empty Buffers are initialized with constructors.  msg = new(pb.Message) // non-empty proto messages are initialized with builders or by setting fields one by one.)

size 提示

以下是利用 size 提示来预分配容量的声明方式:

// Good:var (    // Preferred buffer size for target filesystem: st_blksize.    buf = make([]byte, 131072)    // Typically process up to 8-10 elements per run (16 is a safe assumption).    q = make([]Node, 0, 16)    // Each shard processes shardSize (typically 32000+) elements.    seen = make(map[string]bool, shardSize))

根据对代码及其集成的经验分析,对创建性能敏感和资源高效的代码,size 提示和预分配是重要的步骤。

大多数代码不需要 size 提示或预分配,可以允许运行时根据需要增长 slice 或 map。当最终大小已知时,预分配是可以接受的(例如,在 slice 或 map 之间转换时),但这不是一个可读性要求,而且在少数情况下可能不值得这样做。

警告:预先分配比你需要的更多的内存,会在队列中浪费内存,甚至损害性能。如有疑问,请参阅GoTip #3: Benchmarking Go Code并默认为零初始化复合字面量声明

Channel 方向

尽可能地指定Channel 方向

// Good:// sum computes the sum of all of the values. It reads from the channel until// the channel is closed.func sum(values <-chan int) int {    // ...}

这可以防止在没有规范的情况下可能出现的随意编码错误。

// Bad:func sum(values chan int) (out int) {    for v := range values {        out += v    }    // values must already be closed for this code to be reachable, which means    // a second close triggers a panic.    close(values)}

当方向被指定时,编译器会捕捉到像这样的简单错误。它还有助于向类型传达一种所有权的措施。 也请看 Bryan Mills 的演讲 “重新思考经典的并发模式”。PPT链接 视频链接