天天看点

standards-go

Golang 代码规范

目录

1. 前言

2. 代码风格

2.1 【必须】格式化

2.2 【推荐】换行

2.3 【必须】括号和空格

2.4 【必须】import 规范

2.5 【必须】错误处理

2.5.1 【必须】error 处理

2.5.2 【必须】panic 处理

2.5.3 【必须】recover 处理

2.6 【必须】单元测试

2.7 【必须】类型断言失败处理

3. 注释

3.1 【必须】包注释

3.2 【必须】结构体注释

3.3 【必须】方法注释

3.4 【必须】变量和常量注释

3.5 【必须】类型注释

4. 命名规范

4.1 【推荐】包命名

4.2 【必须】文件命名

4.3 【必须】结构体命名

4.4 【推荐】接口命名

4.5 【必须】变量命名

4.6 【必须】常量命名

4.7 【必须】函数命名

5. 控制结构

5.1 【推荐】if

5.2 【推荐】for

5.3 【必须】range

5.4 【必须】switch

5.5 【推荐】return

5.6 【必须】goto

6. 函数

6.1 【推荐】函数参数

6.2 【必须】defer

6.3 【推荐】方法的接收器

6.4 【推荐】代码行数

6.5 【必须】嵌套

6.6 【推荐】变量声明

6.7 【必须】魔法数字

7. 依赖管理

7.1 【必须】go1.11 以上必须使用 go modules 模式:

7.2 【推荐】代码提交

8. 应用服务

8.1 【推荐】应用服务接口建议有 README.md

8.2 【必须】应用服务必须要有接口测试。

附:常用工具

为形成公司统一的 Go 编码风格,以保障公司项目代码的易维护性和编码安全性,特制定本规范。

本规范在 Google Golang 代码规范 的基础上,根据腾讯实际情况进行了调整和补充。

每项规范内容,给出了要求等级,其定义为:

必须(Mandatory):用户必须采用;

推荐(Preferable):用户理应采用,但如有特殊情况,可以不采用;

可选(Optional):用户可参考,自行决定是否采用;

目前本规范以 Gometalinter 工具落地,点击查看工具源码。

代码都必须用 <code>gofmt</code> 格式化。

建议一行代码不要超过<code>120列</code>,超过的情况,使用合理的换行方法换行。

例外场景:

import 模块语句

工具生成代码

struct tag

遵循 <code>gofmt</code> 的逻辑。

运算符和操作数之间要留空格。

作为输入参数或者数组下标时,运算符和运算数之间不需要空格,紧凑展示。

使用 <code>goimports</code> 自动格式化引入的包名,import 规范原则上以 <code>goimports</code> 规则为准。

<code>goimports</code> 会自动把依赖包按首字母排序,并对包进行分组管理,通过空行隔开,默认分为本地包(标准库、内部包)、第三方包。

标准包永远位于最上面的第一组。

内部包是指不能被外部 import 的包,如 GoPath 模式下的包名或者非域名开头的当前项目的 GoModules 包名。

带域名的包名都属于第三方包,如 company.code.oa.com/xxx/xxx,github.com/xxx/xxx,不用区分是否是当前项目内部的包。

<code>goimports</code> 默认最少分成本地包和第三方包两大类,这两类包必须分开不能放在一起。本地包或者第三方包内部可以继续按实际情况细分不同子类。

不要使用相对路径引入包:

应该使用完整的路径引入包:

包名和 git 路径名不一致时,或者多个相同包名冲突时,使用别名代替:

【可选】匿名包的引用建议使用一个新的分组引入,并在匿名包上写上注释说明。

完整示例如下:

<code>error</code> 作为函数的值返回,必须对 <code>error</code> 进行处理, 或将返回值赋值给明确忽略。对于 <code>defer xx.Close()</code>可以不用显式处理。

<code>error</code> 作为函数的值返回且有多个返回值的时候,<code>error</code> 必须是最后一个参数。

错误描述不需要标点结尾。

采用独立的错误流进行处理。

如果返回值需要初始化,则采用下面的方式:

错误返回的判断独立处理,不与其他变量组合逻辑判断。

【推荐】建议 go1.13 以上,error 生成方式为:<code>fmt.Errorf("module xxx: %w", err)</code>。

在业务逻辑处理中禁止使用 <code>panic</code>。

在 <code>main</code> 包中只有当完全不可运行的情况可使用 <code>panic</code>,例如:文件无法打开,数据库无法连接导致程序无法正常运行。

对于其它的包,可导出的接口一定不能有 <code>panic</code>;在包内传递错误时,不推荐使用 <code>panic</code> 来传递 <code>error</code>。

建议在 <code>main</code> 包中使用 <code>log.Fatal</code> 来记录错误,这样就可以由 <code>log</code> 来结束程序,或者将 <code>panic</code> 抛出的异常记录到日志文件中,方便排查问题。

<code>panic</code> 捕获只能到 <code>goroutine</code> 最顶层,每个自行启动的 <code>goroutine</code>,必须在入口处捕获 <code>panic</code>,并打印详细堆栈信息或进行其它处理。

<code>recover</code> 用于捕获 <code>runtime</code> 的异常,禁止滥用 <code>recover</code>。

必须在 <code>defer</code> 中使用,一般用来捕获程序运行期间发生异常抛出的 <code>panic</code> 或程序主动抛出的 <code>panic</code>。

单元测试文件名命名规范为 <code>example_test.go</code>。

测试用例的函数名称必须以 <code>Test</code> 开头,例如 <code>TestExample</code>。

如果存在 <code>func Foo</code>,单测函数可以带下划线,为 <code>func Test_Foo</code>。如果存在 <code>func (b *Bar) Foo</code>,单测函数可以为 <code>func TestBar_Foo</code>。下划线不能出现在前面描述情况以外的位置。

单测文件行数限制是普通文件的2倍,即<code>1600行</code>。单测函数行数限制也是普通函数的2倍,即为<code>160行</code>。圈复杂度、列数限制、 import 分组等其他规范细节和普通文件保持一致。

由于单测文件内的函数都是不对外的,所有可导出函数可以没有注释,但是结构体定义时尽量不要导出。

每个重要的可导出函数都要首先编写测试用例,测试用例和正规代码一起提交方便进行回归测试。

<code>type assertion</code> 的单个返回值形式针对不正确的类型将产生 <code>panic</code>。因此,请始终使用 <code>“comma ok”</code> 的惯用法。

在编码阶段同步写好变量、函数、包注释,注释可以通过 <code>godoc</code> 导出生成文档。

程序中每一个被导出的(大写的)名字,都应该有一个文档注释。

所有注释掉的代码在提交 code review 前都应该被删除,除非添加注释讲解为什么不删除, 并且标明后续处理建议(比如删除计划)。

每个包都应该有一个包注释。

包如果有多个 go 文件,只需要出现在一个 go 文件中(一般是和包同名的文件)即可,格式为:“// Package 包名 包信息描述”。

每个需要导出的自定义结构体或者接口都必须有注释说明。

注释对结构进行简要介绍,放在结构体定义的前一行。

格式为:"// 结构体名 结构体信息描述"。

结构体内的可导出成员变量名,如果是个生僻词,或者意义不明确的词,就必须要给出注释,放在成员变量的前一行或同一行的末尾。

每个需要导出的函数或者方法(结构体或者接口下的函数称为方法)都必须有注释。注意,如果方法的接收器为不可导出类型,可以不注释,但需要质疑该方法可导出的必要性。

注释描述函数或方法功能、调用方等信息。

格式为:"// 函数名 函数信息描述"。

每个需要导出的常量和变量都必须有注释说明。

该注释对常量或变量进行简要介绍,放在常量或者变量定义的前一行。

大块常量或变量定义时,可在前面注释一个总的说明,然后每一行常量的末尾详细注释该常量的定义。

格式为:"// 变量名 变量信息描述",斜线后面紧跟一个空格。

每个需要导出的类型定义(type definition)和类型别名(type aliases)都必须有注释说明。

该注释对类型进行简要介绍,放在定义的前一行。

格式为:"// 类型名 类型信息描述"。

命名是代码规范中很重要的一部分,统一的命名规范有利于提高代码的可读性,好的命名仅仅通过命名就可以获取到足够多的信息。

保持 <code>package</code> 的名字和目录一致。

尽量采取有意义、简短的包名,尽量不要和标准库冲突。

包名应该为小写单词,不要使用下划线或者混合大小写,使用多级目录来划分层级。

项目名可以通过中划线来连接多个单词。

简单明了的包命名,如:<code>time</code>、<code>list</code>、<code>http</code>。

不要使用无意义的包名,如:<code>util</code>、<code>common</code>、<code>misc</code>、<code>global</code>。<code>package</code>名字应该追求清晰且越来越收敛,符合‘单一职责’原则。而不是像<code>common</code>一样,什么都能往里面放,越来越膨胀,让依赖关系变得复杂,不利于阅读、复用、重构。注意,<code>xx/util/encryption</code>这样的包名是允许的。

采用有意义,简短的文件名。

文件名应该采用小写,并且使用下划线分割各个单词。

采用驼峰命名方式,首字母根据访问控制采用大写或者小写。

结构体名应该是名词或名词短语,如 <code>Customer</code>、<code>WikiPage</code>、<code>Account</code>、<code>AddressParser</code>,它不应是动词。

避免使用 <code>Data</code>、<code>Info</code> 这类意义太宽泛的结构体名。

结构体的声明和初始化格式采用多行,例如:

命名规则基本保持和结构体命名规则一致。

单个函数的接口名以 <code>er</code> 作为后缀,例如 <code>Reader</code>,<code>Writer</code>。

两个函数的接口名综合两个函数名。

三个以上函数的接口名,类似于结构体名。

变量名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写。

特有名词时,需要遵循以下规则:

如果变量为私有,且特有名词为首个单词,则使用小写,如 <code>apiClient</code>;

其他情况都应该使用该名词原有的写法,如 <code>APIClient</code>、<code>repoID</code>、<code>UserID</code>;

错误示例:<code>UrlArray</code>,应该写成 <code>urlArray</code> 或者 <code>URLArray</code>;

详细的专有名词列表可参考这里。

若变量类型为 <code>bool</code> 类型,则名称应以 <code>Has</code>,<code>Is</code>,<code>Can</code> 或者 <code>Allow</code> 开头。

私有全局变量和局部变量规范一致,均以小写字母开头。

代码生成工具自动生成的代码可排除此规则(如 xxx.pb.go 里面的 Id)。

变量名更倾向于选择短命名。特别是对于局部变量。 <code>c</code>比<code>lineCount</code>要好,<code>i</code>比<code>sliceIndex</code>要好。基本原则是:变量的使用和声明的位置越远,变量名就需要具备越强的描述性。

常量均需遵循驼峰式。

如果是枚举类型的常量,需要先创建相应类型:

私有全局常量和局部变量规范一致,均以小写字母开头。

函数名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写。

代码生成工具自动生成的代码可排除此规则(如协议生成文件 xxx.pb.go , gotests 自动生成文件 xxx_test.go 里面的下划线)。

<code>if</code> 接受初始化语句,约定如下方式建立局部变量:

<code>if</code> 对两个值进行判断时,约定如下顺序:变量在左,常量在右:

<code>if</code> 对于bool类型的变量,应直接进行真假判断:

采用短声明建立局部变量:

如果只需要第一项(key),就丢弃第二个:

如果只需要第二项,则把第一项置为下划线:

要求必须有 <code>default</code>:

尽早 <code>return</code>,一旦有错误发生,马上返回:

业务代码禁止使用 <code>goto</code>,其他框架或底层源码推荐尽量不用。

函数返回相同类型的两个或三个参数,或者如果从上下文中不清楚结果的含义,使用命名返回,其它情况不建议使用命名返回。

传入变量和返回变量以小写字母开头。

参数数量均不能超过<code>5个</code>。

尽量用值传递,非指针传递。

传入参数是 <code>map</code>,<code>slice</code>,<code>chan</code>,<code>interface</code> 不要传递指针。

当存在资源管理时,应紧跟 <code>defer</code> 函数进行资源的释放。

判断是否有错误发生之后,再 <code>defer</code> 释放资源。

禁止在循环中使用 <code>defer</code>,举例如下:

【推荐】推荐以类名第一个英文首字母的小写作为接收器的命名。

【推荐】接收器的命名在函数超过<code>20行</code>的时候不要用单字符。

【必须】命名不能采用 <code>me</code>,<code>this</code>,<code>self</code> 这类易混淆名称。

【必须】文件长度不能超过<code>800行</code>。

【推荐】函数长度不能超过<code>80行</code>。

嵌套深度不能超过<code>4层</code>:

变量声明尽量放在变量第一次使用前面,就近原则。

如果魔法数字出现超过<code>2次</code>,则禁止使用。

用一个常量代替:

建议所有不对外开源的工程的 <code>module name</code> 使用 <code>test.oa.com/group/repo</code> ,方便他人直接引用。

建议使用 <code>go modules</code> 作为依赖管理的项目不提交 <code>vendor</code> 目录。

建议使用 <code>go modules</code> 管理依赖的项目, <code>go.sum</code> 文件必须提交,不要添加到 .gitignore 规则中。

其中建议包括服务基本描述、使用方法、部署时的限制与要求、基础环境依赖(例如最低 go 版本、最低外部通用包版本)等。

go 语言本身在代码规范性这方面也做了很多努力,很多限制都是强制语法要求,例如左大括号不换行,引用的包或者定义的变量不使用会报错,此外 go 还是提供了很多好用的工具帮助我们进行代码的规范。

<code>gofmt</code> ,大部分的格式问题可以通过 <code>gofmt</code> 解决, <code>gofmt</code> 自动格式化代码,保证所有的 go 代码与官方推荐的格式保持一致,于是所有格式有关问题,都以 <code>gofmt</code> 的结果为准。

<code>goimports</code> ,此工具在 <code>gofmt</code> 的基础上增加了自动删除和引入包。

<code>go vet</code> ,<code>vet</code> 工具可以帮我们静态分析我们的源码存在的各种问题,例如多余的代码,提前 <code>return</code> 的逻辑, <code>struct</code> 的 <code>tag</code> 是否符合标准等。编译前先执行代码静态分析。

<code>golint</code> ,类似 <code>javascript</code> 中的 <code>jslint</code> 的工具,主要功能就是检测代码中不规范的地方。

I can see a bigger world.

继续阅读