Home Golang 开发规范 -- Go Code Review Comments
Post
Cancel

Golang 开发规范 -- Go Code Review Comments

疫情期间入职新公司一个月了,部门每月会有知识分享,由大家轮流进行! 我们的主力开发语言由 PHP 在逐步转向Go,参考 Go Code Review Comments 在上一次的部门分享我做的是 Go 语言的开发规范,在官方的基础上做了一个简单的分类,供大家参考共同学习。

格式化

gofmt

通过gofmt自动格式化代码,以保证所有的go代码与官方推荐的格式保持一致。

首字母大写和缩写

当一个单词在代码中,可以是全小写的。也可以选择首字母大写,或者缩写。值得注意的是,一旦该单词选择了首字母大写或缩写的风格,就应当在整份代码中保持这种风格,不要首字母大写和缩写两种风格混用。 以 URL 为例,如果选择了缩写 URL 这种风格,则应在整份代码中保持,以下命名都是不错的:URLPonyurlPony;切勿使用 UrlPony 这样的风格。

代码行长度

在 Golang 中,没有严格限制代码行长度,但我们应该尽量避免一行内写过长的代码,以及将长代码进行断行。 每行不超过 80 个字符,依然是一个不错的建议。

尽可能减少正常逻辑代码的缩进

当函数调用返回错误时,我们需要判断错误是否为空,若不为空要进入错误处理的代码,结束后再进入正常逻辑代码。应当尽可能减少正常逻辑代码的缩进,这有利于提高代码的可读性,便于快速分辨出哪些还是正常逻辑代码。

段落引用尽早 return :一有错误发生,马上返回。

这是一个不好的代码风格,正常逻辑代码被缩进在else分支里面:

1
2
3
4
5
if err != nil {
    //error handling
} else {
    //normal code
}

这个是一个不错的代码风格,没有增加正常逻辑代码的缩进:

1
2
3
4
5
if err != nil {
    // error handling
    return // or continue, etc.
}
// normal code

另一种常见的情况,如果我们需要用函数的返回值来初始化某个变量,应该把这个函数调用单独写在一行,例如:

1
2
3
4
5
6
x, err := f()
if err != nil {
    // error handling
    return
}
// use x

命名

文件命名

文件命名一律采用小写,不用驼峰,尽量见名思义,非测试文件禁止出现 *_test.go

包命名

包名应该是全小写单词,不要使用下划线;包名应该尽可能简短,长单词并不有助于可读性,尽量不要与标准库重名。

变量命名

一般采用驼峰,应该尽可能短,尤其是局部变量;对于特殊或全局变量可能需要对它有更多的描述建议使用长命名。

函数与结构体命名

必须为大小写驼峰模式,不要使用下划线,可以长,但是需要把函数功能描述清楚。例如:updateByIdgetUserInfo。 函数名建议动词或者动宾结构单词,结构体建议名词或者动名词。

单个变量、结构体的声明应该使用单行声明,两个或以上使用圆括号声明方式。 枚举常量:使用类型前缀区分(理论上和文件名相同),采用驼峰。

声明空数组切片

这是一个推荐的做法:

1
var t[]string

这是不好的:

1
golang t := []string{}

原因是,前者能避免分配内存空间。有些时候,可能你从没向这个数组分片里面 append 元素。

给函数返回值命名

这是一个不好的代码风格,我们只知道函数返回的类型,但不知道每个返回值的名字:

1
2
func (n *Node) Parent1 *Node
func (n *Node) parent2 (*Node, error)

这是一个不错的代码风格,我们准确知道每个返回值的名字:

1
2
func (n *Node) Parent1 (node *Node)
func (n *Node) parent2 (node *Node, err error)

这条建议几乎不需要过多的解释!尤其对于一种场景,当你需要在函数结束的 defer 中对返回值做一些事情,给返回值名字实在是太必要了。

接受者命名

结构体函数中,接受者的命名不应该采用 methisself 等通用的名字,而应该采用简短的(1或2个字符)并且能反映出结构体名的命名风格。 例如,结构体名为 Client,接受者可以命名为 c 或者 cl。 这样做的好处是,当生成了 godoc 后,过长或者过于具体的命名,会影响搜索体验。

接受者类型

编写结构体函数时,接受者的类型到底是选择值还是指针通常难以决定。

一条万能的建议:如果你不知道要使用哪种传递时,请选择指针传递吧!

以下是一些不错的建议:

  1. 当接受者是 map, chan, func, 不要使用指针传递,因为它们本身就是引用类型。
  2. 当接受者是 slice,而函数内部不会对 slice 进行切片或者重新分配空间,不要使用指针传递。
  3. 当函数内部需要修改接受者,必须使用指针传递。
  4. 当接受者是一个结构体,并且包含了 sync.Mutex 或者类似的用于同步的成员。必须使用指针传递,避免成员拷贝。
  5. 当接受者类型是一个结构体并且很庞大,或者是一个大数组,建议使用指针传递来提高性能。
  6. 当接受者是结构体,数组或 slice,并且其中的元素是指针,并且函数内部可能修改这些元素,那么使用指针传递是个不错的选择,这能使得函数的语义更加明确。
  7. 当接受者是小型结构体,小数组,并且不需要修改里面的元素,里面的元素又是一些基础类型,使用值传递是个不错的选择。

传递值而不是指针

不要为了节省一点空间就传递指针而不是传递值。 除非要传递的是一个庞大的结构体或者可预知在将来会变得非常庞大的结构体,指针是一个不错的选择。

注释

注释应当是一个完整的句子

所有的注释都应该是一个完整的句子。句子应该以主语开头,句号结尾。这样做,能使注释在转化成 godoc 时有一个不错的格式。

文档注释

Go 提供两种注释风格,C 的块注释风格 /**/ ,C++ 的行注释风格 //

每一个包都应该有包注释,位于文件的顶部,在包名出现之前。

如果一个包有多个文件,包注释只需要出现在一个文件的顶部即可。

包注释建议使用 C 注释风格,如果这个包特别简单,需要的注释很少,也可以选择使用 C++ 注释风格。

每个 public 函数都应该有注释,注释句子应该以该函数名开头,如:

1
2
// Encode writes the JSON encoding of req to w.
func Encode(w io.Writer, req *Request) { ...

error处理

不要抛出 panic

尽量不要使用 panic 处理错误。函数应该设计成多返回值,其中含括返回响应的 error 类型。

error 提示

错误提示不需要大写字母开头的单词,即使是句子的首字母也不需要。除非那是个专有名词或者缩写。同时,错误提示也不需要以句号结尾,因为通常在打印完错误提示后还需要跟随别的提示信息。

处理错误

不要将 error 赋值给匿名变量 _(因为你不可以使用匿名变量,当把 error 赋值给匿名变量后,相当于抛弃了这个 error)。

如果一个函数返回 error,一定要检查它是否为空,判断函数调用是否成功。如果不为空,说明发生了错误,一定要处理它。

imports

import 多个包时,应该对包进行分组。同一组的包之间不需要有空行,不同组之间的包需要一个空行。标准库的包应该放在第一组。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
    "fmt"
    "hash/adler32"
    "os"

    "appengine/foo"
    "appengine/user"

    "github.com/foo/bar"
    "rsc.io/goversion/version"
)

流程控制

if

If 条件语句与PHP不同不需要圆括号,省略不必要的 else 语句。(左花括号必须与条件语句在同一行)

1
2
3
4
// if接受初始化变量,约定如下方式建立局部变量
if err := file.Chmod(666); err != nil {
    return err
}

for

采用短声明建立局部变量,并且++或–是操作语句而非表达式

1
for i := 0; i < 10; i++{}

工程根目录规范

1
2
3
4
5
6
7
8
${GOPATH}/src/${GIT_HOST}/{$GIT_USER}|${GIT_GROUP}/${PROJ_ROOT}

GIT_HOST git 仓库 host
GIT_USER git 用户名称  GIT_GROUP 二选一
GIT_GROUP git 工作组名称 GIT_USER 二选一
PROJ_ROOT 项目根

比如我的就在github.com/wkk/goblog

如有需要更正和不足之处请大家Email:wkekai@163.com

参考:Go Code Review Comments

This post is licensed under CC BY 4.0 by the author.

-

Getting Started