近日,Uber 开源了其公司内部使用的《 Go 语言编程规范》。该指南是为了使代码库更易于管理,同时让工程师有效地使用 Go 语言特性。文档中详细描述了在 Uber 编写 Go 代码的各种注意事项,包括具体的“Dos and Don’ts of writing Go code at Uber”,也就是 Go 代码应该怎样写、不该怎样写。今天我们就来简单了解一下国外大厂都是如何来写代码的。行文仓促,错误之处,多多指正。
type S struct { data string } func(s S) Read() string { return s.data } func(s *S) Write(str string) { s.data = str } sVals := map[int]S{1: {"A"}} // You can only call Read using a value sVals[1].Read() // This will not compile: // sVals[1].Write("test") sPtrs := map[int]*S{1: {"A"}} // You can call both Read and Write using a pointer sPtrs[1].Read() sPtrs[1].Write("test")
相似的,pointer 也可以满足 interface 的要求,尽管 method 使用 value 作为 receiver。
type F interface { f() } type S1 struct{} func(s S1) f() {} type S2 struct{} func(s *S2) f() {} s1Val := S1{} s1Ptr := &S1{} s2Val := S2{} s2Ptr := &S2{} var i F i = s1Val i = s1Ptr i = s2Ptr // The following doesn't compile, since s2Val is a value, and there is no value receiver for f. // i = s2Val
func(d *Driver) SetTrips(trips []Trip) { d.trips = trips } trips := ... d1.SetTrips(trips) // Did you mean to modify d1.trips? trips[0] = ...
Good
1 2 3 4 5 6 7 8 9 10
func(d *Driver) SetTrips(trips []Trip) { d.trips = make([]Trip, len(trips)) copy(d.trips, trips) } trips := ... d1.SetTrips(trips) // We can now modify trips[0] without affecting d1.trips. trips[0] = ...
slice 和 map 作为返回值
当我们的函数返回 slice 或者 map 的时候,也要注意是不是直接返回了内部数据的引用到外部。
Bad
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
type Stats struct { sync.Mutex counters map[string]int } // Snapshot returns the current stats. func(s *Stats) Snapshot() map[string]int { s.Lock() defer s.Unlock() return s.counters } // snapshot is no longer protected by the lock! snapshot := stats.Snapshot()
Good
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
type Stats struct { sync.Mutex counters map[string]int } func(s *Stats) Snapshot() map[string]int { s.Lock() defer s.Unlock() result := make(map[string]int, len(s.counters)) for k, v := range s.counters { result[k] = v } return result } // Snapshot is now a copy. snapshot := stats.Snapshot()
2.5 使用 defer 做资源清理
建议使用 defer 去做资源清理工作,比如文件,锁等。
Bad
1 2 3 4 5 6 7 8 9 10 11 12 13
p.Lock() if p.count < 10 { p.Unlock() return p.count } p.count++ newCount := p.count p.Unlock() return newCount // easy to miss unlocks due to multiple returns
Good
1 2 3 4 5 6 7 8 9 10 11
p.Lock() defer p.Unlock() if p.count < 10 { return p.count } p.count++ return p.count // more readable
for _, v := range data { if v.F1 == 1 { v = process(v) if err := v.Call(); err == nil { v.Send() } else { return err } } else { log.Printf("Invalid v: %v", v) } }
Good
1 2 3 4 5 6 7 8 9 10 11 12
for _, v := range data { if v.F1 != 1 { log.Printf("Invalid v: %v", v) continue } v = process(v) if err := v.Call(); err != nil { return err } v.Send() }
var _s = F() // Since F already states that it returns a string, we don't need to specify // the type again. funcF()string { return"A" }
上面说的第二种两边数据类型不同的情况。
1 2 3 4 5 6 7 8
type myError struct{} func(myError) Error() string { return"error" } funcF() myError { return myError{} } var _e error = F() // F returns an object of type myError but we want error.
// foo.go const ( defaultPort = 8080 defaultUser = "user" ) // bar.go funcBar() { defaultPort := 9090 ... fmt.Println("Default port", defaultPort) // We will not see a compile error if the first line of // Bar() is deleted. }
type Region int const ( UnknownRegion Region = iota Local ) type Status int const ( StatusReady = iota + 1 StatusDone // Maybe we will have a StatusInProgress in the future. ) funcprintInfo(name string, region Region, status Status)
4.16 使用原生字符串,避免转义
Go 支持使用反引号,也就是 “`” 来表示原生字符串,在需要转义的场景下,我们应该尽量使用这种方案来替换。
很明显,使用 test table 的方式在代码逻辑扩展的时候,比如新增 test case,都会显得更加的清晰。
在命名方面,我们将 struct 的 slice 命名为 tests,同时每一个 test case 命名为 tt。而且,我们强烈建议通过 give 和 want 前缀来表示 test case 的 input 和 output 的值。
1 2 3 4 5 6 7 8 9 10 11
tests := []struct{ give string wantHost string wantPort string }{ // ... } for _, tt := range tests { // ... }
5.2 Functional Options
关于 functional options 简单来说就是通过类似闭包的方式来进行函数传参。
Bad
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// package db funcConnect( addr string, timeout time.Duration, caching bool, ) (*Connection, error) { // ... } // Timeout and caching must always be provided, // even if the user wants to use the default. db.Connect(addr, db.DefaultTimeout, db.DefaultCaching) db.Connect(addr, newTimeout, db.DefaultCaching) db.Connect(addr, db.DefaultTimeout, false/* caching */) db.Connect(addr, newTimeout, false/* caching */)