• 关于Go你不得不知道的小技巧


    Go 箴言

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

    Go 之禅

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

    代码

    使用 go fmt 格式化

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

    多个 if 语句可以折叠成 switch
    // NOT BAD
    if foo() {
        // ...
    } else if bar == baz {
        // ...
    } else {
        // ...
    }
    
    // BETTER
    switch {
    case foo():
        // ...
    case bar == baz:
        // ...
    default:
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    chan struct{} 来传递信号, chan bool 表达的不够清楚

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

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

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

    type Service struct {
        deleteCh chan struct{} // ok, if event than delete something.
    }
    
    • 1
    • 2
    • 3
    30 * time.Secondtime.Duration(30) * time.Second 更好

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

    // BAD
    delay := time.Second * 60 * 24 * 60
    
    // VERY BAD
    delay := 60 * time.Second * 60 * 24
    
    // GOOD
    delay := 24 * 60 * 60 * time.Second
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    time.Duration 代替 int64 + 变量名
    // BAD
    var delayMillis int64 = 15000
    
    // GOOD
    var delay time.Duration = 15 * time.Second
    
    • 1
    • 2
    • 3
    • 4
    • 5
    按类型分组 const 声明,按逻辑和/或类型分组 var
    // BAD
    const (
        foo = 1
        bar = 2
        message = "warn message"
    )
    
    // MOSTLY BAD
    const foo = 1
    const bar = 2
    const message = "warn message"
    
    // GOOD
    const (
        foo = 1
        bar = 2
    )
    
    const message = "warn message"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这个模式也适用于 var

    • ** 每个阻塞或者 IO 函数操作应该是可取消的或者至少是可超时的

    • ** 为整型常量值实现 Stringer 接口

    • ** 检查 defer 中的错误

      defer func() {
          err := ocp.Close()
          if err != nil {
              rerr = err
          }
      }()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • ** 不要在 checkErr 函数中使用 panic()os.Exit()

    • ** 仅仅在很特殊情况下才使用 panic, 你必须要去处理 error

    • ** 不要给枚举使用别名,因为这打破了类型安全

      package main
      type Status = int
      type Format = int // remove `=` to have type safety
    
      const A Status = 1
      const B Format = 1
    
      func main() {
          println(A == B)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • **

      如果你想省略返回参数,你最好表示出来

      • _ = f()f() 更好
    • **

      我们用 a := []T{} 来简单初始化 slice

    • **

      用 range 循环来进行数组或 slice 的迭代

      • for _, c := range a[3:7] {...}for i := 3; i < 7; i++ {...} 更好
    • **

      多行字符串用反引号(`)

    • **

      _ 来跳过不用的参数

      func f(a int, _ string) {}
    
    • 1
    • ** 如果你要比较时间戳,请使用 time.Beforetime.After ,不要使用 time.Sub 来获得 duration (持续时间),然后检查它的值。
    • ** 带有上下文的函数第一个参数名为 ctx,形如:func foo(ctx Context, ...)
    • ** 几个相同类型的参数定义可以用简短的方式来进行
      func f(a int, b int, s string, p string)
    
    • 1
      func f(a, b int, s, p string)
    
    • 1
    • ** 一个 slice 的零值是 nil

      ```
      var s []int
      fmt.Println(s, len(s), cap(s))
      if s == nil {
      fmt.Println("nil!")
      }
      // Output:
      // [] 0 0
      // nil!
      ```
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      var a []string
      b := []string{}
    
      fmt.Println(reflect.DeepEqual(a, []string{}))
      fmt.Println(reflect.DeepEqual(b, []string{}))
      // Output:
      // false
      // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • ** 不要将枚举类型<, >, <=>= 进行比较

      • 使用确定的值,不要像下面这样做:
      value := reflect.ValueOf(object)
      kind := value.Kind()
      if kind >= reflect.Chan && kind <= reflect.Slice {
        // ...
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • ** 用 %+v 来打印数据的比较全的信息

    • ** 注意空结构 struct{}

      func f1() {
        var a, b struct{}
        print(&a, "\n", &b, "\n") // Prints same address
        fmt.Println(&a == &b)     // Comparison returns false
      }
    
      func f2() {
        var a, b struct{}
        fmt.Printf("%p\n%p\n", &a, &b) // Again, same address
        fmt.Println(&a == &b)          // ...but the comparison returns true
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • **

      • 例如: errors.Wrap(err, "additional message to a given error")
    • **

      在 Go 里面要小心使用 range:

      • for i := range a and for i, v := range &a ,都不是 a 的副本
      • 但是 for i, v := range a 里面的就是 a 的副本
    • **

      从 map 读取一个不存在的 key 将不会 panic

      • value := map["no_key"] 将得到一个 0 值
      • value, ok := map["no_key"] 更好
    • **

      不要使用原始参数进行文件操作

      • 而不是一个八进制参数 os.MkdirAll(root, 0700)
      • 使用此类型的预定义常量 os.FileMode
    • **

      不要忘记为 iota 指定一种类型

        const (
          _ = iota
          testvar         // testvar 将是 int 类型
        )
    
    • 1
    • 2
    • 3
    • 4

    vs

        type myType int
        const (
          _ myType = iota
          testvar         // testvar 将是 myType 类型
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    不要在你不拥有的结构上使用 encoding/gob

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

    不要依赖于计算顺序,特别是在 return 语句中。
      // BAD
      return res, json.Unmarshal(b, &res)
    
      // GOOD
      err := json.Unmarshal(b, &res)
      return res, err
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    防止结构体字段用纯值方式初始化,添加 _ struct {} 字段:
    type Point struct {
      X, Y float64
      _    struct{} // to prevent unkeyed literals
    }
    
    • 1
    • 2
    • 3
    • 4

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

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

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

    为了防止结构比较,添加 func 类型的空字段
      type Point struct {
        _ [0]func() // unexported, zero-width non-comparable field
        X, Y float64
      }
    
    • 1
    • 2
    • 3
    • 4
    http.HandlerFunchttp.Handler 更好

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

    移动 defer 到顶部

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

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

    json:"id,string" 代替

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

    并发

    • ** 以线程安全的方式创建单例(只创建一次)的最好选择是 sync.Once

      • 不要用 flags, mutexes, channels or atomics
    • ** 永远不要使用 select{}, 省略通道, 等待信号

    • ** 不要关闭一个发送(写入)管道,应该由创建者关闭

      • 往一个关闭的 channel 写数据会引起 panic
    • ** math/rand 中的 func NewSource(seed int64) Source 不是并发安全的,默认的 lockedSource 是并发安全的。

    • ** 当你需要一个自定义类型的 atomic 值时,可以使用 atomic.Value

    性能

    • ** 不要省略 defer

      • 在大多数情况下 200ns 加速可以忽略不计
    • ** 总是关闭 http body defer r.Body.Close()

      • 除非你需要泄露 goroutine
    • ** 过滤但不分配新内存

      b := a[:0]
      for _, x := range a {
          if f(x) {
            b = append(b, x)
          }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 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被读完和关闭。
      res, _ := client.Do(req)
      io.Copy(ioutil.Discard, res.Body)
      defer res.Body.Close()
    
    • 1
    • 2
    • 3
    • ** 不要在循环中使用 defer,否则会导致内存泄露

      • 因为这些 defer 会不断地填满你的栈(内存)
    • ** 不要忘记停止 ticker, 除非你需要泄露 channel

      ticker := time.NewTicker(1 * time.Second)
      defer ticker.Stop()
    
    • 1
    • 2
    • ** 用自定义的 marshaler 去加速 marshaler 过程

      • 但是在使用它之前要进行定制!
      func (entry Entry) MarshalJSON() ([]byte, error) {
        buffer := bytes.NewBufferString("{")
        first := true
        for key, value := range entry {
            jsonValue, err := json.Marshal(value)
            if err != nil {
                return nil, err
            }
            if !first {
                buffer.WriteString(",")
            }
            first = false
            buffer.WriteString(key + ":" + string(jsonValue))
        }
        buffer.WriteString("}")
        return buffer.Bytes(), nil
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • **

      sync.Map 不是万能的,没有很强的理由就不要使用它。

    • **

      sync.Pool 中分配内存存储非指针数据

    • **

      为了隐藏逃生分析的指针,你可以小心使用这个函数::

      // noescape hides a pointer from escape analysis.  noescape is
      // the identity function but escape analysis doesn't think the
      // output depends on the input. noescape is inlined and currently
      // compiles down to zero instructions.
      //go:nosplit
      func noescape(p unsafe.Pointer) unsafe.Pointer {
        x := uintptr(p)
        return unsafe.Pointer(x ^ 0)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • **

      对于最快的原子交换,你可以使用这个 m := (*map[int]int)(atomic.LoadPointer(&ptr))

    • **

      如果执行许多顺序读取或写入操作,请使用缓冲 I/O

      • 减少系统调用次数
    • **

      有 2 种方法清空一个 map:

      • 重用 map 内存 (但是也要注意 m 的回收)
      for k := range m {
        delete(m, k)
      }
    
    • 1
    • 2
    • 3
    • 分配新的
      m = make(map[int]int)
    
    • 1

    构建

    • ** 用这个命令 go build -ldflags="-s -w" ... 去掉你的二进制文件

    • ** 拆分构建不同版本的简单方法

      • // +build integration 并且运行他们 go test -v --tags integration .
    • ** 最小的 Go Docker 镜像

    • ** run go format on CI and compare diff

      • 这将确保一切都是生成的和承诺的
    • ** 用最新的 Go 运行 Travis-CI,用 travis 1

    • ** 检查代码格式是否有错误 diff -u <(echo -n) <(gofmt -d .)

    测试

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

    • ** 多次运行你的基准测试可以避免噪音。

      • go test -test.bench=. -count=20

    工具

    • **

      快速替换 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 告诉我们为什么特定的模块在 go.mod 文件中。

    • **

      GOGC=off go build ... 应该会加快构建速度 source

    • **

      内存分析器每 512KB 记录一次分配。你能通过 GODEBUG 环境变量增加比例,来查看你的文件的更多详细信息。

    • **

      go mod why -m 告诉我们为什么特定的模块是在 go.mod 文件中。

    其他

    • ** dump goroutines
      go func() {
        sigs := make(chan os.Signal, 1)
        signal.Notify(sigs, syscall.SIGQUIT)
        buf := make([]byte, 1<<20)
        for {
          <-sigs
          stacklen := runtime.Stack(buf, true)
          log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n"  , buf[:stacklen])
        }
      }()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • ** 在编译期检查接口的实现

        var _ io.Reader = (*MyFastReader)(nil)
      
      • 1
    • ** len(nil) = 0

    • ** 匿名结构很酷

      var hits struct {
        sync.Mutex
        n int
      }
      hits.Lock()
      hits.n++
      hits.Unlock()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • **

      httputil.DumpRequest 是非常有用的东西,不要自己创建

    • **

      获得调用堆栈,我们可以使用 runtime.Caller

    • **

      要 marshal 任意的 JSON, 你可以 marshal 为 map[string]interface{}{}

    • **

      配置你的 CDPATH 以便你能在任何目录执行 cd github.com/golang/go

      • 添加这一行代码到 bashrc(或者其他类似的) export CDPATH=$CDPATH:$GOPATH/src
    • **

      从一个 slice 生成简单的随机元素

      • []string{"one", "two", "three"}[rand.Intn(3)]

    参考资料:
    https://github.com/cristaloleg/go-advice/blob/master/README_ZH.md

  • 相关阅读:
    MPP 架构在 OLAP 数据库的运用
    234 基于matlab的萤火虫算法多变量寻优
    OA项目之会议通知(查询&是否参会&反馈详情)
    WPF 控件专题 Menu 控件详解
    【.NET】简单使用Description特性
    arcgis中xy连线时出现多余的线
    Spring Framework 简介与起源
    肖sir__mysql之navicat安装__003
    JMM对正确同步的多线程程序的内存一致性的保证
    [PAT练级笔记] 28 Basic Level 1028 人口普查
  • 原文地址:https://blog.csdn.net/weixin_53795646/article/details/127955234