k8s Golang代码编码规范

总结下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,一般把它作为第一个Ref

1
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
17
import (
"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

日志字符串,一般不进行首字母大写(除非是专有名词)
use

1
fmt.Errorf("something bad")

not

1
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. 控制流

use

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

not

1
2
3
4
5
if 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 Dot
    import . "fmt"可以省略包名调用包中的函数,这样会使代码可读性变差。除了在以一测试这种情况下尽量不要使用这种导入。
    1
    2
    3
    4
    5
    6
    package foo_test

    import (
    "bar/testutil" // also imports "foo"
    . "foo"
    )

在上面这种情况下,测试文件不能导入package foo因为bar/testutil中已经导入了foo
使用import .表示假装引入了包。这里还是有点不太明白?
- Package Names
所有对包内资源的引用都必须通过包名,所以包名的命名是很重要的而且你可以适当省略掉一些无用的名称。
chubby.Filebetter 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
3
if 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


内建函数newmakenew这个一个用来分配内存的内建函数,但它不初始化内存,只是将其置零。new(T)会为T类型的新项目分配被置零的存储,并且返回它的地址,一个类型为*T的值(返回一个指向新分配的类型为T,值为零的指针)

new([]int)返回一个指向新分配的,被置零的slice结构体的指针,即指向nil slice值的指针。

内建函数make(T, args)new(T)的用途不一样,它只用于来创建slice,map,channel,并且返回一个初始化的的(而不是置零),类型为T`的值。

1
2
var p *[]int = new([]int)
var v []int = make([]int, 100)

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