Table of Contents generated with DocToc

Go 箴言

  • 不要通过共享内存进行通信,通过通信共享内存
  • 并发不是并行
  • 管道用于协调;互斥量(锁)用于同步
  • 接口越大,抽象就越弱
  • 利用好零值
  • 空接口 interface{} 没有任何类型约束
  • Gofmt 的风格不是人们最喜欢的,但 gofmt 是每个人的最爱
  • 允许一点点重复比引入一点点依赖更好
  • 系统调用必须始终使用构建标记进行保护
  • 必须始终使用构建标记保护 Cgo
  • Cgo 不是 Go
  • 使用标准库的 unsafe 包,不能保证能如期运行
  • 清晰比聪明更好
  • 反射永远不清晰
  • 错误是值
  • 不要只检查错误,还要优雅地处理它们
  • 设计架构,命名组件,(文档)记录细节
  • 文档是供用户使用的
  • 不要(在生产环境)使用 panic()

Author: Rob Pike See more: https://go-proverbs.github.io/

Go 之禅

  • 每个 package 实现单一的目的
  • 显式处理错误
  • 尽早返回,而不是使用深嵌套
  • 让调用者处理并发(带来的问题)
  • 在启动一个 goroutine 时,需要知道何时它会停止
  • 避免 package 级别的状态
  • 简单很重要
  • 编写测试以锁定 package API 的行为
  • 如果你觉得慢,先编写 benchmark 来证明
  • 适度是一种美德
  • 可维护性

Author: Dave Cheney See more: https://the-zen-of-go.netlify.com/

代码

使用 go fmt 格式化

让团队一起使用官方的 Go 格式工具,不要重新发明轮子。 尝试减少代码复杂度。 这将帮助所有人使代码易于阅读。

多个 if 语句可以折叠成 switch

  1. // NOT BAD
  2. if foo() {
  3. // ...
  4. } else if bar == baz {
  5. // ...
  6. } else {
  7. // ...
  8. }
  9. // BETTER
  10. switch {
  11. case foo():
  12. // ...
  13. case bar == baz:
  14. // ...
  15. default:
  16. // ...
  17. }

chan struct{} 来传递信号, chan bool 表达的不够清楚

当你在结构中看到 chan bool 的定义时,有时不容易理解如何使用该值,例如:

  1. type Service struct {
  2. deleteCh chan bool // what does this bool mean?
  3. }

但是我们可以将其改为明确的 chan struct {} 来使其更清楚:我们不在乎值(它始终是 struct {}),我们关心可能发生的事件,例如:

  1. type Service struct {
  2. deleteCh chan struct{} // ok, if event than delete something.
  3. }

30 * time.Secondtime.Duration(30) * time.Second 更好

你不需要将无类型的常量包装成类型,编译器会找出来。
另外最好将常量移到第一位:

  1. // BAD
  2. delay := time.Second * 60 * 24 * 60
  3. // VERY BAD
  4. delay := 60 * time.Second * 60 * 24
  5. // GOOD
  6. delay := 24 * 60 * 60 * time.Second

time.Duration 代替 int64 + 变量名

  1. // BAD
  2. var delayMillis int64 = 15000
  3. // GOOD
  4. var delay time.Duration = 15 * time.Second

按类型分组 const 声明,按逻辑和 / 或类型分组 var

  1. // BAD
  2. const (
  3. foo = 1
  4. bar = 2
  5. message = "warn message"
  6. )
  7. // MOSTLY BAD
  8. const foo = 1
  9. const bar = 2
  10. const message = "warn message"
  11. // GOOD
  12. const (
  13. foo = 1
  14. bar = 2
  15. )
  16. const message = "warn message"

这个模式也适用于 var

  1. defer func() {
  2. err := ocp.Close()
  3. if err != nil {
  4. rerr = err
  5. }
  6. }()
  • 不要在 checkErr 函数中使用 panic()os.Exit()
  • 仅仅在很特殊情况下才使用 panic, 你必须要去处理 error
  • 不要给枚举使用别名,因为这打破了类型安全
  1. package main
  2. type Status = int
  3. type Format = int // remove `=` to have type safety
  4. const A Status = 1
  5. const B Format = 1
  6. func main() {
  7. println(A == B)
  8. }
  • 如果你想省略返回参数,你最好表示出来
    • _ = f()f() 更好
  • 我们用 a := []T{} 来简单初始化 slice
  • 用 range 循环来进行数组或 slice 的迭代
    • for _, c := range a[3:7] {...}for i := 3; i <7; i++ {...} 更好
  • 多行字符串用反引号 (`)
  • _ 来跳过不用的参数
  1. func f(a int, _ string) {}
  • 如果你要比较时间戳,请使用 time.Beforetime.After ,不要使用 time.Sub 来获得 duration (持续时间),然后检查它的值。
  • 带有上下文的函数第一个参数名为 ctx,形如:func foo(ctx Context, ...)
  • 几个相同类型的参数定义可以用简短的方式来进行
  1. func f(a int, b int, s string, p string)
  1. func f(a, b int, s, p string)
  1. var s []int
  2. fmt.Println(s, len(s), cap(s))
  3. if s == nil {
  4. fmt.Println("nil!")
  5. }
  6. // Output:
  7. // [] 0 0
  8. // nil!
  1. var a []string
  2. b := []string{}
  3. fmt.Println(reflect.DeepEqual(a, []string{}))
  4. fmt.Println(reflect.DeepEqual(b, []string{}))
  5. // Output:
  6. // false
  7. // true
  • 不要将枚举类型与 <, >, <=>= 进行比较
    • 使用确定的值,不要像下面这样做:
  1. value := reflect.ValueOf(object)
  2. kind := value.Kind()
  3. if kind >= reflect.Chan && kind <= reflect.Slice {
  4. // ...
  5. }
  1. func f1() {
  2. var a, b struct{}
  3. print(&a, "\n", &b, "\n") // Prints same address
  4. fmt.Println(&a == &b) // Comparison returns false
  5. }
  6. func f2() {
  7. var a, b struct{}
  8. fmt.Printf("%p\n%p\n", &a, &b) // Again, same address
  9. fmt.Println(&a == &b) // ...but the comparison returns true
  10. }
  • 包装错误: http://github.com/pkg/errors
    • 例如: errors.Wrap(err, "additional message to a given error")
  • 在 Go 里面要小心使用 range:
  • 从 map 读取一个不存在的 key 将不会 panic
    • value := map["no_key"] 将得到一个 0 值
    • value, ok := map["no_key"] 更好
  • 不要使用原始参数进行文件操作
    • 而不是一个八进制参数 os.MkdirAll(root, 0700)
    • 使用此类型的预定义常量 os.FileMode
  • 不要忘记为 iota 指定一种类型
  1. const (
  2. _ = iota
  3. testvar // testvar 将是 int 类型
  4. )

vs

  1. type myType int
  2. const (
  3. _ myType = iota
  4. testvar // testvar 将是 myType 类型
  5. )

不要在你不拥有的结构上使用 encoding/gob

在某些时候,结构可能会改变,而你可能会错过这一点。因此,这可能会导致很难找到 bug。

不要依赖于计算顺序,特别是在 return 语句中。

  1. // BAD
  2. return res, json.Unmarshal(b, &res)
  3. // GOOD
  4. err := json.Unmarshal(b, &res)
  5. return res, err

防止结构体字段用纯值方式初始化,添加 _ struct {} 字段:

  1. type Point struct {
  2. X, Y float64
  3. _ struct{} // to prevent unkeyed literals
  4. }

对于 Point {X:1,Y:1} 都可以,但是对于 Point {1,1} 则会出现编译错误:

  1. ./file.go:1:11: too few values in Point literal

当在你所有的结构体中添加了 _ struct{} 后,使用 go vet 命令进行检查,(原来声明的方式)就会提示没有足够的参数。

为了防止结构比较,添加 func 类型的空字段

  1. type Point struct {
  2. _ [0]func() // unexported, zero-width non-comparable field
  3. X, Y float64
  4. }

http.HandlerFunchttp.Handler 更好

http.HandlerFunc 你仅需要一个 func,http.Handler 需要一个类型。

移动 defer 到顶部

这可以提高代码可读性并明确函数结束时调用了什么。

JavaScript 解析整数为浮点数并且你的 int64 可能溢出

json:"id,string" 代替

  1. type Request struct {
  2. ID int64 `json:"id,string"`
  3. }

并发

  • 以线程安全的方式创建单例(只创建一次)的最好选择是 sync.Once
    • 不要用 flags, mutexes, channels or atomics
  • 永远不要使用 select{}, 省略通道, 等待信号
  • 不要关闭一个发送(写入)管道,应该由创建者关闭
    • 往一个关闭的 channel 写数据会引起 panic
  • math/rand 中的 func NewSource(seed int64) Source 不是并发安全的,默认的 lockedSource 是并发安全的, see issue: https://github.com/golang/go/issues/3611
  • 当你需要一个自定义类型的 atomic 值时,可以使用 atomic.Value

性能

  • 不要省略 defer
    • 在大多数情况下 200ns 加速可以忽略不计
  • 总是关闭 http body defer r.Body.Close()
    • 除非你需要泄露 goroutine
  • 过滤但不分配新内存
  1. b := a[:0]
  2. for _, x := range a {
  3. if f(x) {
  4. b = append(b, x)
  5. }
  6. }

为了帮助编译器删除绑定检查,请参见此模式 _ = b [7]

  • time.Time 有指针字段 time.Location 并且这对 go GC 不好
    • 只有使用了大量的 time.Time 才(对性能)有意义,否则用 timestamp 代替
  • regexp.MustCompileregexp.Compile 更好
    • 在大多数情况下,你的正则表达式是不可变的,所以你最好在 func init 中初始化它
  • 请勿在你的热点代码中过度使用 fmt.Sprintf. 由于维护接口的缓冲池和动态调度,它是很昂贵的。
    • 如果你正在使用 fmt.Sprintf("%s%s", var1, var2), 考虑使用简单的字符串连接。
    • 如果你正在使用 fmt.Sprintf("%x", var), 考虑使用 hex.EncodeToString or strconv.FormatInt(var, 16)
  • 如果你不需要用它,可以考虑丢弃它,例如io.Copy(ioutil.Discard, resp.Body)
    • HTTP 客户端的传输不会重用连接,直到 body 被读完和关闭。
  1. res, _ := client.Do(req)
  2. io.Copy(ioutil.Discard, res.Body)
  3. defer res.Body.Close()
  • 不要在循环中使用 defer,否则会导致内存泄露
    • 因为这些 defer 会不断地填满你的栈(内存)
  • 不要忘记停止 ticker, 除非你需要泄露 channel
  1. ticker := time.NewTicker(1 * time.Second)
  2. defer ticker.Stop()
  1. func (entry Entry) MarshalJSON() ([]byte, error) {
  2. buffer := bytes.NewBufferString("{")
  3. first := true
  4. for key, value := range entry {
  5. jsonValue, err := json.Marshal(value)
  6. if err != nil {
  7. return nil, err
  8. }
  9. if !first {
  10. buffer.WriteString(",")
  11. }
  12. first = false
  13. buffer.WriteString(key + ":" + string(jsonValue))
  14. }
  15. buffer.WriteString("}")
  16. return buffer.Bytes(), nil
  17. }
  1. // noescape hides a pointer from escape analysis. noescape is
  2. // the identity function but escape analysis doesn't think the
  3. // output depends on the input. noescape is inlined and currently
  4. // compiles down to zero instructions.
  5. //go:nosplit
  6. func noescape(p unsafe.Pointer) unsafe.Pointer {
  7. x := uintptr(p)
  8. return unsafe.Pointer(x ^ 0)
  9. }
  • 对于最快的原子交换,你可以使用这个 m := (*map[int]int)(atomic.LoadPointer(&ptr))
  • 如果执行许多顺序读取或写入操作,请使用缓冲 I/O
    • 减少系统调用次数
  • 有 2 种方法清空一个 map:
    • 重用 map 内存 (但是也要注意 m 的回收)
  1. for k := range m {
  2. delete(m, k)
  3. }
  • 分配新的

模块

构建

测试

  • 测试名称 package_testpackage 要好
  • go test -short 允许减少要运行的测试数
  1. func TestSomething(t *testing.T) {
  2. if testing.Short() {
  3. t.Skip("skipping test in short mode.")
  4. }
  5. }
  • 根据系统架构跳过测试
  1. if runtime.GOARM == "arm" {
  2. t.Skip("this doesn't work under ARM")
  3. }

工具

  • 快速替换 gofmt -w -l -r "panic(err) -> log.Error(err)" .
  • go list 允许找到所有直接和传递的依赖关系
    • go list -f '{{.Imports}}' package
    • go list -f '{{.Deps}}' package
  • 对于快速基准比较,我们有一个 benchstat 工具。
  • go-critic linter 从这个文件中强制执行几条建议
  • go mod why -m <module> 告诉我们为什么特定的模块在 go.mod 文件中。
  • GOGC=off go build ... 应该会加快构建速度 source
  • 内存分析器每 512KB 记录一次分配。你能通过 GODEBUG 环境变量增加比例,来查看你的文件的更多详细信息。
  • go mod why -m <module> 告诉我们为什么特定的模块是在 go.mod 文件中。

其他

  1. go func() {
  2. sigs := make(chan os.Signal, 1)
  3. signal.Notify(sigs, syscall.SIGQUIT)
  4. buf := make([]byte, 1<<20)
  5. for {
  6. <-sigs
  7. stacklen := runtime.Stack(buf, true)
  8. log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n" , buf[:stacklen])
  9. }
  10. }()
  1. var hits struct {
  2. sync.Mutex
  3. n int
  4. }
  5. hits.Lock()
  6. hits.n++
  7. hits.Unlock()