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: // ... }
用 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) {}
1
** 如果你要比较时间戳,请使用 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。