golang最佳实践

177 阅读3分钟

1.readable

else处理和happypath

避免else return

bad example

func absoluteNumber(x int)int{
    if x >= 0{
        return x
    }else{
        return -x
    }
}

good example

func absoluteNumber(x int)int{
    if x >= 0{
        return x
    }
    return -x
}

保持happypath的缩进

bad example

func aFunc() error{
    err := doSometing()
    if err == nil{
        err := doAnotherThing()
        if err == nil{
            return nil // happy path
        }
        return err
    }
    return err
}

good example

func aFunc() error{
    if err := doSometing();err != nil{
        return err
    }
    if err := doAnotherThing(); err != nil{
        return err
    }
    return nil
}

去除没有必要的 else return

bad example

var a int
if flag {
    a = 1
}else {
    a = -1
}

good example

a := -1
if flag {
    a = 1
}

init()

init 函数是什么

golang 中提前定义好的一个函数,写在 init 函数内部的代码块会被最提前执行

执行顺序:

  1. 初始化所有的 import 包
  2. 初始所有当前包中的变量声明
  3. 执行 init()

使用 init()的问题

  • 可读性变差: 不知道 init 函数有提前处理的逻辑,在查看代码的时候容易忽略这一块内容
  • 导入一个包的时候,包中的 init 函数有可能会产生一些不可预料的副作用

bad example

func init(){
    c = buildClient()
}
// 编写测试代码,或者在开发环境执行测试用例的时候会直接跑这部分代码,如果这部分代码没有对其它环境进行适配,就存在报错的风险

good example

func InitClient(){
    c = buildClient()
}
// server.go
func main(){
    xxx.InitClient()
}

bad example

type MyVar struct{...}
var defaultVar MyVar

func init(){
    defaultVar = MyVar{...}
}
// 变量 defaultVar 不好 mock

good example

var defaultVar createVar()

func createVar() MyVar{
    return MyVar{...}
}

使用 init() 注意

  • 不要有很强的依赖关系
  • 不要操作访问全局变量 or 环境变量
  • 避免依赖 init()函数之间的执行顺序
  • 不要在 init()函数中使用 I/O 操作

注释

go 语言有两种注释风格

  1. /* */进行注释
  2. 通过 // 注释
// Bad example
printInfo("foo", true, true)
// good example
printInfo("foo", true/* isLocal*/, true/* done */)

  • 每个包中都应该有一个包注释
    • 小包情况下: 在package foo的 foo.go 中进行注释
    • 大包情况下: 在 doc.go 中进行注释
  • 关于每个导出的定义
  • 避免一些显然的注释

注释原则: 是 what 而不是 how

2. robust 可靠性

panic

产生 panic 的两种情况

  1. 程序运行时爆出来的不可以简单继续的一个uncover 的 error
  2. 一个developer error 如空指针

处理 panic 的方式

  1. 使用 defer 方法进行 recover
  2. 在方法开始的时候定义 defer
  3. 多个 defer 执行是按照声明的逆序进行执行的

error

优雅处理 error return err --> return fmt.Errorf("context: %w", err) 通过 errors.Is(err, io.EOF) 来确定是否是 error

3. efficient

pointer

一个存储地址的变量

使用 pointer 的场景 Good

  • 方法修改 参数/receiver的时候
  • 有大量的数据, 通过 pointer 来避免值传递
  • 其它方法都使用 pointer [代码一致性] Bad
  • 通过 pointer 指向一个 interface
  • 方法没有设定他的 参数/receiver的时候
  • 需要传递的数据量很小

deprecation

单元测试

  1. 给deprecation的方法添加单元测试
  2. 在deprecation的方法中调用新的代码

进行 mark

image.png