本规范的层次结构
条目的级别和编号
参考下列规范并结合自己的业务场景
{RULE001} 当明确 expr 为 bool 类型时,禁止使用 == 或 != 与 true/false 比较,应该使用 expr 或 !expr
{RULE002} 判断某个整数表达式 expr 是否为零时,禁止使用 !expr,应该使用 expr == 0
示例:
// GOOD:
var isWhiteCat bool
var num int
if isWhiteCat {
//
}
if num == 0 {
// ...
}
//BAD:
var isWhiteCat bool
var num int
if isWhite == true {
// ...
}
if !num {
// ...
}
2.2.1 Receiver Type
解释
关于 receiver 的定义详见 Receiver 定义:The receiver is specified via an extra parameter section preceeding the method name. That parameter section must declare a single parameter, the receiver. Its type must be of the form T or *T (possibly using parentheses) where T is a type name. The type denoted by T is called the receiver base type; it must not be a pointer or interface type and it must be declared in the same package as the method. The method is said to be bound to the base type and the method name is visible only within selectors for that type.
struct 或者 array 中的元素个数超过 3 个,则认为比较大,反之,则认为比较小
2.2.2 receiver 命名
示例
//GOOD:
// call()和 done()都使用了在上下文中有意义的"c"进行 receiver 命名
func (c Client) call() error {
// ...
}
func (c Client) done() error {
// ...
}
//BAD:
// 1. "c"和"client"命名不一致:done()用了 c,call()用了 client
// 2. client 命名过于冗余
func (c Client) done() error {
// ...
}
func (client Client) call() error {
// ...
}
// 不允许使用 self
func (self Server) rcv() error {
// ...
}
// 不允许使用 this
func (this Server) call() error {
// ...
}
//GOOD:
var t []string
// 指定容量
res := make([]string, 0 ,10)
//定义如下:
type User struct{
Username string
Email string
}
u := User{
Username: "astaxie",
Email: "[email protected]",
}
//BAD:
t := []string{}
res := make([]string)
{RULE011} 对于返回值中的 error,一定要进行判断和处理,不可以使用 ”_“ 变量忽略 error
{RULE012} 逻辑处理中禁用panic,对于其他的package对外的接口不能有panic,只能在包内采用。
{RULE012} 错误描述如果是英文必须为小写,不需要标点结尾
{RULE013} error的处理采用独立的错误流进行处理
//GOOD:
if inputIo, err := ioutil.ReadAll(ctx.Request.Body); err != nil {
// handling
}
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
// 如果返回值需要初始化,则采用下面的方式
x, err := f()
if err != nil {
// error handling
return
}
// use x
//BAD:
// 忽略err不可采取
inputIo, _ := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
// error handling
} else {
// normal code
}
type MyInt int
// GOOD:
func(m MyInt) String() string {
return fmt.Sprint(int(m)) //这是安全的,因为我们内部进行了类型转换
}
//BAD:
func (m MyInt) String() string {
return fmt.Sprint(m) //BUG:死循环
}
参考:http://golang.org/doc/articles/race_detector.html#Race_on_loop_counter
{RULE015} Race on loop counter:注意闭包的调用,在循环中调用函数或者goroutine方法,一定要采用显示的变量调用,不要再闭包函数里面调用循环的参数,
{RULE016} Accidentally shared variable:协程中外层共享变量的使用,如外层中使用的err,协程中依然使用
{RULE017} Unprotected global variable:全局变量如果有修改的操作,需要进行加锁,如Map
//GOOD:
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
fmt.Println(j) // Good. Read local copy of the loop counter.
wg.Done()
}(i)
}
wg.Wait()
}
func main() {
f1, err := os.Create("file1")
if err != nil {
res <- err
} else {
go func() {
// This err is shared with the main goroutine,
// so the write races with the write below.
_, err := f1.Write(data)
res <- err
f1.Close()
}()
}
}
//BAD:
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i)
// Not the 'i' you are looking for.
wg.Done()
}()
}
wg.Wait()
}
func main() {
f1, err := os.Create("file1")
if err != nil {
res <- err
} else {
go func() {
// This err is shared with the main goroutine,
// so the write races with the write below.
_, err = f1.Write(data)
res <- err
f1.Close()
}()
}
}
{RULE018}业务中引入任何第三包之前,需要调研对比各个包之间的性能差异,使用范围(如star数量)
如json包:官方包encoding/json的性能较差,选用: jsoniter “github.com/json-iterator/go” 效率更高
解释
示例
//GOOD:
type Automobile struct {
// ...
}
type Engine struct {
// ....
}
// 正确的定义
type Car struct {
Automobile // Car is a Automobile engine 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
}
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
}
{RULE023} 建议文件按以下顺序进行布局
{RULE024} 对于以上的各个部分,采用单个空行分割,同时:
{ADVICE007} 函数内不同的业务逻辑处理建议采用单个空行分割
{ADVICE008} 常量或者变量如果较多,建议按照业务进行分组,组间用单个空行分割
示例
//GOOD:
/* Copyright 2015 Webben Inc. All Rights Reserved. */
/* bfe_server.go - the main structure of bfe-server */
/* modification history -------------------- 2014/6/5, by Zhang San, create*/
/* DESCRIPTION This file contains the most important struct 'BfeServer' of go- bfe and new/init method of the struct.*/
package bfe_server
// imports
import (
"fmt"
"time"
"code.google.com/p/log4go"
"bfe_config/bfe_conf"
"bfe_module"
)
const (
version = "1.0.0.0"
)
// typedefs
type BfeModule interface {
Init(cfg bfe_conf.BfeConfig, cbs *BfeCallbacks) error
}
type BfeModules struct {
modules map[string]BfeModule
}
// vars
var errTooLarge = errors.New("http: request too large")
//functions
func foo() {
//...
}
解释
Title 中包括文件的名称和文件的简单说明
Modification History 记录文件的修改过程,并且只记录最主要的修改
Description 详细描述文件的功能和作用
示例
//GOOD:
/* Copyright 2015 Webben Inc. All Rights Reserved. */
/* bfe_server.go - the main structure of bfe-server */
/* modification history -------------------- 2014/6/5, by Zhang San, create*/
/* DESCRIPTION This file contains the most important struct 'BfeServer' of go- bfe and new/init method of the struct.*/
package bfe_server func func1() {
// ...
}
//BAD:
package bfe_server func func1() {
// ...
}
示例:
//GOOD:
import (
"fmt"
"time"
)
import (
"code.google.com/p/log4go"
)
import (
"bfe_config/bfe_conf"
"bfe_module"
)
//GOOD:
import (
"fmt"
"time"
"code.google.com/p/log4go"
"bfe_config/bfe_conf"
"bfe_module"
)
//BAD:
// 同一类 package 内 import 顺序需按字母升序排序
import (
"time"
"fmt"
)
// 不同类的 package import 顺序出错(先第三方 package,再程序自己的 package)
import (
"bfe_config/bfe_conf"
"bfe_module"
)
import (
"code.google.com/p/log4go"
)
//BAD:
import (
// 同一类 package 内 import 顺序出错
"time"
"fmt"
// 不同类的 package import 顺序出错(先第三方 package,再程序自己的 package)
"bfe_config/bfe_conf"
"bfe_module"
"code.google.com/p/log4go"
)
3.4 Go 函数 Layout
3.4.1 函数注释
示例
/**
Init - initialize log lib
** PARAMS:
* - levelStr: "DEBUG", "TRACE", "INFO", "WARNING", "ERROR", "CRITICAL"
* - when: "M", minute* "H", hour* "D", day
* "MIDNIGHT", roll over at midnight
* - backupCount: If backupCount is > 0, when rollover is done, no more than
* backupCount files are kept - the oldest ones are deleted.
** RETURNS:
* nil, if succeed
* error, if fail
**/
func Init(levelStr string, when string, backupCount int) error {
// ...
}
3.4.2 函数参数和返回值
示例:
//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) {
// ...
}
解释
3.6.1 文件名
示例:
//GOOD:
// 可以使用下划线分割文件名web_server.go
// 文件名全部小写http.go
//BAD:
// 文件名不允许出现大写字符webServer.go
// 文件名后缀不允许出现大写字符http.GO
3.6.2 函数名/变量名
示例:
//GOOD:
// 本 package 可以访问的函数
func innerFunc() bool {
// ...
}
// 其余 package 可以访问的函数
func OuterFunc() error {
// ...
}
//BAD: // 禁止用下划线分割
func inner_Func() bool {
var srv_name string
// ...
}
// 禁止用下划线分割
// 其余 package 可以访问的函数
func Outer_Func() error {
// ...
}
3.6.3 常量
解释
示例
//GOOD:
// 大写字母
const METHOD = "Get"
// 下划线分割
const HEADER_USER_AGENT = "User-Agent"
// go 标准库中的命名方式
const defaultUserAgent = "Go 1.1 package http"
//BAD:
// 全部为下划线分割的小写字母
const header_user_agent = "User-Agent"
3.6.4 缩写词
示例:
//GOOD:
var URL string
var ID int
var appID int
type ServeURL struct {
// ...
}
//BAD:
var Url string
// not consistent
var ID int
var appid int
type ServUrl struct {
// ...
}
解释
//GOOD:
var (
s = make([]int, 10)
)
func foo() {
m := map[string]string{"language": "golang"}
r := 1 + 2
func1(1+2)
fmt.Println(m["language"])
}
//BAD:
var ( s = make( []int , 10 ))
func foo() {
m := map[string]string{ "language" : "golang" }
r := 1+2
func1(1 + 2)
fmt.Println(m[ "language" ])
}
//GOOD:
if x {}
func func1() int {
var num int
return num
}
//BAD:
if (x) {
}
func func1 int {
var num int
return (num)
}
解释
示例
//GOOD:
/* This is the correct format for a single-line comment */
// This is the correct format for a single-line comment
/** This is the correct format for a multiline comment* in a section of code.*/
// This is the correct format for a multiline comment
// in a section of code. var a int
// this is the correct format for a
// multiline comment in a declaration
//BAD:
/* This is an incorrect format for a multiline comment* in a section of code.*/
var a int /* this is an incorrect comment format */
//GOOD:
fmt.Errorf("something bad")
//BAD:
fmt.Errorf("Something bad")
{ADVICE026} 如果临界区内的逻辑较复杂、无法完全避免 panic 的发生,则要求适用 defer 来调用Unlock,即使在临界区过程中发生了 panic,也会在函数退出时调用 Unlock 释放锁
解释:go 提供了 recover,可以对 panic 进行捕获,但如果 panic 发生在临界区内,则可能导致对锁的使用没有释放
这种情况下,即使 panic 不会导致整个程序的奔溃,也会由于”锁不释放“的问题而使临界区无法被后续的调用访问
示例
//GOOD:
func doDemo() {
lock.Lock()
defer lock.Unlock()
// 访问临界区
}
//BAD:
func doDemo() {
lock.Lock()
// 访问临近区
lock.Unlock()
}
{ADVICE027} 上述操作如果造成临界区扩大后,需要建立单独的一个函数访问临界区。
对于如下的代码:
func doDemo() {
lock.Lock()
// step1: 临界区内的操作
lock.Unlock()
// step2: 临界区外的操作
}
//如果改造为 defer 的方式,变为如下代码,实际上扩大了临界区的范围(step2 的操作也被放置在临界区了)
func doDemo() {
lock.Lock()
defer lock.Unlock()
// step1: 临界区内的操作
// step2: 临界区外的操作
}
//优化:需要使用单独的匿名函数,专门用于访问临界区:
func doDemo() {
func() {
lock.Lock()
defer lock.Unlock()
// step1: 临界区内的操作操作
}()
// step2: 临界区外的操作
}
示例
// ToString 把 []byte 转换为 string 没有多余的内存开销。
// 使用该方法需要了解到 []byte 将和 string 公用一块内存, 修改 []byte 的数据将导致 string 发生变化,
// 这打破了字符串不可以修改的特性,如果你恰恰需要这么做,可能非常的有用。
// 要保证字符串不可变的特性,就必须保证 []byte 不会发生变化,或者立即消费 string,
func ToString(s []byte) string {
return *(*string)(unsafe.Pointer(&s))
}
//使用该函数要明确两个事情:
// - 确定字符串是否是字面量,或者内存被分配在只读空间上。
// - 确保访问该函数结果的函数是否会按照你的意愿访问或修改数据。
func UnsafeToBytes(s string) []byte {
strHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: strHeader.Data,
Len: strHeader.Len,
Cap: strHeader.Len,
}))
}
{RULE043} 业务代码中禁止直接使用go关键字,避免启动的协程不可控,使用 go-common 包提供的协程池进行相关的操作,限制收敛协程的数量,协程池使用说明:[gouroutine使用说明]
{RULE044} 业务代码中禁止使用chan管道,避免造成死锁和性能损失,有需要使用管道的地方,向基础组件同学提相关的需求
{RULE045} 使用常量值做为slice的索引取值时,要注意越界问题,需要判断len(arr) > index
{RULE046} 了解slice的append底层原理,容量不够时会另开空间,在函数传递时使用slice,注意函数内部使用append后,另开空间,函数外层无感知
BAD:
//GOOD:
type cacheLevel int
const (
//多级缓存开启策略配置
USE_ONLY_STORED cacheLevel = 1 //仅仅开启第二层stored分布式缓存,关闭第三层redis缓存
USE_ONLY_REDIS cacheLevel = 2 //仅仅开启第三层redis缓存,关闭第二层stored分布式缓存
USE_STORED_REDIS cacheLevel = 3 //开启第二层stored,第三层redis缓存
)
if level == USE_ONLY_STORED {
//...
}
//BAD:
if level == 1 {
//...
}