【青训营】Go的高质量编程
Go的高质量编程
本文内容总结自字节跳动青年训练营 第五届 后端组
什么是高质量?
- 各种边界条件是否完备
- 异常情况能正常处理,稳定性有保障
- 易读易维护
Go语言开发者Dave Cheney指出,编程需要遵循以下原则:
简单性
- 消除多余的复杂性,以简单清晰的逻辑编写代码
- 无法理解的代码意味着无法修复和改进
可读性
- 代码是给人看的
- 编写可维护代码的第一步是确保代码可读
生产力
- 团队整体工作效率很重要
一、注释
- 包中生命的每个公共符号,包括变量、函数以及结构体都要注释
- 任何既不明显也不简短的公共功能 都要注释
- 无论长度或者复杂程度,库中任何函数都需要进行注释
- 实现接口的函数不需要注释
gofmt是Go语言官方提供的工具,能够自动格式化Go语言代码为官方统一风格,一般的IDE比如GoLand直接内置并且默认开启gofmt
除了gofmt之外还有goimport,实际上等于gofmt加上依赖包管理,会自动增删依赖包的引用并且按照字母排序并分类
注释需要: 解释代码作用、代码的逻辑、代码实现的原因、在什么情况下会出错
1.解释代码的作用
在功能和逻辑不太明显的情况需要额外注释,而对于逻辑清晰的比如:
for e := range elements{
process(e)
}
可以不需要注释
2.注释代码的实现原因
对于注释,我们应该解释代码实现的原因,另外还可以解释代码的外部因素,和额外的上下文关系
3.注释代码出错的原因
适合解释代码的限制条件,比如对于将字符串转化为时间格式的方法
那么上面则解释了传入非法的、不符合格式的字符串会触发什么异常
4.公共符号始终要注释
小结
总而言之,代码就是最好的注释,而注释需要提供代码未表达的上下文信息
二、命名
1.变量名
- 简洁胜于冗长
- 缩略词全大写,但是如果位于变量头则全小写
- 变量距离其被使用的地方越远,则需要携带更详细的信息
比如:
for i := 0; i < 10; i++ {
process()
}
for index := 0; index < 10; index++ {
process()
}
上述例子中,i和index作用域仅限于for内部,因此index的额外长度几乎没有增加对程序的理解,因此使用更短的i是更适合的
还有一个例子,新建一个接受截止时间的函数:
func(c *Client) send(req *Request, deadline time.Time)
func(c *Client) send(req *Request, t time.Time)
上述例子中,deadline可以更详细描述变量作用,因此比t更适合
2.函数名
- 函数名不懈怠包名已有的上下文信息,因为包名和函数名会一起出现
- 函数名尽量简短
- 当名为foo的包的某个函数返回的类型T并不是Foo的时候,可以在返回的函数名中加入类型信息
比如在http包中有一个创建服务的函数,有以下两种命名:
func Serve(l net.Listener, handler Handler)
func ServeHTTP(l net.Listener, handler Handler)
其中,要调用这个函数分别是使用http.Serve和http.ServeHTTP。我们发现http.ServeHTTP对于http重复描述了,实际上不够好,更合适的命名方式为http.Serve
3.包名
- 只使用小写字母组成,不包含大写字母和下划线
- 简短并包含一定上下文信息
- 不要和标准库同名
小结
核心目标是降低阅读理解代码的成本,重点考虑上下文名称,设计简洁清晰的名称
三、流程控制
1.避免嵌套,保证流程清晰
如果两个分支中都有return,则需要去除冗余的else
2.复杂的控制流程,优先处理错误和特殊情况,尽早返回或者继续循环来减少嵌套
也就是要尽量的扁平,减少嵌套层数
3.小结
- 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
- 故障问题大多出现在复杂的条件语句和循环语句中
四.错误和异常
1.简单错误
只出现一次的错误,而且在其他地方都不需要捕获该错误。优先使用errors.New来创建匿名变量直接表示简单错误,如果有格式化要求则使用fmr.Errorf
func defaultCheckRedirect(req *Request, via []*Request) error{
if len(Via) >= 10{
return errors.New("stopped after 10 redirects")
}
return null
}
2.错误的Wrap和Unwarp
这是错误的包装和解包,这可以使得一个错误嵌套另外一个错误,从而形成错误链条,方便跟踪排查问题。在fmt.Errorf中使用%w的关键字将一个错误关联到错误链中
3.错误判定
使用errors.Is判断错误
使用errors.As在错误链上获取指定类型的错误
4.painc
painc错误是一种严重的异常,用于指明一些程序已无法继续运行的严重错误。
5.recover
6.defer
defer是后进先出的