Golang 最佳实践
命名
避免重复
- 函数或方法名中可以省略如下字段:
- 输入输出类型
- 方法接收器类型
- 输入输出是否为指针
- 包的名字
- 方法接收器的名字
- 参数传递的变量名称
- 返回值类型的名称和类型
命名惯例
- 返回一个对象,一般以对象名命名(避免加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函数中内联逻辑