Golang 最佳实践

Google Go 语言最佳实践 | 中文版本

命名

避免重复

  • 函数或方法名中可以省略如下字段:
    • 输入输出类型
    • 方法接收器类型
    • 输入输出是否为指针
  • 包的名字
  • 方法接收器的名字
  • 参数传递的变量名称
  • 返回值类型的名称和类型

命名惯例

  • 返回一个对象,一般以对象名命名(避免加Get)
  • 动作一般会加动作前缀
  • 如果涉及到不同的类型,会把类型放到函数末尾
    • 函数主版本,则可以省略类型

测试助手包

  • 原包名 + test
  • 简单测试,桩直接使用Stub 即可
  • 多个测试桩,按照测试行为命名
  • 多个类型,多个测试桩,需要明确,类型+Stub,方法要明确
  • 测试中局部变量,命名要能区分出真实、测试类型

遮蔽

  • 避免作用域遮蔽原有的上下文变量;而在作用域外又重新使用的情况
  • 不使用和标准库的包名相同的变量

Util 包

  • 不使用 util、helper、common 类似的包名
  • 考虑调用时如何使用

包的大小

  • 文件应该足够内聚,以便维护者可以知道哪个文件包含了什么功能
  • 文件应该足够小,以便一旦有了它,就很容易找到

导入

proto,stub文件导入

  • 包重命名为 pb,
  • 若多个包,加前缀

导入顺序:

  • 标准库导入;
  • 普通项目导入;
  • pb等导入;
  • 副作用导入;
  • 分组导入

错误处理

错误消费端:

  • 诊断信息,返回给人类;
  • 维护者使用;
  • 终端用户解释

错误定义:

  • 是否需要结构;
  • 考虑添加你拥有但调用者、被调用者没有的信息

使用errgroup;

错误值定义:

  • 哨兵模式,直接判等;
  • errors.Is() 方式包含error;
  • 结构化error,提供status字段

错误信息,不冗余;

  • 适当添加额外的信息使错误信息更有用;
  • 一般使用 fmt.Errorf(“xxx %w”) %w 会包装参数中的err;需要提供细节的err 一般会这么使用;
  • 链式,一次只能wrap一个err
  • 屏蔽细节,使用 fmt.Errorf(“xxx %v”)

日志中输出错误

  • 敏感数据问题
  • 尽量少使用log.Error

程序初始化

  • 初始化错误,应该传播到main,调用 log.Exit 退出,解释如何修复

程序检查和panic;

  • 尽量少用panic,倾向于返回错误,而不是终止程序;
  • panic的传导可能会影响上下文状态;

何时用panic,

  • 对api的误用,报panic;
  • 调用链中有一个对应的recover,确保panic 不跨越包。
  • 命令后解析之前,不调用日志函数

文档

参数与配置:

  • 不是每个参数都需要文档

只描述易出错和不明显的字段和参数

context

  • 惯例:取消操作,返回ctx.Err(),不需要注释;
  • 例外情况进行注释

是否支持并发的注释

清理,标记是否有后续的清理

godoc 格式化

  • 段落之间空行
  • 测试文件包含可运行代码
  • 缩进行增加两个空格,格式化代码
  • 可运行的代码,更好的方式是提供Example Case,而非注释
  • 一行以大写字母开始,除括号和逗号外不含标点符号,后面是另一个段落,这样的行将被格式化为标题

经常使用的 err != nil  ,如果判断 err==nil 增加注释强调

变量申明

初始化,非零值初始化,尽量使用 :=

非指针零值

  • 用申明而不是赋值
  • 锁不可复制的结构体字段,使用值类类型,用零值初始化,指针传递
  • 复合类型,函数返回,最好返回指针

复合字面值

  • 已知的参数,可以用复合字面值声明值

尺寸提示

  • 备注,提示容量内容。比如slice,map等
  • 性能敏感型尤为注意

channel

  • 尽可能指向channel方向,防止误操作

函数参数列表

  • 减少参数的数量,抽离出多个函数,或者使用一个未导出的实现
  • 参数过多,可以使用一个struct作为入参,或者variadic 技术

不定长参数 variadic 

复杂的命令行交互界面

  • cobra
  • subcommands

测试

把测试留给Test 函数

  • 测试case 正交
  • 数据类似,使用表驱动的测试
  • 在Test函数中内联逻辑

设计可扩展的验证APIs

字符串链接

简单情况,首选 +

需要格式化,选择 fmt.Sprintf / fmt.Fprintf

零散的字符串,使用strings.Builder()

用 `` 构建多行常量字符串

0%