总结下k8s项目编码规范,加深记忆。
编码规范可参阅Effective Go.
1.Gofmt
所有的代码都必须使用gofmt
进行格式化,可选的工具还包括goimports
(可看作gofmt的超集)
2.注释语句
注释必须完整,清楚,允许一定的冗余。注释应该必须以描述的资源开头。
比如你注释的是一个函数就应该以函数名开头,注释的是一个结构体就应该以结构体的名称开头并以.
结尾
1
2
3
4
5 // Request represents a request to run a command.
type Request struct { ...
// Encode writes the JSON encoding of req to w.
func Encode(w io.Writer, req *Request) { ...
3.Context
如果一个函数中有参数类型为Context
,一般把它作为第一个Ref1
func F(ctx context.Context, /* other arguments */) {}
4.复制
To avoid unexpected aliasing, be careful when copying a struct from another package. For example, the bytes.Buffer type contains a []byte slice. If you copy a Buffer, the slice in the copy may alias the array in the original, causing subsequent method calls to have surprising effects.
In general, do not copy a value of type T if its methods are associated with the pointer type, *T.
5.Crypto Rand
不要使用math/rand
来生成私钥,即使只生成一次。如果没有设置种子,生成器生成的随机数完成是可预测的。使用time.Nanosecond()
作为种子,种子差异比较小,使用crypto/rand
来生成。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import (
"crypto/rand"
// "encoding/base64"
// "encoding/hex"
"fmt"
)
func Key() string {
buf := make([]byte, 16)
_, err := rand.Read(buf)
if err != nil {
panic(err) // out of randomness, should never happen
}
return fmt.Sprintf("%x", buf)
// or hex.EncodeToString(buf)
// or base64.StdEncoding.EncodeToString(buf)
}
6.空切片
定义一个空切片,使用var t []string
而不是t := []string{}
。前者描述的是一个空的切片,后者是non-nil但长度为0的切片。两者在功能上是一样的(len和cap都为0),但是我们倾向于使用前者。
当编码为一个JSON
对象时,nil
—> null,[]string{}
—> JSON array []
。
7.文档注释-Doc Comments
All top-level, exported names should have doc comments, as should non-trivial unexported type or function declarations. See https://golang.org/doc/effective_go.html#commentary for more information about commentary conventions.
8.Don’t Panic
不要使用panic
处理普通的错误。使用多返回值进行处理。
9.Error Strings
日志字符串,一般不进行首字母大写(除非是专有名词)
use1
fmt.Errorf("something bad")
not1
fmt.Errorf("Something bad")
10.示例
当我们新增一个package
时,需要提供一些可运行的例子或者简单的测试例子。
https://blog.golang.org/examples
11.Goroutine生命周期
Goroutine会导致内存泄漏当channel阻塞于发送或接收:垃圾收集器并不会终止Goroutine即使channel一直阻塞。
尽量使并发代码简单明了,Goroutine生命周期明显。如果过于复杂,可以添加注释表明Goroutine何时和为什么退出.
12.处理错误
https://golang.org/doc/effective_go.html#errors
13. Imports
避免重命名imports,除非为了避免命名冲突;好的模块名一般不需要进行重命名。
对imports进行分类排序,goimports
可以帮你完成这个操作。
14. 控制流
use1
2
3
4
5if err != nil {
// error handling
return // or continue, etc.
}
// normal code
not1
2
3
4
5if err != nil {
// error handling
} else {
// normal code
}
15. 首字母缩略词
专有名词缩略词采用全大写。
如:URL
,URLPony
,ServeHTTP
,appID
16.Interfaces
Go接口通常属于使用接口类型的值的包,而不是实现这些值的包。 实现包应返回具体的(通常是指针或结构)类型:这样,可以将新方法添加到实现中,而无需进行大量重构。
不要在“用于模拟”的API的实现者端定义接口; 而是设计API,以便可以使用实际实现的公共API对其进行测试。
在使用接口之前,不要先定义它们:如果没有实际的用法示例,很难知道接口是否是必需的,更不用说接口应该包含什么方法了。
17.模块注释
模块注释必须在package main
上面,而且不能有空格。1
2// Package math provides basic constants and mathematical functions.
package math
18.模块
- Import Blank
import _ "pkg
,_
操作其实是引入该包,不直接使用包里面的函数,而是调用了该包里的init函数。不要这样使用,除非在main package
中或者测试需要
- Import Dotimport . "fmt"
可以省略包名调用包中的函数,这样会使代码可读性变差。除了在以一测试这种情况下尽量不要使用这种导入。1
2
3
4
5
6package foo_test
import (
"bar/testutil" // also imports "foo"
. "foo"
)
在上面这种情况下,测试文件不能导入package foo
因为bar/testutil
中已经导入了foo
。
使用import .
表示假装引入了包。这里还是有点不太明白?
- Package Names
所有对包内资源的引用都必须通过包名,所以包名的命名是很重要的而且你可以适当省略掉一些无用的名称。
如chubby.File
better than chubby.ChubbyFile
。
19.传值
不要试图通过传递指针来节省空间。常见的实例包括传递指向字符串的指针( string)或指向接口值的指针( io.Reader)。 在这两种情况下,值本身都是固定大小,可以直接传递。 此建议不适用于大型struct
,甚至不适用于可能增长的小型struct
。
20.接收者名称与接收者类型
- 接收者名称
接收者名称必须使用有意义的名称,不能使用一些通用的名称,如me,this,self
等。
- 接收者类型
1.如果接收者是map,func,chan
,不要使用指针。如果接收者是slice
而且函数没有对slice
进行reslice和reallocate操作,不要使用指针。
2.如果函数需要对接收都进行修改,则必须使用指针。
3.如果接收者是一个结构体并且包含sync.Mutext
或者类似的同步字段,必须使用指针防止拷贝。
4.如果接收者是大的数组,结构体,使用指针会更高效。
Assume it’s equivalent to passing all its elements as arguments to the method. If that feels too large, it’s also too large for the receiver。
5.如果接收者是struct,array,slice
,它们其中有字段指向可变的结构,最好使用指针。
6.如果接收方是一个很小的数组或结构,自然是一个值类型(例如,诸如time.Time类型),没有可变字段且没有指针,或者仅仅是一个简单的基本类型(如int或string),则value
接收者是有道理的。 值接收器可以减少可以生成的垃圾数量; 如果将值传递给value方法,则可以使用堆栈上的副本来代替在堆上分配。 (编译器会尽量避免这种分配,但是它不可能总是成功。)由于这个原因,请勿在没有进行概要分析的情况下选择值接收器类型。
7.如果不确认使用如种类型,使用指针接收者。
21.同步函数
最好使用同步函数,同步函数直接返回结果,在返回之前完成所的调用或channel ops
。同步函数更容易使用goroutine
,更好的避免内存泄漏和数据竞争。还有一个好处就是使用同步方法可以更好的进行测试。如果调用者需要更高的并发性,可以将这个改造成一个单独的goroutine
。
22.出现错误必须提供有用的信息
对必要的地方,可能出错的地方打印日志。应该包含如下信息什么错误,输入是什么,实际的结果是什么,预期的结果是什么
。1
2
3if got != tt.want {
t.Errorf("Foo(%q) = %d; want %d", tt.in, got, tt.want) // or Fatalf, if test can't test anything more past this point
}
23.变量名称
在Go
中变量尽量简单明了。对于有限作用域的局部变量,尽量使用简短的变量。如lincCount
—>c
,sliceIndex
—>i
。
内建函数new
和make
。new
这个一个用来分配内存的内建函数,但它不初始化内存,只是将其置零。new(T)
会为T
类型的新项目分配被置零的存储,并且返回它的地址,一个类型为*T
的值(返回一个指向新分配的类型为T
,值为零的指针)
。new([]int)
返回一个指向新分配的,被置零的slice
结构体的指针,即指向nil slice
值的指针。
内建函数make(T, args)
与new(T)的用途不一样,它只用于来创建
slice,map,channel,并且返回一个初始化的的(而不是置零),类型为
T`的值。
1 | var p *[]int = new([]int) |
Ref:
1.https://github.com/kubernetes/community/blob/master/contributors/guide/coding-conventions.md
2.effective-go
3.https://godoc.org/golang.org/x/tools/cmd/goimports