统一规范篇

合理规划目录

本篇主要描述了公司内部同事都必须遵守的一些开发规矩,如统一开发空间,既使用统一的开发工具来保证代码最后的格式的统一,开发中对文件和代码长度的控制,必须经过go语言自带的检测机制等。

【原则1.1】合理规划目录,一个目录中只包含一个包(实现一个模块的功能),如果模块功能复杂考虑拆分子模块,或者拆分目录。

GOPATH设置

【建议1.2】使用单一的 GOPATH

import 规范

【规则1.3.1】在非测试文件(*_test.go)中,禁止使用 . 来简化导入包的对象调用。

【规则1.3.2】禁止使用相对路径导入(./subpackage),所有导入路径必须符合 go get 标准。

【建议1.3.3】建议使用goimports工具或者IDE工具来管理多行import

代码风格

【规则1.4.1】提交代码时,必须使用gofmt对代码进行格式化。

【规则1.4.2】提交代码时,必须使用golint对代码进行检查。

【建议1.4.3】提交代码前,必须使用go vet对代码进行检查。

大小约定

【建议1.5.1】单个文件长度不超过500行。

【建议1.5.2】单个函数长度不超过50行。

【规则1.5.3】单个函数圈复杂度最好不要超过10,禁止超过15。

【规则1.5.4】单行语句不能过长,如不能拆分需要分行写。一行最多120个字符。

【建议1.5.5】函数中缩进嵌套必须小于等于3层。

【原则1.5.6】保持函数内部实现的组织粒度是相近的。

命名篇

本篇以开发时从上往下的顺序既:开发前约定的基本命名规范、包、常量、变量、结构体、参数、返回值的顺序讲解了开发中各个环节的命名规范。

基本命令规范

【规则2.1.1】需要注释来补充的命名就不算是好命名。

【规则2.1.2】使用可搜索的名称

【规则2.1.3】做有意义的区分

项目目录名

【规则2.2.1】目录名必须为全小写单词,允许加中划线‘-’组合方式,但是头尾不能为中划线。

【建议2.2.2】虽然允许出现中划线,但是尽量避免或少加中划线。

包名

【原则2.3.1】取名尽量采取有意义的包名,简单和可阅读。

【规则2.3.2】包名必须全部为小写单词,无下划线,越短越好。尽量不要与标准库重名。

【规则2.3.3】禁止通过中划线连接多个单词的方式来命名包名。

【建议2.3.4】包名尽量与所在目录名一致,引用时比较方便。

文件名

【规则2.4.1】文件名必须为小写单词,允许加下划线‘_’组合方式,但是头尾不能为下划线

【建议2.4.2】虽然允许出现下划线,但是尽量避免。

【建议2.4.3】文件名以功能为指引,名字中不需再出现模块名或者组件名。

常量

【规则2.5.1】常量&枚举名采用大小写混排的驼峰模式(Golang官方要求),不允许出现下划线

【建议2.5.2】按照功能来区分,而不是将所有类型都分在一组,并建议将公共常量置于私有常量之前

【规则2.2.3】如果是枚举类型的常量,需要先创建相应类型

【建议2.5.4】如果模块的功能较为复杂、常量名称容易混淆的情况下,为了更好地区分枚举类型,可以使用完整的前缀

变量

变量申明

【规则2.6.1】变量命名基本上遵循相应的英文表达或简写,在相对简单的环境(对象数量少、针对性强)中,可以将一些名称由完整单词简写为单个字母

变量命名惯例

【规则2.6.2】变量名称一般遵循驼峰法,并且不允许出现下划线,当遇到特有名词时,需要遵循以下规则:

  • 如果变量为私有,且特有名词为首个单词,则使用小写,如:apiClient
  • 其它情况都应当使用该名词原有的写法,如 APIClient、repoID、UserID

【规则2.6.3】不要使用_来命名变量名,多个变量申明放在一起

【规则2.6.4】在函数外部申明必须使用var,不要采用:=,容易踩到变量的作用域的问题。

全局变量名

【规则2.6.5】全局变量必须为大小写混排的驼峰模式,不允许出现下划线。首字母根据作为范围确定大小写。

【建议2.6.6】尽量避免跨package使用全局变量,尽量减少全局变量的使用。

局部变量名

【规则2.6.7】局部变量名必须为大小写混排,且首字母小写,不能有下划线。

循环变量

【建议2.6.8】for循环变量可以使用单字母。

结构体(struct)

【规则2.7.1】struct申明和初始化格式采用多行

【规则2.7.2】结构体名必须为大小写混排的驼峰模式,不允许出现下划线,可被包外部引用则首字母大写;如仅包内使用,则首字母小写。

【建议2.7.3】结构名建议采用名词、动名词为好。

接口名

【规则2.8.1】接口名必须为大小写混排,支持包外引用则首字母大写,仅包内使用则首字母小写。不能有下划线,整体必须为名词。

【建议2.8.2】单个函数的接口名以”er”作为后缀。

函数和方法名

【规则2.9.1】函数名必须为大小写混排的驼峰模式

【建议2.9.2】函数名力求精简准确,并采用用动词或动词短

【规则2.9.3】方法接收名必须为大小写混排,首字母小写。方法接收者命名要能够体现接收者对象。

【建议2.9.4】接收者名通常1个或者2个字母就够,最长不能超过4个字母。

【建议2.9.5】接收者名不要使用me,this 或者 self 这种泛指的名字。

【建议2.9.6】定义方法时,如果方法内不会直接引用接收者,则省略掉接收者名。

参数名

【规则2.10】参数名必须为大小写混排,且首字母小写,不能有下划线。

返回值

【规则2.11.1】返回值如果是命名的,则必须大小写混排,首字母小写。

【建议2.11.2】 函数的返回值应避免使用命名的参数。

开发篇

本篇主要是讲解开发中各个环节的开发规范和对一些代码的优化写法。在本文中有一些特别标黄的建议,我真的建议你好好看看那些代码,因为那可能对你提高代码开发会很有帮助。

【建议3.1.1】项目仓库中包含全量的代码

【建议3.1.2】建议采用 Glide 来管理第三方包

魔鬼数字

【规则3.2】代码中禁止使用魔鬼数字。

常量 & 枚举

【建议3.3.1】 为整数常量添加 String() 方法

【建议3.3.2】让 iota 从 a +1 开始增量

结构体

【规则3.4.1】对于要使用json转换的结构体代码,变量名必须为大写,否则你只会得到一个为空的对象

【建议3.4.2】 在初始化结构体时使用带有标签的语法

【建议3.4.3】将结构体的初始化拆分到多行

运算符

【规则3.5】运算符前后、逗号后面、if后面等需有单空格隔开。

函数

【原则3.6.1】保持函数内部实现的组织粒度是相近的。

【建议3.6.2】 返回函数调用

【建议3.6.3】 withContext 封装函数

参数

【建议3.7.1】参数按逻辑紧密程度安排位置, 同种类型的参数放在相邻位置。

【建议3.7.2】避免使用标识参数来控制函数的执行逻辑。

【建议3.7.3】参数个数不要超过5个

返回值

【规则3.8.1】函数返回值个数不要超过3个。

【建议3.8.2】如果函数的返回值超过3个,建议将其中关系密切的返回值参数封装成一个结构体。

注释

【原则3.9.1】编写代码首先考虑如何代码自我解释,然后才是添加注释进行补充说明

【原则3.9.2】注释的内容要清楚、明了,含义准确,防止注释二义性。

【原则3.9.3】在代码的功能、意图层次上进行注释,即注释用于解释代码难以直接表达的意图,而不是重复描述代码。

【规则3.9.4】所有导出对象都需要注释说明其用途;非导出对象根据情况进行注释。必须时,应该说明值的取值范围,及默认值。

【规则3.9.5】注释的单行长度不能超过 80 个字符。

【规则3.9.6】注释需要紧贴对应的包声明和函数之前,不能有空行、

【规则3.9.7】非跨度很长的注释,尽量使用 // 方式。

【规则3.9.8】避免多余的空格,两句注释之间保持一个空格。

【原则3.9.9】注释第一条语句应该为一条概括语句,并且使用被声明的名字作为开头。

【建议3.9.10】//与注释的文档之间空一格。

【规则3.9.11】每个程序包都应该有一个包注释,一个位于package子句之前的块注释。

【规则3.9.12】不要依靠用空格进行对齐。

【建议3.27】类型定义一般都以单数信息描述。

【建议3.9.13】函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等

【建议3.9.14】如果函数或者方法为判断类型(返回值主要为bool类型),则以 returns true if 开头。

错误

【原则3.10.1】错误处理的原则就是不能丢弃任何有返回err的调用,不要采用_丢弃,必须全部处理。接收到错误,要么返回err,要么实在不行就panic,或者使用log记录下来

【规则3.10.2】error的信息不要采用大写字母,尽量保持你的错误简短,但是要足够表达你的错误的意思。

【规则3.10.3】导出的错误变量的命名,以Err开始,如ErrSomething,无需导出的错误变量命名,以Error作为后缀,如specificError

【规则3.10.4】公共包内禁止使用panic,如果有panic需要内部recover并返回error。

其他

【建议3.11.1】在代码中编写字符串形式的json时,使用反单引号,而不是双引号。

【规则3.11.2】相对独立的程序块之间、变量说明之后必须加空行,而逻辑紧密相关的代码则放在一起。

【规则3.11.3】尽早return:一旦有错误发生,马上返回。

【建议3.11.4】禁止出现2处及以上的重复代码。

【建议3.11.5】if条件判断, 同时使用超过3个表达式以上的时候, 使用switch替代。

【建议3.11.6】定义bool变量时,要避免判断时出现双重否定,应使用肯定形式的表达式。

【建议3.11.7】for循环初始值从0开始,判断条件使用<无等号的方式。

【建议3.11.8】长句子打印或者调用,使用参数进行格式化分行

【建议3.11.9】 将 for-select 封装到函数中

【建议3.11.10】把 slice、map 等定义为自定义类型

【建议3.11.11】 为访问 map 增加 setter,getters

参数传递

【建议3.11.12】 对于少量数据,不要传递指针

【建议3.11.13】 对于大量数据的 struct 可以考虑使用指针

【建议3.11.14】 传入的参数是 map,slice,chan 不要传递指针,因为 map,slice,chan 是引用类型,不需要传递指针的指针

注意闭包的调用

【原则3.11.15】在循环中调用函数或者goroutine方法,一定要采用显示的变量调用,不要再闭包函数里面调用循环的参数

优化篇

本篇的意义是为开发提供一些经过验证的开发规则和建议,让开发在开发过程中避免低级错误,从而提高代码的质量保证和性能效率

质量保证

代码质量保证优先原则

【原则4.1.1】代码质量保证优先原则:
(1)正确性,指程序要实现设计要求的功能。
(2)简洁性,指程序易于理解并且易于实现。
(3)可维护性,指程序被修改的能力,包括纠错、改进、新需求或功能规格变化的适应能力。
(4)可靠性,指程序在给定时间间隔和环境条件下,按设计要求成功运行程序的概率。
(5)代码可测试性,指软件发现故障并隔离、定位故障的能力,以及在一定的时间和成本前提下,进行测试设计、测试执行的能力。
(6)代码性能高效,指是尽可能少地占用系统资源,包括内存和执行时间。
(7)可移植性,指为了在原来设计的特定环境之外运行,对系统进行修改的能力。

对外接口原则

【原则4.1.2】对于主要功能模块抽象模块接口,通过interface提供对外功能。

值与指针(T/*T)的使用原则

【建议4.1.3.1】基本类型传递时,尽量使用值传递。

【建议4.1.3.2】如果传递字符串或者接口对象时,建议直接实例传递而不是指针传递。

【建议4.1.3.3】如果是map、func、chan,那么直接用T。

【建议4.1.3.4】如果是slice,method里面不重新reslice之类的就用T。

【建议4.1.3.5】如果想通过method改变里面的属性,那么请使用*T。

【建议4.1.3.6】如果是struct,并且里面包含了sync.Mutex之类的同步原语,那么请使用*T,避免copy。

【建议4.1.3.7】如果是一个大型的struct或者array,那么使用*T会比较轻量,效率更高。

【建议4.1.3.8】如果是struct、slice、array里面的元素是一个指针类型,然后调用函数又会改变这个数据,那么对于读者来说采用*T比较容易懂。

【建议4.1.3.9】其它情况下,建议采用*T。

init的使用原则

【规则4.1.4.1】一个文件只定义一个init函数。

【规则4.1.4.2】一个包内的如果存在多个init函数,不能有任何的依赖关系。

defer的使用原则

【建议4.1.5.1】如果函数存在多个返回的地方,则采用defer来完成如关闭资源、解锁等清理操作。

【建议4.1.5.2】defer会消耗更多的系统资源,不建议用于频繁调用的方法中。

【建议4.1.5.3】避免在for循环中使用defer。

Goroutine使用原则

【规则4.1.6.1】确保每个goroutine都能退出。

【规则4.1.6.2】禁止在闭包中直接引用闭包外部的循环变量。

Channel使用原则

【规则4.1.7.1】传递channel类型的参数时应该区分其职责。

【规则4.1.7.2】确保对channel是否关闭做检查。

【规则4.1.7.3】禁止重复释放channel。

其它

【建议4.1.8.1】使用go vet --shadow检查变量覆盖,以避免无意的变量覆盖。

【建议4.1.8.2】GO的结构体中控制使用Slice和Map。

【规则4.1.8.3】避免在循环引用调用 runtime.SetFinalizer。

【规则4.1.8.4】避免在for循环中使用time.Tick()函数。

性能效率

Memory优化

【建议4.2.1.1】将多次分配小对象组合为一次分配大对象。

【建议4.2.1.2】将多个不同的小对象绑成一个大结构,可以减少内存分配的次数。

【建议4.2.1.3】组合内存分配的一个特殊情形是对分片数组进行预分配。

【建议4.2.1.4】尽可能使用小数据类型,并尽可能满足硬件流水线(Pipeline)的操作,如对齐数据预取边界。

【建议4.2.1.5】使用对象池来重用临时对象,减少内存分配。

GC 优化

【建议4.2.2.1】设置GOMAXPROCS为CPU的核心数目,或者稍高的数值。

【建议4.2.2.2】避免频繁创建对象导致GC处理性能问题。

其它优化建议

【建议4.2.3.1】减少[]byte和string之间的转换,尽量使用[]byte来处理字符。

【建议4.2.3.2】make申请slice/map时,根据预估大小来申请合适内存。

【建议4.2.3.3】字符串拼接优先考虑bytes.Buffer。

【建议4.2.3.4】避免使用CGO或者减少跨CGO调用次数。

【建议4.2.3.5】避免高并发调用同步系统接口。

【建议4.2.3.6】高并发时避免共享对象互斥。

【建议4.2.3.7】长调用链或在函数中避免申明较多较大临时变量。

【建议4.2.3.8】为高并发的轻量级任务处理创建routine池。

【建议4.2.3.9】建议版本提供性能/内存监控的功能,并动态开启关闭,但不要长期开启pprof提供的CPU与MEM profile功能。