腾讯standards-go
<h1 id="golang-代码规范">Golang 代码规范</h1><p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>Golang 代码规范<ul><li>1. 前言</li><li>2. 代码风格<ul><li>2.1 【必须】格式化</li><li>2.2 【推荐】换行</li><li>2.3 【必须】括号和空格</li><li>2.4 【必须】import 规范</li><li>2.5 【必须】错误处理<ul><li>2.5.1 【必须】error 处理</li><li>2.5.2 【必须】panic 处理</li><li>2.5.3 【必须】recover 处理</li></ul></li><li>2.6 【必须】单元测试</li><li>2.7 【必须】类型断言失败处理</li></ul></li><li>3. 注释<ul><li>3.1 【必须】包注释</li><li>3.2 【必须】结构体注释</li><li>3.3 【必须】方法注释</li><li>3.4 【必须】变量和常量注释</li><li>3.5 【必须】类型注释</li></ul></li><li>4. 命名规范<ul><li>4.1 【推荐】包命名</li><li>4.2 【必须】文件命名</li><li>4.3 【必须】结构体命名</li><li>4.4 【推荐】接口命名</li><li>4.5 【必须】变量命名</li><li>4.6 【必须】常量命名</li><li>4.7 【必须】函数命名</li></ul></li><li>5. 控制结构<ul><li>5.1【推荐】if</li><li>5.2 【推荐】for</li><li>5.3 【必须】range</li><li>5.4 【必须】switch</li><li>5.5 【推荐】return</li><li>5.6 【必须】goto</li></ul></li><li>6. 函数<ul><li>6.1 【推荐】函数参数</li><li>6.2 【必须】defer</li><li>6.3 【推荐】方法的接收器</li><li>6.4 【推荐】代码行数</li><li>6.5 【必须】嵌套</li><li>6.6 【推荐】变量声明</li><li>6.7 【必须】魔法数字</li></ul></li><li>7. 依赖管理<ul><li>7.1 【必须】go1.11 以上必须使用 <code>go modules</code> 模式:</li><li>7.2 【推荐】代码提交</li></ul></li><li>8. 应用服务<ul><li>8.1 【推荐】应用服务接口建议有 <code>README.md</code></li><li>8.2 【必须】应用服务必须要有接口测试。</li></ul></li><li>附:常用工具</li></ul></li></ul></div><p></p>
<h2 id="1-前言">1. 前言</h2>
<p>为形成公司统一的 Go 编码风格,以保障公司项目代码的易维护性和编码安全性,特制定本规范。</p>
<p>本规范在 Google Golang 代码规范 的基础上,根据腾讯实际情况进行了调整和补充。</p>
<p>每项规范内容,给出了要求等级,其定义为:</p>
<ul>
<li><strong>必须(Mandatory)</strong>:用户必须采用;</li>
<li><strong>推荐(Preferable)</strong>:用户理应采用,但如有特殊情况,可以不采用;</li>
<li><strong>可选(Optional)</strong>:用户可参考,自行决定是否采用;</li>
</ul>
<p>目前本规范以 Gometalinter 工具落地,点击查看工具源码。</p>
<h2 id="2-代码风格">2. 代码风格</h2>
<h3 id="21-必须格式化">2.1 【必须】格式化</h3>
<ul>
<li>代码都必须用 <code>gofmt</code> 格式化。</li>
</ul>
<h3 id="22-推荐换行">2.2 【推荐】换行</h3>
<ul>
<li>建议一行代码不要超过<code>120列</code>,超过的情况,使用合理的换行方法换行。</li>
<li>例外场景:
<ul>
<li>import 模块语句</li>
<li>工具生成代码</li>
<li>struct tag</li>
</ul>
</li>
</ul>
<h3 id="23-必须括号和空格">2.3 【必须】括号和空格</h3>
<ul>
<li>
<p>遵循 <code>gofmt</code> 的逻辑。</p>
</li>
<li>
<p>运算符和操作数之间要留空格。</p>
</li>
<li>
<p>作为输入参数或者数组下标时,运算符和运算数之间不需要空格,紧凑展示。</p>
</li>
</ul>
<h3 id="24-必须import-规范">2.4 【必须】import 规范</h3>
<ul>
<li>
<p>使用 <code>goimports</code> 自动格式化引入的包名,import 规范原则上以 <code>goimports</code> 规则为准。</p>
</li>
<li>
<p><code>goimports</code> 会自动把依赖包按首字母排序,并对包进行分组管理,通过空行隔开,默认分为本地包(标准库、内部包)、第三方包。</p>
</li>
<li>
<p>标准包永远位于最上面的第一组。</p>
</li>
<li>
<p>内部包是指不能被外部 import 的包,如 GoPath 模式下的包名或者非域名开头的当前项目的 GoModules 包名。</p>
</li>
<li>
<p>带域名的包名都属于第三方包,如 company.code.oa.com/xxx/xxx,github.com/xxx/xxx,不用区分是否是当前项目内部的包。</p>
</li>
<li>
<p><code>goimports</code> 默认最少分成本地包和第三方包两大类,这两类包必须分开不能放在一起。本地包或者第三方包内部可以继续按实际情况细分不同子类。</p>
</li>
<li>
<p>不要使用相对路径引入包:</p>
</li>
</ul>
<pre><code class="language-go">// 不要采用这种方式
import (
"../net"
)
</code></pre>
<ul>
<li>应该使用完整的路径引入包:</li>
</ul>
<pre><code class="language-go">import (
"xxxx.com/proj/net"
)
</code></pre>
<ul>
<li>包名和 git 路径名不一致时,或者多个相同包名冲突时,使用别名代替:</li>
</ul>
<pre><code class="language-go">import (
opentracing "github.com/opentracing/opentracing-go"
)
</code></pre>
<ul>
<li>【可选】匿名包的引用建议使用一个新的分组引入,并在匿名包上写上注释说明。</li>
</ul>
<p>完整示例如下:</p>
<pre><code class="language-go">import (
// standard package & inner package
"encoding/json"
"myproject/models"
"myproject/controller"
"strings"
// third-party package
"git.obc.im/obc/utils"
"git.obc.im/dep/beego"
"git.obc.im/dep/mysql"
opentracing "github.com/opentracing/opentracing-go"
// anonymous import package
// import filesystem storage driver
_ "test.oa.com/org/repo/pkg/storage/filesystem
)
</code></pre>
<h3 id="25-必须错误处理">2.5 【必须】错误处理</h3>
<h4 id="251-必须error-处理">2.5.1 【必须】error 处理</h4>
<ul>
<li>
<p><code>error</code> 作为函数的值返回,必须对 <code>error</code> 进行处理, 或将返回值赋值给明确忽略。对于 <code>defer xx.Close()</code>可以不用显式处理。</p>
</li>
<li>
<p><code>error</code> 作为函数的值返回且有多个返回值的时候,<code>error</code> 必须是最后一个参数。</p>
</li>
</ul>
<pre><code class="language-go">// 不要采用这种方式
func do() (error, int) {
}
// 要采用下面的方式
func do() (int, error) {
}
</code></pre>
<ul>
<li>
<p>错误描述不需要标点结尾。</p>
</li>
<li>
<p>采用独立的错误流进行处理。</p>
</li>
</ul>
<pre><code class="language-go">// 不要采用这种方式
if err != nil {
// error handling
} else {
// normal code
}
// 而要采用下面的方式
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
</code></pre>
<ul>
<li>如果返回值需要初始化,则采用下面的方式:</li>
</ul>
<pre><code class="language-go">x, err := f()
if err != nil {
// error handling
return // or continue, etc.
}
// use x
</code></pre>
<ul>
<li>错误返回的判断独立处理,不与其他变量组合逻辑判断。</li>
</ul>
<pre><code class="language-go">// 不要采用这种方式:
x, y, err := f()
if err != nil || y == nil {
return err // 当y与err都为空时,函数的调用者会出现错误的调用逻辑
}
// 应当使用如下方式:
x, y, err := f()
if err != nil {
return err
}
if y == nil {
return fmt.Errorf("some error")
}
</code></pre>
<ul>
<li>【推荐】建议 go1.13 以上,error 生成方式为:<code>fmt.Errorf("module xxx: %w", err)</code>。</li>
</ul>
<h4 id="252-必须panic-处理">2.5.2 【必须】panic 处理</h4>
<ul>
<li>
<p>在业务逻辑处理中禁止使用 <code>panic</code>。</p>
</li>
<li>
<p>在 <code>main</code> 包中只有当完全不可运行的情况可使用 <code>panic</code>,例如:文件无法打开,数据库无法连接导致程序无法正常运行。</p>
</li>
<li>
<p>对于其它的包,可导出的接口一定不能有 <code>panic</code>;在包内传递错误时,不推荐使用 <code>panic</code> 来传递 <code>error</code>。</p>
</li>
</ul>
<pre><code class="language-go">// 不推荐为传递error而在包内使用panic,以下为示例
// PError 包内定义的错误类型
type PError string
// Error error接口方法
func (e PError) Error() string {
return string(e)
}
func do(str string) {
// ...
// 此处的panic用于传递error
panic(PError("错误信息"))
// ...
}
// Do 包级访问入口
func Do(str string) (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(PError)
}
}()
do(str)
return nil
}
</code></pre>
<ul>
<li>
<p>建议在 <code>main</code> 包中使用 <code>log.Fatal</code> 来记录错误,这样就可以由 <code>log</code> 来结束程序,或者将 <code>panic</code> 抛出的异常记录到日志文件中,方便排查问题。</p>
</li>
<li>
<p><code>panic</code> 捕获只能到 <code>goroutine</code> 最顶层,每个自行启动的 <code>goroutine</code>,必须在入口处捕获 <code>panic</code>,并打印详细堆栈信息或进行其它处理。</p>
</li>
</ul>
<h4 id="253-必须recover-处理">2.5.3 【必须】recover 处理</h4>
<ul>
<li>
<p><code>recover</code> 用于捕获 <code>runtime</code> 的异常,禁止滥用 <code>recover</code>。</p>
</li>
<li>
<p>必须在 <code>defer</code> 中使用,一般用来捕获程序运行期间发生异常抛出的 <code>panic</code> 或程序主动抛出的 <code>panic</code>。</p>
</li>
</ul>
<pre><code class="language-go">package main
import (
"log"
)
func main() {
defer func() {
if err := recover(); err != nil {
// do something or record log
log.Println("exec panic error: ", err)
// log.Println(debug.Stack())
}
}()
getOne()
panic(11) //手动抛出panic
}
// getOne 模拟slice越界 runtime运行时抛出的panic
func getOne() {
defer func() {
if err := recover(); err != nil {
// do something or record log
log.Println("exec panic error: ", err)
// log.Println(debug.Stack())
}
}()
var arr = []string{"a", "b", "c"}
log.Println("hello,", arr)
}
// 执行结果:
// 2020/01/02 17:18:53 exec panic error:runtime error: index out of range
// 2020/01/02 17:18:53 exec panic error:11
</code></pre>
<h3 id="26-必须单元测试">2.6 【必须】单元测试</h3>
<ul>
<li>
<p>单元测试文件名命名规范为 <code>example_test.go</code>。</p>
</li>
<li>
<p>测试用例的函数名称必须以 <code>Test</code> 开头,例如 <code>TestExample</code>。</p>
</li>
<li>
<p>如果存在 <code>func Foo</code>,单测函数可以带下划线,为 <code>func Test_Foo</code>。如果存在 <code>func (b *Bar) Foo</code>,单测函数可以为 <code>func TestBar_Foo</code>。下划线不能出现在前面描述情况以外的位置。</p>
</li>
<li>
<p>单测文件行数限制是普通文件的2倍,即<code>1600行</code>。单测函数行数限制也是普通函数的2倍,即为<code>160行</code>。圈复杂度、列数限制、 import 分组等其他规范细节和普通文件保持一致。</p>
</li>
<li>
<p>由于单测文件内的函数都是不对外的,所有可导出函数可以没有注释,但是结构体定义时尽量不要导出。</p>
</li>
<li>
<p>每个重要的可导出函数都要首先编写测试用例,测试用例和正规代码一起提交方便进行回归测试。</p>
</li>
</ul>
<h3 id="27-必须类型断言失败处理">2.7 【必须】类型断言失败处理</h3>
<ul>
<li><code>type assertion</code> 的单个返回值形式针对不正确的类型将产生 <code>panic</code>。因此,请始终使用 <code>“comma ok”</code> 的惯用法。</li>
</ul>
<pre><code class="language-go">// 不要采用这种方式
t := i.(string)
// 而要采用下面的方式
t, ok := i.(string)
if !ok {
// 优雅地处理错误
}
</code></pre>
<h2 id="3-注释">3. 注释</h2>
<ol>
<li>在编码阶段同步写好变量、函数、包注释,注释可以通过 <code>godoc</code> 导出生成文档。</li>
<li>程序中每一个被导出的(大写的)名字,都应该有一个文档注释。</li>
<li>所有注释掉的代码在提交 code review 前都应该被删除,除非添加注释讲解为什么不删除, 并且标明后续处理建议(比如删除计划)。</li>
</ol>
<h3 id="31-必须包注释">3.1 【必须】包注释</h3>
<ul>
<li>
<p>每个包都应该有一个包注释。</p>
</li>
<li>
<p>包如果有多个 go 文件,只需要出现在一个 go 文件中(一般是和包同名的文件)即可,格式为:“// Package 包名 包信息描述”。</p>
</li>
</ul>
<pre><code class="language-go">// Package math provides basic constants and mathematical functions.
package math
// 或者
/*
Package template implements data-driven templates for generating textual
output such as HTML.
....
*/
package template
</code></pre>
<h3 id="32-必须结构体注释">3.2 【必须】结构体注释</h3>
<ul>
<li>
<p>每个需要导出的自定义结构体或者接口都必须有注释说明。</p>
</li>
<li>
<p>注释对结构进行简要介绍,放在结构体定义的前一行。</p>
</li>
<li>
<p>格式为:"// 结构体名 结构体信息描述"。</p>
</li>
<li>
<p>结构体内的可导出成员变量名,如果是个生僻词,或者意义不明确的词,就必须要给出注释,放在成员变量的前一行或同一行的末尾。</p>
</li>
</ul>
<pre><code class="language-go">// User 用户结构定义了用户基础信息
type User struct {
Namestring
Email string
// Demographic 族群
Demographic string
}
</code></pre>
<h3 id="33-必须方法注释">3.3 【必须】方法注释</h3>
<ul>
<li>
<p>每个需要导出的函数或者方法(结构体或者接口下的函数称为方法)都必须有注释。注意,如果方法的接收器为不可导出类型,可以不注释,但需要质疑该方法可导出的必要性。</p>
</li>
<li>
<p>注释描述函数或方法功能、调用方等信息。</p>
</li>
<li>
<p>格式为:"// 函数名 函数信息描述"。</p>
</li>
</ul>
<pre><code class="language-go">// NewtAttrModel 是属性数据层操作类的工厂方法
func NewAttrModel(ctx *common.Context) *AttrModel {
// TODO
}
</code></pre>
<h3 id="34-必须变量和常量注释">3.4 【必须】变量和常量注释</h3>
<ul>
<li>
<p>每个需要导出的常量和变量都必须有注释说明。</p>
</li>
<li>
<p>该注释对常量或变量进行简要介绍,放在常量或者变量定义的前一行。</p>
</li>
<li>
<p>大块常量或变量定义时,可在前面注释一个总的说明,然后每一行常量的末尾详细注释该常量的定义。</p>
</li>
<li>
<p>格式为:"// 变量名 变量信息描述",斜线后面紧跟一个空格。</p>
</li>
</ul>
<pre><code class="language-go">// FlagConfigFile 配置文件的命令行参数名
const FlagConfigFile = "--config"
// 命令行参数
const (
FlagConfigFile1 = "--config" // 配置文件的命令行参数名1
FlagConfigFile2 = "--config" // 配置文件的命令行参数名2
FlagConfigFile3 = "--config" // 配置文件的命令行参数名3
FlagConfigFile4 = "--config" // 配置文件的命令行参数名4
)
// FullName 返回指定用户名的完整名称
var FullName = func(username string) string {
return fmt.Sprintf("fake-%s", username)
}
</code></pre>
<h3 id="35-必须类型注释">3.5 【必须】类型注释</h3>
<ul>
<li>
<p>每个需要导出的类型定义(type definition)和类型别名(type aliases)都必须有注释说明。</p>
</li>
<li>
<p>该注释对类型进行简要介绍,放在定义的前一行。</p>
</li>
<li>
<p>格式为:"// 类型名 类型信息描述"。</p>
</li>
</ul>
<pre><code class="language-go">// StorageClass 存储类型
type StorageClass string
// FakeTime 标准库时间的类型别名
type FakeTime = time.Time
</code></pre>
<h2 id="4-命名规范">4. 命名规范</h2>
<p>命名是代码规范中很重要的一部分,统一的命名规范有利于提高代码的可读性,好的命名仅仅通过命名就可以获取到足够多的信息。</p>
<h3 id="41-推荐包命名">4.1 【推荐】包命名</h3>
<ul>
<li>
<p>保持 <code>package</code> 的名字和目录一致。</p>
</li>
<li>
<p>尽量采取有意义、简短的包名,尽量不要和标准库冲突。</p>
</li>
<li>
<p>包名应该为小写单词,不要使用下划线或者混合大小写,使用多级目录来划分层级。</p>
</li>
<li>
<p>项目名可以通过中划线来连接多个单词。</p>
</li>
<li>
<p>简单明了的包命名,如:<code>time</code>、<code>list</code>、<code>http</code>。</p>
</li>
<li>
<p>不要使用无意义的包名,如:<code>util</code>、<code>common</code>、<code>misc</code>、<code>global</code>。<code>package</code>名字应该追求清晰且越来越收敛,符合‘单一职责’原则。而不是像<code>common</code>一样,什么都能往里面放,越来越膨胀,让依赖关系变得复杂,不利于阅读、复用、重构。注意,<code>xx/util/encryption</code>这样的包名是允许的。</p>
</li>
</ul>
<h3 id="42-必须文件命名">4.2 【必须】文件命名</h3>
<ul>
<li>
<p>采用有意义,简短的文件名。</p>
</li>
<li>
<p>文件名应该采用小写,并且使用下划线分割各个单词。</p>
</li>
</ul>
<h3 id="43-必须结构体命名">4.3 【必须】结构体命名</h3>
<ul>
<li>
<p>采用驼峰命名方式,首字母根据访问控制采用大写或者小写。</p>
</li>
<li>
<p>结构体名应该是名词或名词短语,如 <code>Customer</code>、<code>WikiPage</code>、<code>Account</code>、<code>AddressParser</code>,它不应是动词。</p>
</li>
<li>
<p>避免使用 <code>Data</code>、<code>Info</code> 这类意义太宽泛的结构体名。</p>
</li>
<li>
<p>结构体的声明和初始化格式采用多行,例如:</p>
</li>
</ul>
<pre><code class="language-go">// User 多行声明
type User struct {
Namestring
Email string
}
// 多行初始化
u := User{
UserName: "john",
Email: "john@example.com",
}
</code></pre>
<h3 id="44-推荐接口命名">4.4 【推荐】接口命名</h3>
<ul>
<li>
<p>命名规则基本保持和结构体命名规则一致。</p>
</li>
<li>
<p>单个函数的接口名以 <code>er</code> 作为后缀,例如 <code>Reader</code>,<code>Writer</code>。</p>
</li>
</ul>
<pre><code class="language-go">// Reader 字节数组读取接口
type Reader interface {
// Read 读取整个给定的字节数据并返回读取的长度
Read(p []byte) (n int, err error)
}
</code></pre>
<ul>
<li>
<p>两个函数的接口名综合两个函数名。</p>
</li>
<li>
<p>三个以上函数的接口名,类似于结构体名。</p>
</li>
</ul>
<pre><code class="language-go">// Car 小汽车结构申明
type Car interface {
// Start ...
Start([]byte)
// Stop ...
Stop() error
// Recover ...
Recover()
}
</code></pre>
<h3 id="45-必须变量命名">4.5 【必须】变量命名</h3>
<ul>
<li>
<p>变量名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写。</p>
</li>
<li>
<p>特有名词时,需要遵循以下规则:</p>
<ul>
<li>如果变量为私有,且特有名词为首个单词,则使用小写,如 <code>apiClient</code>;</li>
<li>其他情况都应该使用该名词原有的写法,如 <code>APIClient</code>、<code>repoID</code>、<code>UserID</code>;</li>
<li>错误示例:<code>UrlArray</code>,应该写成 <code>urlArray</code> 或者 <code>URLArray</code>;</li>
<li>详细的专有名词列表可参考这里。</li>
</ul>
</li>
<li>
<p>若变量类型为 <code>bool</code> 类型,则名称应以 <code>Has</code>,<code>Is</code>,<code>Can</code> 或者 <code>Allow</code> 开头。</p>
</li>
<li>
<p>私有全局变量和局部变量规范一致,均以小写字母开头。</p>
</li>
<li>
<p>代码生成工具自动生成的代码可排除此规则(如 xxx.pb.go 里面的 Id)。</p>
</li>
<li>
<p>变量名更倾向于选择短命名。特别是对于局部变量。 <code>c</code>比<code>lineCount</code>要好,<code>i</code>比<code>sliceIndex</code>要好。基本原则是:变量的使用和声明的位置越远,变量名就需要具备越强的描述性。</p>
</li>
</ul>
<h3 id="46-必须常量命名">4.6 【必须】常量命名</h3>
<ul>
<li>常量均需遵循驼峰式。</li>
</ul>
<pre><code class="language-go">// AppVersion 应用程序版本号定义
const AppVersion = "1.0.0"
</code></pre>
<ul>
<li>如果是枚举类型的常量,需要先创建相应类型:</li>
</ul>
<pre><code class="language-go">// Scheme 传输协议
type Scheme string
const (
// HTTP 表示HTTP明文传输协议
HTTP Scheme = "http"
// HTTPS 表示HTTPS加密传输协议
HTTPS Scheme = "https"
)
</code></pre>
<ul>
<li>私有全局常量和局部变量规范一致,均以小写字母开头。</li>
</ul>
<pre><code class="language-go">const appVersion = "1.0.0"
</code></pre>
<h3 id="47-必须函数命名">4.7 【必须】函数命名</h3>
<ul>
<li>函数名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写。</li>
<li>代码生成工具自动生成的代码可排除此规则(如协议生成文件 xxx.pb.go , gotests 自动生成文件 xxx_test.go 里面的下划线)。</li>
</ul>
<h2 id="5-控制结构">5. 控制结构</h2>
<h3 id="51--推荐if">5.1【推荐】if</h3>
<ul>
<li><code>if</code> 接受初始化语句,约定如下方式建立局部变量:</li>
</ul>
<pre><code class="language-go">if err := file.Chmod(0664); err != nil {
return err
}
</code></pre>
<ul>
<li><code>if</code> 对两个值进行判断时,约定如下顺序:变量在左,常量在右:</li>
</ul>
<pre><code class="language-go">// 不要采用这种方式
if nil != err {
// error handling
}
// 不要采用这种方式
if 0 == errorCode {
// do something
}
// 而要采用下面的方式
if err != nil {
// error handling
}
// 而要采用下面的方式
if errorCode == 0 {
// do something
}
</code></pre>
<ul>
<li><code>if</code> 对于bool类型的变量,应直接进行真假判断:</li>
</ul>
<pre><code class="language-go">var allowUserLogin bool
// 不要采用这种方式
if allowUserLogin == true {
// do something
}
// 不要采用这种方式
if allowUserLogin == false {
// do something
}
// 而要采用下面的方式
if allowUserLogin {
// do something
}
// 而要采用下面的方式
if !allowUserLogin {
// do something
}
</code></pre>
<h3 id="52推荐for">5.2 【推荐】for</h3>
<ul>
<li>采用短声明建立局部变量:</li>
</ul>
<pre><code class="language-go">sum := 0
for i := 0; i < 10; i++ {
sum += 1
}
</code></pre>
<h3 id="53必须range">5.3 【必须】range</h3>
<ul>
<li>如果只需要第一项(key),就丢弃第二个:</li>
</ul>
<pre><code class="language-go">for key := range m {
if key.expired() {
delete(m, key)
}
}
</code></pre>
<ul>
<li>如果只需要第二项,则把第一项置为下划线:</li>
</ul>
<pre><code class="language-go">sum := 0
for _, value := range array {
sum += value
}
</code></pre>
<h3 id="54必须switch">5.4 【必须】switch</h3>
<ul>
<li>要求必须有 <code>default</code>:</li>
</ul>
<pre><code class="language-go">switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
</code></pre>
<h3 id="55-推荐return">5.5 【推荐】return</h3>
<ul>
<li>尽早 <code>return</code>,一旦有错误发生,马上返回:</li>
</ul>
<pre><code class="language-go">f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
d, err := f.Stat()
if err != nil {
return err
}
codeUsing(f, d)
</code></pre>
<h3 id="56-必须goto">5.6 【必须】goto</h3>
<ul>
<li>业务代码禁止使用 <code>goto</code>,其他框架或底层源码推荐尽量不用。</li>
</ul>
<h2 id="6-函数">6. 函数</h2>
<h3 id="61-推荐函数参数">6.1 【推荐】函数参数</h3>
<ul>
<li>函数返回相同类型的两个或三个参数,或者如果从上下文中不清楚结果的含义,使用命名返回,其它情况不建议使用命名返回。</li>
</ul>
<pre><code class="language-go">// Parent1 ...
func (n *Node) Parent1() *Node
// Parent2 ...
func (n *Node) Parent2() (*Node, error)
// Location ...
func (f *Foo) Location() (lat, long float64, err error)
</code></pre>
<ul>
<li>
<p>传入变量和返回变量以小写字母开头。</p>
</li>
<li>
<p>参数数量均不能超过<code>5个</code>。</p>
</li>
<li>
<p>尽量用值传递,非指针传递。</p>
</li>
<li>
<p>传入参数是 <code>map</code>,<code>slice</code>,<code>chan</code>,<code>interface</code> 不要传递指针。</p>
</li>
</ul>
<h3 id="62-必须defer">6.2 【必须】defer</h3>
<ul>
<li>
<p>当存在资源管理时,应紧跟 <code>defer</code> 函数进行资源的释放。</p>
</li>
<li>
<p>判断是否有错误发生之后,再 <code>defer</code> 释放资源。</p>
</li>
</ul>
<pre><code class="language-go">resp, err := http.Get(url)
if err != nil {
return err
}
// 如果操作成功,再defer Close()
defer resp.Body.Close()
</code></pre>
<ul>
<li>禁止在循环中使用 <code>defer</code>,举例如下:</li>
</ul>
<pre><code class="language-go">// 不要这样使用
func filterSomething(values []string) {
for _, v := range values {
fields, err := db.Query(v) // 示例,实际不要这么查询,防止sql注入
if err != nil {
// xxx
}
defer fields.Close()
// 继续使用fields
}
}
// 应当使用如下的方式:
func filterSomething(values []string) {
for _, v := range values {
func() {
fields, err := db.Query(v) // 示例,实际不要这么查询,防止sql注入
if err != nil {
...
}
defer fields.Close()
// 继续使用fields
}()
}
}
</code></pre>
<h3 id="63-推荐方法的接收器">6.3 【推荐】方法的接收器</h3>
<ul>
<li>
<p>【推荐】推荐以类名第一个英文首字母的小写作为接收器的命名。</p>
</li>
<li>
<p>【推荐】接收器的命名在函数超过<code>20行</code>的时候不要用单字符。</p>
</li>
<li>
<p>【必须】命名不能采用 <code>me</code>,<code>this</code>,<code>self</code> 这类易混淆名称。</p>
</li>
</ul>
<h3 id="64-推荐代码行数">6.4 【推荐】代码行数</h3>
<ul>
<li>
<p>【必须】文件长度不能超过<code>800行</code>。</p>
</li>
<li>
<p>【推荐】函数长度不能超过<code>80行</code>。</p>
</li>
</ul>
<h3 id="65-必须嵌套">6.5 【必须】嵌套</h3>
<ul>
<li>嵌套深度不能超过<code>4层</code>:</li>
</ul>
<pre><code class="language-go">// AddArea 添加成功或出错
func (s *BookingService) AddArea(areas ...string) error {
s.Lock()
defer s.Unlock()
for _, area := range areas {
for _, has := range s.areas {
if area == has {
return srverr.ErrAreaConflict
}
}
s.areas = append(s.areas, area)
s.areaOrders = new(order.AreaOrder)
}
return nil
}
</code></pre>
<pre><code class="language-go">// 建议调整为这样:
// AddArea 添加成功或出错
func (s *BookingService) AddArea(areas ...string) error {
s.Lock()
defer s.Unlock()
for _, area := range areas {
if s.HasArea(area) {
return srverr.ErrAreaConflict
}
s.areas = append(s.areas, area)
s.areaOrders = new(order.AreaOrder)
}
return nil
}
// HasArea ...
func (s *BookingService) HasArea(area string) bool {
for _, has := range s.areas {
if area == has {
return true
}
}
return false
}
</code></pre>
<h3 id="66-推荐变量声明">6.6 【推荐】变量声明</h3>
<ul>
<li>变量声明尽量放在变量第一次使用前面,就近原则。</li>
</ul>
<h3 id="67-必须魔法数字">6.7 【必须】魔法数字</h3>
<ul>
<li>如果魔法数字出现超过<code>2次</code>,则禁止使用。</li>
</ul>
<pre><code class="language-go">func getArea(r float64) float64 {
return 3.14 * r * r
}
func getLength(r float64) float64 {
return 3.14 * 2 * r
}
</code></pre>
<ul>
<li>用一个常量代替:</li>
</ul>
<pre><code class="language-go">// PI ...
const PI = 3.14
func getArea(r float64) float64 {
return PI * r * r
}
func getLength(r float64) float64 {
return PI * 2 * r
}
</code></pre>
<h2 id="7-依赖管理">7. 依赖管理</h2>
<h3 id="71-必须go111-以上必须使用-go-modules-模式">7.1 【必须】go1.11 以上必须使用 <code>go modules</code> 模式:</h3>
<pre><code class="language-go">go mod init test.oa.com/group/myrepo
</code></pre>
<h3 id="72-推荐代码提交">7.2 【推荐】代码提交</h3>
<ul>
<li>
<p>建议所有不对外开源的工程的 <code>module name</code> 使用 <code>test.oa.com/group/repo</code> ,方便他人直接引用。</p>
</li>
<li>
<p>建议使用 <code>go modules</code> 作为依赖管理的项目不提交 <code>vendor</code> 目录。</p>
</li>
<li>
<p>建议使用 <code>go modules</code> 管理依赖的项目, <code>go.sum</code> 文件必须提交,不要添加到 .gitignore 规则中。</p>
</li>
</ul>
<h2 id="8-应用服务">8. 应用服务</h2>
<h3 id="81-推荐应用服务接口建议有-readmemd">8.1 【推荐】应用服务接口建议有 <code>README.md</code></h3>
<ul>
<li>其中建议包括服务基本描述、使用方法、部署时的限制与要求、基础环境依赖(例如最低 go 版本、最低外部通用包版本)等。</li>
</ul>
<h3 id="82-必须应用服务必须要有接口测试">8.2 【必须】应用服务必须要有接口测试。</h3>
<h2 id="附常用工具">附:常用工具</h2>
<p>go 语言本身在代码规范性这方面也做了很多努力,很多限制都是强制语法要求,例如左大括号不换行,引用的包或者定义的变量不使用会报错,此外 go 还是提供了很多好用的工具帮助我们进行代码的规范。</p>
<ul>
<li><code>gofmt</code> ,大部分的格式问题可以通过 <code>gofmt</code> 解决, <code>gofmt</code> 自动格式化代码,保证所有的 go 代码与官方推荐的格式保持一致,于是所有格式有关问题,都以 <code>gofmt</code> 的结果为准。</li>
<li><code>goimports</code> ,此工具在 <code>gofmt</code> 的基础上增加了自动删除和引入包。</li>
<li><code>go vet</code> ,<code>vet</code> 工具可以帮我们静态分析我们的源码存在的各种问题,例如多余的代码,提前 <code>return</code> 的逻辑, <code>struct</code> 的 <code>tag</code> 是否符合标准等。编译前先执行代码静态分析。</li>
<li><code>golint</code> ,类似 <code>javascript</code> 中的 <code>jslint</code> 的工具,主要功能就是检测代码中不规范的地方。</li>
</ul>
</div>
<div id="MySignature" role="contentinfo">
I can see a bigger world.<br><br>
来源:https://www.cnblogs.com/xuweiqiang/p/15337132.html
頁:
[1]