后端学习

Go 箴言

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

Go 之禅

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

多个 if 语句可以折叠成 switch

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

    // BETTER
    switch {
    case foo():
        // ...
    case bar == baz:
        // ...
    default:
        // ...
    }
            

常量&配置

    常量统一到一处去,不要在函数里直接用魔法值
    埋点上报的名称统一到常量处去,方便整理监控
    缓存的key统一到常量处去,方便修改与整理
    异步协程一定要保证recovery panic

日志

    如果最底层打印了error日志,上层函数可以不用打印
    少用defer, 很多时候加个代理、包装下函数,往往有奇效
    如果不知道如何打日志,可以无脑遵循这几点(尽量要把所有的参数&结果都打印出来,这样才好排查问题):
    函数开始处: logger.CtxInfo(ctx, "[xxx] start, params=%v")
    函数执行中间处,多了其余计算的临时变量: logger.CtxInfo(ctx, "[xxx] run step xxx, params=%v")
    函数执行中间处,调用其余函数报错了,打出error: logger.CtxError(ctx, "[xxx] exec xxx fail, err=%v")
    函数执行结束处,执行失败了返回了error: logger.CtxError(ctx, "[xxx] fail, err=%v")
    函数执行结束处,执行成功了: logger.CtxInfo(ctx, "[xxx] success, result=%v")

断言判断

    判断2个error要用errors.Is(err, xxxErr)的方式,不然会判断失败
    结构体里的slice,map等,要用指针不然修改不了值,除非是只读的map和slice

判断指针或值是否为空

    func IsNil(v interface{}) bool {
        type xface struct { // golang的interface{}内部结构
        x uintptr
        data unsafe.Pointer
        }
        // var ptr *int64
        // var ptrPtr interface{} = ptr
        // fmt.Println(ptrPtr == nil) false, 所以不能只判断v==nil, 得看指针里的值是否为nil
        return (*xface)(unsafe.Pointer(&v)).data == nil
    }

循环依赖快速检测工具

go install github.com/elza2/go-cyclic@latest
go-cyclic run --dir ./

用 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。

不要依赖于计算顺序,特别是在 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
    }
    

移动 defer 到顶部

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

并发

    以线程安全的方式创建单例(只创建一次)的最好选择是 sync.Once
    不要用 flags, mutexes, channels or atomics
     永远不要使用 select{}, 省略通道, 等待信号
     不要关闭一个发送(写入)管道,应该由创建者关闭
    往一个关闭的 channel 写数据会引起 panic
     math/rand 中的 func NewSource(seed int64) Source 不是并发安全的,默认的 lockedSource 是并发安全的, see issue: https://github.com/golang/go/issues/3611
    更多: https://golang.org/pkg/math/rand/
     当你需要一个自定义类型的 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)
        }
    }

构建

    用这个命令 go build -ldflags="-s -w" ... 去掉你的二进制文件
    拆分构建不同版本的简单方法
   用 // +build integration 并且运行他们 go test -v --tags integration .
    最小的 Go Docker 镜像
   https://twitter.com/bbrodriges/status/873414658178396160
   CGO_ENABLED=0 go build -ldflags="-s -w" app.go && tar C app | docker import - myimage:latest
    run go format on CI and compare diff
   这将确保一切都是生成的和承诺的
    用最新的 Go 运行 Travis-CI,用 travis 1
   了解更多:https://github.com/travis-ci/travis-build/blob/master/public/version-aliases/go.json
    检查代码格式是否有错误 diff -u <(echo -n) <(gofmt -d .)
粤ICP备2024213434号-1