Effective Go

代码规范

  • 代码一致用 gofmt 格式化,这样就保持风格一致
  • 注释文档
    • 每个 package 都应该有文档,简单的包文档也可以很简单,一两行足以
    • 第一行概要介绍包的作用
    • 如果是一堆常量定义(例如 Error),注释可以一笔带过(liqiang 注:这个有点流于形式了?)
  • package
    • package 名称应该简短,就一个小写单词即可,不应该带下划线(_)以及 mixedCaps 格式
    • 命名无需过长:Long names don’t automatically make things more readable. A helpful doc comment can often be more valuable than an extra long name.
      • eg:once.Do(setup) better than once.DoOrWaitUntilDone(setup)

语言特性

  • 条件语句
    • 如果 if 的条件流程里面最后会 return,那么 else 关键词就不应该出现,他是多余的,直接在 if 段后面接上就可以了
    • := 如果左边的变量在前面已经定义过了,合不合理看情况:
      • 如果左边的变量全都在之前定义过了,那么会报错
      • 如果左边的变量部分定义过了,部分没定义,那么是合法的,并且,定义过的变量不会重新定义一遍,而是对之前的变量重新赋值
    • for
      • 如果你只需要 slice 的下标 或者 map 的 key,那么你只需要进行: for key := range var
      • 如果只需要值,那就需要忽略前一部分了:for _, v := range var
      • 边遍历 map 的 key,边删除 key 是合法的
      • for 遍历 string 的单位是 rune,所以中文也是可以正确处理的
    • switch
      • Go 的 switch 可以带表达式,也可以不带表达式
      • 不带表达式时,从上到下遍历 case,如果 case 值为 true,执行对应的语句
      • case 支持逗号分隔的多个匹配值
  • defer 如果调用的是一个函数 / 方法,那么值是在调用 defer 的时候先计算好的,而不是在运行函数内容的时候再计算
  • new 只是创建了一个空白的结构,并没有初始化内存,对于一些结构体来说,空白是有意义的,例如 Mutex,空白表示未加锁,所以可以直接使用;
  • make 只能用于 Array/Slice/Map,它是初始化了内存的,例如初始化了数组的长度是 100
  • 数组
    • Go 的数组是传值的,赋值 / 传参一个 Array 不是传的指针,是会对数组的值进行拷贝
    • 数组的 size 也是类型的一部分,[10] int 和 [20] int 是不同的两个类型,不能直接赋值
    • 数组有个比 slice 好的地方在于他的内存是固定的,所以在多重数组的情况下,可以直接使用 array [1][2]
  • map 和 slice 都是传指针的,可以修改内容
  • 如果 map 取值,key 不存在,那么返回的是 value 的零值
  • delete 删除不存在的 map key 是合法的
  • 常量只能是 数字、多个字符(runes)、字符串和 bool 类型
  • 常量是在编译时初始化的,包括函数中定义的常量,所以不能包含运行时的信息
  • 变量是在运行时初始化
  • interface
    • 如果 interface{} 的类型断言失败,将会产生一个运行时错误
    • If a type exists only to implement an interface and will never have exported methods beyond that interface, the constructor should return an interface value rather than the implementing type.
    • 确保一个结构体已经实现了一个 interface 可以这个写:var _ json.Marshaler = (*RawMessage)(nil)
  • 嵌套结构体
    • When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one.
    • Embedding types introduces the problem of name conflicts but the rules to resolve them are simple. First, a field or method X hides any other item X in a more deeply nested part of the type.
    • if the same name appears at the same nesting level, it is usually an error;
    • However, if the duplicate name is never mentioned in the program outside the type definition, it is OK.

并发

  • Do not communicate by sharing memory; instead, share memory by communicating.

Thanos Coding Style Guide

开发 / Review

  • 可靠性

    • CloseWithErrCapture

      1. [root@liqiang.io]# cat runutil.go
      2. ...
      3. // CloseWithErrCapture runs function and on error return error by argument including the given error (usually
      4. // from caller function).
      5. func CloseWithErrCapture(err *error, closer io.Closer, format string, a ...interface{}) {
      6. merr := errutil.MultiError{}
      7. merr.Add(*err)
      8. merr.Add(errors.Wrapf(closer.Close(), format, a...))
      9. *err = merr.Err()
      10. }
    • 不要使用 panic,如果有依赖用到了,别忘了用 recover 来处理,如果可以,放弃这个依赖吧;
    • 不要在不同的块作用域使用相同的变量名字(if/for 内外)
  • 性能

    • 预先分配好 slice 和 map 的大小
    • 重用 slice 的底层数组,避免重新分配
      • messages = messages[:0] 可以重置 slice
  • 可读性

    • 接口应该尽可能小(1-3 个方法)
    • 避免 shallow function(减少不必要的抽象)
    • 如果明确知道一个函数返回的错误无伤大雅,可以用 _ = 忽略
    • 如果一个变量只使用一次,那么不用赋值变量了,直接在用的地方初始化就好了
    • 函数 / 方法的参数,要么全在一行,要么每个参数一行
    • 结构化日志应该结构化一些,不要所有信息都塞在一条日志文本中

测试

  • 使用表驱动测试,这样更易于阅读
  • 不要依赖实时时间做比较,请使用相对时间
  • 请使用 linters
    • go vet
    • golangci-lint
      • deadcode
      • errcheck
      • goconst
      • goimports
      • gosimple
      • govet
      • ineffassign
      • staticcheck
      • structcheck
      • typecheck
      • unused
      • varcheck
  • 不要 print,用 logger

Ref