译文 Go-advices 中文版本]

xiemengjun · 2020年02月11日 · 661 次阅读

Table of Contents generated with DocToc

(有些建议在 go-critic 中实现)

Go-advices 中文版本

代码

  • [ ] 使用 go fmt / gofmt 格式化你的代码, 让大家都开心
  • [ ] 多个 if 语句可以折叠成 switch
  • [ ] 用 chan struct{} 来传递信号, chan bool 表达的不够清楚
  • [ ] 30 * time.Secondtime.Duration(30) * time.Second 更好
  • [ ] 用 var foo time.Duration 代替 var fooMillis int64 会更好
  • [ ] 总是把 for-select 换成一个函数
  • [ ] 分组定义 const 类型声明和 var 逻辑类型声明
  • [ ] 每个阻塞或者 IO 函数操作应该是可取消的或者至少是可超时的
  • [ ] 为整型常量值实现 Stringer 接口
  • [ ] 用 defer 来检查你的错误
defer func() {
    err := ocp.Close()
    if err != nil {
        rerr = err
    }
}()
  • [ ] 任何 panic 都不要使用 checkErr 函数或者用 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.Beforetime.After ,不要使用 time.Sub 来获得 duration (持续时间),然后检查它的值。
  • [ ] 总是将上下文作为第一个参数传递给具有 ctx 名称的 func
  • [ ] 几个相同类型的参数定义可以用简短的方式来进行
func f(a int, b int, s string, p string)
func f(a, b int, s, p string)
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 {
  // ...
}
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
}
  • [ ] 包装错误: 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 指定一种类型

const (
  _ = iota
  testvar         // testvar 将是 int 类型
)

vs

type myType int
const (
  _ myType = iota
  testvar         // testvar 将是 myType 类型
)
// NOT CLEAR
return res, json.Unmarshal(b, &res)

// CLEAR
err := json.Unmarshal(b, &res)
return res, err
  • [ ] 为了防止结构比较,添加 func 类型的空字段
type Point struct {
  _ [0]func() // unexported, zero-width non-comparable field
  X, Y float64
}
  • [ ] http.HandlerFunchttp.Handler 更好
    • http.HandlerFunc 你仅需要一个 func,http.Handler 需要一个类型。
  • [ ] 移动 defer 到顶部
    • 代码可读性更好和在函数结束时有什么被调用也更清楚了。
  • [ ] JavaScript 解析整数为浮点数并且你的 int64 可能溢出
    • json:"id,string" 代替

并发

  • [ ] 以线程安全的方式创建一些东西的最好选择是 sync.Once
    • 不要用 flags, mutexes, channels or atomics
  • [ ] 永远不要使用 select{}, 省略通道, 等待信号
  • [ ] 不要在 channel 里关闭,这是它的创作者的责任。
    • 往一个关闭的 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
  • [ ] 过滤但不分配新内存
b := a[:0]
for _, x := range a {
  if f(x) {
      b = append(b, x)
  }
}
  • [ ] 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()
  • [ ] 不要在循环中使用 defer,否则会导致内存泄露
    • 'cause defers will grow your stack without the reason
  • [ ] 不要忘记停止 ticker, 除非你需要泄露 channel
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
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
}
// 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)
}
  • [ ] 对于最快的原子交换,你可以使用这个 m := (*map[int]int)(atomic.LoadPointer(&ptr))
  • [ ] 如果执行许多顺序读取或写入操作,请使用缓冲 I/O

    • 减少系统调用次数
  • [ ] 有 2 种方法清空一个 map:

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

模块

构建

测试

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

工具

  • [ ] 快速替换 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 环境变量增加比例,来查看你的文件的更多详细信息。

Misc

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])
  }
}()
var hits struct {
  sync.Mutex
  n int
}
hits.Lock()
hits.n++
hits.Unlock()
  • [ ] 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)]
更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册