为了保持一致性,当用非零值初始化一个新的变量时,首选:=
而不是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.Message
而pb.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 提示来预分配容量的声明方式:
// 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 方向。
// 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链接 视频链接。