注释

可以通过 /……/ 或者 // ……增加注释, //之后应该加一个空格。

如果你想在每个文件中的头部加上注释,需要在版权注释和 Package前面加一个空行,否则版权注释会作为Package的注释。

注释应该用一个完整的句子,注释的第一个单词应该是要注释的指示符,以便在godoc中容易查找。

注释应该以一个句点.结束。

正例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// EOF is the error returned by Read when no more input is available.
// If the EOF occurs unexpectedly in a structured data stream,
// the appropriate error is either ErrUnexpectedEOF or some other error
// giving more detail.

/**
 * ErrShortWrite means that a write accepted fewer bytes than requested
 * but failed to return an explicit error.
 */

// Seek whence values.
const (
	SeekStart = 0 // seek relative to the origin of the file
)

反例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//Copyright 2009 The Go Authors. All rights reserved.
//Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
//EOF is the error returned by Read when no more input is available.
// If the EOF occurs unexpectedly in a structured data stream,the appropriate error is either ErrUnexpectedEOF or some other error
//giving more detail.

/**ErrShortWrite means that a write accepted fewer bytes than requested
 * but failed to return an explicit error.*/

//Seek whence values.
const (
	SeekStart = 0//seek relative to the origin of the file
)

函数注释开头写上函数的名称,再进行对函数注释。

正例:

1
2
3
4
// SelectOneUser 获取一个用户记录
func SelectOneUser() (*model.User, error) {
    ...
}

反例:

1
2
3
4
//获取一个用户记录
func SelectOneUser() (*model.User, error) {
    ...
}

变量/常量名

一律采用驼峰命名方式。常量、变量都统一采用驼峰命名,公用对象首字母需大写,私有对象首字母可小写。

有些特定名词或缩写名词,建议全部大写,如 HTML、XML、JSON、ID、UID、API、POST 等,但不是特定名词,请不要全部大写,包括常量和变量,避免全部大写,或者全部大写和下划线组合。

正例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const (
    UserID           = 100001
    DefaultCharset  = "utf-8"
    ApplicationJSON = "application/json"
    TextHTML        = "text/html"
    TextXML         = "text/xml"
)

var (
	Expiration = 15 * time.Minute
)

func init() {
    ...
}

func MyProduct() {
    ...
}

反例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 以下都是不规范的命名
const (
    UserId           = 100001
    USERID           = 100001
    DefaultCharset  = "utf-8"
    ApplicationJson = "application/json"
    TextHtml        = "text/html"
    Text_Html       = "text/html"
    TEXTXML         = "text/xml"
    TEXT_XML        = "text/xml"
)

var (
	EXPIRATION = 15 * time.Minute
)

func INIT() {
    ...
}

func My_product() {
    ...
}

文件名

  • 文件名都使用小写字母,如果需要,可以使用下划线分割
  • 文件名的后缀使用小写字母
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// GOOD
// 可以使用下划线分割文件名
web_server.go

// 文件名全部小写
http.go

// BAD
// 文件名不允许出现大写字符
webServer.go

// 文件名后缀不允许出现大写字符
http.GO

项目名

项目名优先采用小写名词命名方式,避免动词和下划线;文件名同理也优先采用小写名词命名方式,有时候可以适当采用下划线,尽量使用单数形式,避免复数名词。都规避采用 Go 关键字和预定的标识符。

正例:

1
2
3
4
5
// 项目名
project
// 文件名
article.go
article_test.go

反例:

1
2
3
4
5
// 项目名
Project
// 文件名
Article.go
ArticleTest.go

空字符串检查

不要使用下面的方式检查空字符串:

1
2
3
if len(s) == 0 {
	...
}

而是使用下面的方式

1
2
3
if s == "" {
	...
}

下面的方法更是语法不对:

1
2
3
if s == nil || s == "" {
	...
}

空slice检查

不要使用下面的方式检查空的slice:

1
2
3
if s != nil && len(s) > 0 {
    ...
}

直接比较长度即可:

1
2
3
if len(s) > 0 {
    ...
}

同样的道理也适用 map和channel。

bool值检查

对于bool类型的变量var b bool,直接使用它作为判断条件,而不是使用它和true/false进行比较

1
2
3
4
5
6
if b {
    ...
}
if !b {
    ...
}

而不是

1
2
3
4
5
6
if b == true {
    ...
}
if b == false {
    ...
}

byte/string slice相等性比较

不要使用

1
2
3
4
5
   var s1 []byte
   var s2 []byte
   ...
bytes.Compare(s1, s2) == 0
bytes.Compare(s1, s2) != 0

而是:

1
2
3
4
5
   var s1 []byte
   var s2 []byte
   ...
bytes.Equal(s1, s2) == 0
bytes.Equal(s1, s2) != 0

检查是否包含子字符串

不要使用 strings.IndexRune(s1, 'x') > -1及其类似的方法IndexAny、Index检查字符串包含, 而是使用strings.ContainsRunestrings.ContainsAnystrings.Contains来检查。

使用类型转换而不是struct字面值

对于两个类型:

1
2
3
4
5
6
7
8
type t1 struct {
	a int
	b int
}
type t2 struct {
	a int
	b int
}

可以使用类型转换将类型t1的变量转换成类型t2的变量,而不是像下面的代码进行转换

1
2
v1 := t1{1, 2}
_ = t2{v1.a, v1.b}

应该使用类型转换,因为这两个struct底层的数据结构是一致的。

1
_ = t2(v1)

复制slice

不要使用下面的复制slice的方式:

1
2
3
4
5
6
7
var b1, b2 []byte
	for i, v := range b1 {
		b2[i] = v
	}
	for i := range b1 {
		b2[i] = b1[i]
    }

而是使用内建的copy函数:

1
copy(b2, b1)

不要在for中使用多此一举的true

不要这样:

1
2
for true {
}

而是要这样:

1
2
for {
}

append slice

不要这样:

1
2
3
4
var a, b []int
for _, v := range a {
	b = append(b, v)
}

而是要这样

1
2
var a, b []int
b = append(b, a...)

简化range

1
2
3
4
5
var m map[string]int
for _ = range m {
}
for _, _ = range m {
}

可以简化为

1
2
for range m {
}

对slice和channel也适用。

正则表达式中使用raw字符串避免转义字符 在使用正则表达式时,不要:

1
2
regexp.MustCompile("\\.")
regexp.Compile("\\.")

而是直接使用raw字符串,可以避免大量的\出现:

1
2
regexp.MustCompile(`\.`)
regexp.Compile(`\.`)

简化只包含单个case的select

1
2
3
select {
	case <-ch:
}

直接写成<-ch即可。send也一样。

1
2
3
4
5
6
   for {
	select {
	case x := <-ch:
		_ = x
	}
}

直接改成 for-range即可。

这种简化只适用包含单个case的情况。

slice的索引

有时可以忽略slice的第一个索引或者第二个索引:

1
2
3
var s []int
_ = s[:len(s)]
_ = s[0:len(s)]

可以写成s[:]

使用time.Since

下面的代码经常会用到:

1
_ = time.Now().Sub(t1)

可以简写为:

1
_ = time.Since(t1)

掐头去尾

不要自己判断字符串是否以XXX开头或者结尾,然后自己再去掉XXX,而是使用现成的strings.TrimPrefix/strings.TrimSuffix。

1
2
3
4
5
6
var s1 = "a string value"
var s2 = "a "
var s3 string
if strings.HasPrefix(s1, s2) {
	s3 = s1[len(s2):]
}

可以简化为

1
2
3
var s1 = "a string value"
var s2 = "a "
var s3 = strings.TrimPrefix(s1, s2)

内嵌结构体

  • 内嵌结构体只用于”is a”的语义下,而不用于”has a”的语义下
  • 一个定义内,多于一个的内嵌结构体尽量少用

解释:

  • 语义上内嵌结构体是一种“继承关系“,而不是”成员关系“
  • 一个定义内有多个内嵌结构体,则很难判断某个成员变量或函数是从哪里继承得到的
  • 一个定义内有多个内嵌结构体,危害和在python中使用from xxx import *是类似的

内嵌结构体只是一种“语法”上的shortcut,并没有规定“语义”上代表什么,所以不能从C++形而上学,认为所有多内嵌结构体均为“多继承”;另外,不提倡多内嵌结构体可能会降低golang的表达力,因此作为规范应当慎重采纳。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// GOOD
type Automobile struct {
    // ...
}

type Engine struct {
    // ....
}

// 正确的定义
type Car struct {
    Automobile         //  Car is a Automobile
    engine Engine      //  Car has a Engine
}

// BAD
type Car struct {
    Automobile      //  Car is a Automobile
    Engine          //  Car has a Engine, but Car is NOT a Engine
}

函数参数和返回值

  • 对于“逻辑判断型”的函数,返回值的意义代表“真”或“假”,返回值类型定义为bool
  • 对于“操作型”的函数,返回值的意义代表“成功”或“失败”,返回值类型定义为error
    • 如果成功,则返回nil
    • 如果失败,则返回对应的error值
  • 对于“获取数据型”的函数,返回值的意义代表“有数据”或“无数据/获取数据失败”,返回值类型定义为(data, error)
    • 正常情况下,返回为:(data, nil)
    • 异常情况下,返回为:(data, error)
  • 函数返回值小于等于3个,大于3个时必须通过struct进行包装
  • 函数参数不建议超过3个,大于3个时建议通过struct进行包装
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// GOOD
type student struct {
    name     string
    email    string
    id       int
    class    string
}

// bool作为逻辑判断型函数的返回值
func isWhiteCat() bool {
    // ...
}

// error作为操作型函数的返回值
func deleteData() error {
    // ...
}

// 利用多返回值的语言特性
func getData() (student, error) {
    // ...
}

// BAD
type student struct {
    name     string
    email    string
    id       int
    class    string
}

// 使用int而非bool作为逻辑判断函数的返回值
func isWhiteCat() int {
    // ...
}

// 操作型函数没有返回值
func deleteData() {
    // ...
}

// 没有充分利用go多返回值的特点
func getData() student {
    // ...
}

// 返回值>3
func getData() (string, string, int, string, error) {
    // ...
}

禁止无recover的go func

禁止无recover的go func

批量接口要限制数组数量

通常为100,200.超出限制要返回错误.

参考:

编写地道的Go代码 用 Go 开发终端接口服务–Go 语言编码规范