对于Golang代码规范的一点理解

321 阅读7分钟

对于Golang代码规范的一点理解

不知道大家有没有这样的感觉,当你新入职一家公司的时候,这个时候领导给你分配了一个任务,在某某业务上面增加一个功能,当你去阅读之前的代码时候,然后第一句话就是:“卧槽”,然后心里就是一边写一边骂娘,之前是怎么样一个煞笔能写出这样的代码。
养成一个好的习惯是一件极其重要的事情,这样你就不会被后面的维护者骂娘了,最重要的是你会发现写代码也是一件很有意义的事情。
作为一个IT boy,对于代码规范应该要有属于自己的一个追求,随着工作年限的增加,所看所写的代码量都会增加,不轻易间我们的代码规范就会慢慢产生一个模型,它可能不是最好的,也可能不是最差的,但是它是最熟悉,最舒服的。 以下是我自己的一点认识:

1、好的命名可以增加代码的阅读性

命名简单来说就是:英文(并非拼音不好,而是行业标准)、简短、明确、格式。
包命名:

package命名尽量保持和包的路径一致、
全英文小写、简短明确,不要和标准库冲突、
建议不使用下划线和数字+英文大小写混合命名

文件命名:

全英文小写、简短明确、下划线分割单词、
不建议使用数字+英文大小写混合命名

函数命令

函数采用驼峰格式、全英文、简短明确、
函数名不易过长、函数入参不超过5个参数、
参数过多应改用结构体传、函数返回不超过3个

结构体命名

结构体命名采用驼峰格式、全英文、简短明确、
首字母根据权限设置大小写、
结构体命名应该是名词(对象)、最好不要使用动词、接口的命名应当保持和结构体命名一样的格式

变量命名

一律采用驼峰格式、根据权限区分大小写、简短明确、如果需要区分变量类型、可以考虑使用类型前缀,如:
bool:bSucc
int: iUserId
string: sUserName
float: fValue
等等,根据个人习惯也可以不加类型前缀,只要语义明确即可。

常量/枚举命名

当代码中需要指定某个数字或者字符串,应该提前定义好常量,代码中尽量少出现大量的魔鬼数字
常量使用全英文大写、各单词之前用下划线连接、个人建议常量值直接设置
少使用iota,如果别人在中间多插入一个常量,就会导致常量值对不上

2、注释是必不可少的

好的命名并不意味着不需要注释,好的注释可以方便我们快速了解代码,同时也方便我们代码的维护,注释在于质量而不是在于多,多了反而会影响代码的阅读性,golang的注释支持 /**/ 同时也支持 // 格式。
包的注释

我们的每一个包都应该有一个注释,标识着这个包是做什么用的。
如果我们一个包里面有多个文件,可以在主要的文件的包名上面加上对应的注释。

函数注释

每一个函数都应该有一个注释,标示着这个函数的具体作用
注释的内容可以包含:函数名、函数作用、函数参数、函数返回值、以及函数的负责人。如下示例:
// XXXXFunction: 函数做什么用
// 参数:xxx
// 返回值:xxx
// 负责人:xxx
func XXXXFunction(xxx) xxx {}

结构体注释

结构体也需要注释,对结构体做具体解释,这样方便后面维护的人理解,注释内容可以包括:
结构体名称+结构体名称解释+结构体成员说明(注释应排列整齐),如:
// Account: 定义了账户相关的信息
type Account struct {
    Name string     // 姓名
    Mobile string   // 手机号
}

变量、代码逻辑注释

创建变量的时候,应该采用用而建的策略,保证我们的变量需要用的时候才创建,不要创建不使用的变量。
对于使用复杂的变量应设置一个注释,方便别人阅读代码的时候能先知道这个变量是干什么用的。
对应重要业务逻辑、异常的逻辑我们都需要添加对应的注释,方便阅读理解。
通常简单的逻辑大家都是能理解的,不需要每一行都加注释,那样是没有意义的,反而会影响阅读性,以及影响代码的美观。
对于代码逻辑我个人的看法是:
每一个函数体内部代码实现,应当提前制定好代码注释,然后根据注释去实现代码,
每一个注释可以是一个代码块,这样看起来就会清晰一些,如遇到复杂、重要的代码,在对应的行上加上注释即可。

3、golang语法使用规范

在实现代码的时候我们也应该制定一系列规范,养成一个好的习惯,同时也可以必坑,减少错误。
异常检查

提前处理异常,再处理正常逻辑,这样可以避免错误处理遗漏,同时还可以提升代码的可阅读性。

代码扁平化处理

控制代码的嵌套层数,if/for循环的语句不应该超过2层,尽量提炼出扁平化代码,
这样提升代码的阅读性、同时可以让代码看起来更优雅。

for range使用

取key,用如下格式:
for key := range data {}
取value, 用户如下格式:
for _, value := range data {}
特别注意:range副本的问题,如:
for i := range s 和 for i, v := range &s ,都不是s的副本
但是 for i, v := range s 里面的就是s的副本

组装切片时提前分配好内存

业务代码实现中,我们通常会遇到这样的情况,从一个切片中提取某一个值的切片,
比如从一个用户信息的切片中提取所有用户id:
这个时候我们应该先声明用户id的切片,并分配好内存,再从用户列表中提前用户id,如下:
userIds := make([]int, 0, len(userList))
这样可以避免在组装数据的时候,重复append分配内存,可以提升代码的性能,减少GC。
有些时候需要注意:
userIds := make([]int, len(userList)),这样分配内存,这样会提前创建零值内容,会浪费空间,同时也有可能造成错误。

map使用

尽可能指定map的容量,避免使用过程中,频繁的分配内存;
养成断言的习惯,虽然value := map["key"]` 将得到一个 0 值,但是当map的值是一个空指针的话,
再通过空指针访问成员,就会造成panic。
注意map不是并发安全的,高并发场景慎用,可以考虑使用sync包sync.Map,这个时并发安全的。

interface类型转换

类型转换也要养成断言的习惯,虽然value := iValue.(type) 如果类型不匹配会直接panic,建议如下使用:
value, ok := iValue.(type)

代码结构

每个函数不能超过100行,每行也不要过长,uber_go_guide中建议将行长限制为99个字符,
按功能点拆成多个函数,尽量考虑复用性和扩展性。

减少不必要的if else

如果过多if else,考虑使用设计模式代替

优先使用strconv而不是fmt

道理很简单strconv的速度比fmt

defer的使用

defer是golang的语法糖,方便我们很多场景释放资源、解锁等操作,但是defer会消耗很多的系统资源,不建议频繁使用;
切记避免在for循环中使用defer