注释
可以通过 /……/ 或者 // ……增加注释, //之后应该加一个空格。
如果你想在每个文件中的头部加上注释,需要在版权注释和 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 == 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.ContainsRune
、strings.ContainsAny
、strings.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底层的数据结构是一致的。
复制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函数:
不要在for中使用多此一举的true
不要这样:
而是要这样:
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 {
}
|
可以简化为
对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
下面的代码经常会用到:
可以简写为:
掐头去尾
不要自己判断字符串是否以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 语言编码规范