go|青训营笔记

40 阅读4分钟

使用 go fmt 格式化

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

多个 if 语句可以折叠成 switch

// NOT BAD
if foo() {
    // ...
} else if bar == baz {
    // ...
} else {
    // ...
}

// BETTER
switch {
case foo():
    // ...
case bar == baz:
    // ...
default:
    // ...
}
复制代码

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

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

type Service struct {
    deleteCh chan bool // what does this bool mean? 
}
复制代码

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

type Service struct {
    deleteCh chan struct{} // ok, if event than delete something.
}
复制代码

30 * time.Second 比 time.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
复制代码

用 time.Duration 代替 int64 + 变量名

// BAD
var delayMillis int64 = 15000

// GOOD
var delay time.Duration = 15 * time.Second
复制代码

按类型分组 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"
复制代码

这个模式也适用于 var

  • ** 每个阻塞或者 IO 函数操作应该是可取消的或者至少是可超时的
  • ** 为整型常量值实现 Stringer 接口
  • ** 检查 defer 中的错误
  defer func() {
      err := ocp.Close()
      if err != nil {
          rerr = err
      }
  }()
复制代码
  • ** 不要在 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)
  }
复制代码
  • **

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

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

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

  • **

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

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

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

  • **

    用 _ 来跳过不用的参数

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

    ```
    var s []int
    fmt.Println(s, len(s), cap(s))
    if s == nil {
    fmt.Println("nil!")
    }
    // Output:
    // [] 0 0
    // nil!
    ```
    复制代码
    
  var a []string
  b := []string{}

  fmt.Println(reflect.DeepEqual(a, []string{}))
  fmt.Println(reflect.DeepEqual(b, []string{}))
  // Output:
  // false
  // true
复制代码
  • ** 不要将枚举类型与 <><= 和 >= 进行比较

    • 使用确定的值,不要像下面这样做:
  value := reflect.ValueOf(object)
  kind := value.Kind()
  if kind >= reflect.Chan && kind <= reflect.Slice {
    // ...
  }
复制代码
  • ** 用 %+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
  }
复制代码
  • **

    • 例如: 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 类型
    )
复制代码

vs

    type myType int
    const (
      _ myType = iota
      testvar         // testvar 将是 myType 类型
    )
复制代码

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

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

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

  // BAD
  return res, json.Unmarshal(b, &res)

  // GOOD
  err := json.Unmarshal(b, &res)
  return res, err
复制代码

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

type Point struct {
  X, Y float64
  _    struct{} // to prevent unkeyed literals
}
复制代码

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

./file.go:1:11: too few values in Point literal
复制代码

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

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

  type Point struct {
    _ [0]func() // unexported, zero-width non-comparable field
    X, Y float64
  }
复制代码

http.HandlerFunc 比 http.Handler 更好

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

移动 defer 到顶部

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