go语言风格培训
参考资料
- go官方:https://github.com/golang/go/wiki/CodeReviewComments
Declaring Empty Slices
good | bad |
---|---|
var t []string | t := []string{} |
- 注:前者是个nil类型slice 而后者是0长度类型,在json编码的时候会不一样,前者会是null而后者是[]
Error Strings
- error string不应该开头大写,除非是以专有名词或首字母缩写开头。也不应该以标点符号结束。
- 日志则不需要符合上面的要求,因为日志直接写一行而且不会有合并的情况。
good | bad |
---|---|
use fmt.Errorf(“something bad”) | fmt.Errorf(“Something bad.”) |
Handle Errors
- 不要使用 _ 来忽略错误,如果函数会返回错误,必须检查这个错误,并作出正确的处理:报错、返回、记录后无视、或者在必要的时候panic。
Imports
- 避免包重名,如果重名,则更改最本地的包。 import要分组,系统包要在第一组,如下:
package main
import (
"fmt"
"hash/adler32"
"os"
"appengine/foo"
"appengine/user"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
Indent Error Flow
- 只有err需要使用的时候
GOOD:
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
BAD:
if err != nil {
// error handling
} else {
// normal code
}
- 需要处理正常返回结果的时候
GOOD:
x, err := f()
if err != nil {
// error handling
return
}
// use x
BAD:
if x, err := f(); err != nil {
// error handling
return
} else {
// use x
}
Initialisms
- 关于缩写:大小写一致。比如 URL 或者url 不要写成Url 。同理 ServeHTTP 不应ServeHttp、 appID 不应appId
good | bad |
---|---|
sql.DB | sql.SqlDB |
http.Server | http.HTTPServer |
list.List | list.NewList |
Package Names
- 避免如下包名: util, common, misc, api, types, and interfaces.
- 包名应该小写,没有大写或者下划线。 包名和包内成员、函数不要有重,比如包名为chubby则其内部的全局变量应为File 而不是ChubbyFile
- 不用复数。 比如 net/url 不是net/urls
File Name
- 必要用驼峰,最好用也不用下划线,全部小写。
- 对于测试文件或者平台、CPU类型、显卡类型等可以使用下划线
Chan
- 在不在乎channel类型的情况下应使用
chan struct{}
Receive Type
- 如果接收器是 map,func或 chan,则不要使用指向它们的指针。如果接收器是 slice并且该方法不重新切片或不重新分配切片,则不要使用指向它的指针。
- 如果该方法需要改变接收器的值,则接收器必须是指针。
- 如果接收器是包含 sync.Mutex 或类似同步字段的 struct,则接收器必须是指针,以避免复制。
- 如果接收器是大型结构或数组,则指针接收器更有效。多大才算大?假设它相当于将其包含的所有元素作为参数传递给方法。如果感觉太大,那么对接收器来说也太大了。
- 函数或方法可以改变接收器吗(并发调用或调用某方法时继续调用相关方法或函数)?在调用方法时,值类型会创建接收器的副本,因此外部更新将不会应用于此接收器。如果必须在原始接收器中看到更改效果,则接收器必须是指针。
- 如果接收器是 struct,数组或 slice,并且其任何元素是指向可能改变的对象的指针,则更倾向于使用指针接收器,因为它将使读者更清楚地意图。
- 如果接收器是一个小型数组或 struct,那么它自然是一个值类型(例如,类似于time.Time类型),对于没有可变字段,没有指针的类型,或者只是一个简单的基本类型,如 int 或 string,值接收器是合适的。值接收器可以减少可以生成的垃圾量;如果将值作为参数传递给值类型方法,则可以使用堆栈上的副本而不需要在堆上进行分配。(编译器试图避免这种分配,但它不能总是成功)因此,在没有进行分析之前,不要选择值接收器类型。
- 最后,如果以上没有解决你的问题,仍然对选择哪种方式有困惑,请使用指针接收器。
Interface assert
- 需要对interface的类型做判断,否则会在类型错误时panic
GOOD:
t,ok := i.(string)
if !ok {
//xxxx
}
BAD:
t := i.(string) // panic when i is not string, such as nil
Switch
- switch的时候必须要吧所有的可能性都包括到,特别是枚举值的情况下,必要时需要加上default
switch signal {
case syscall.SIGHUP:
// xxxx
case syscall.SIGQUIT,syscall.SIGTERM,syscall.SIGINT:
// xxx
default:
// xxx
}
Table Driven Tests
package main
import (
"testing"
)
func TestGetCurrTasksCount(t *testing.T) {
testCases := []struct {
name string
appID int
speed int
learnedWords int64
todayLearnCnt int64
expect int
}{
{
name: "测试今天任务数不满足用户设置每日速度",
appID: context.APPPinYin,
speed: 10,
learnedWords: 40,
todayLearnCnt: 8,
expect: 10,
},
{
name: "测试今天任务数满足用户设置每日速度",
appID: context.APPPinYin,
speed: 10,
learnedWords: 60,
todayLearnCnt: 1,
expect: 4,
},
{
name: "测试已经没有可学任务了",
appID: context.APPPinYin,
speed: 10,
learnedWords: 63,
todayLearnCnt: 0,
expect: 0,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
resp, err := getCurrTasksCount(test.NewCtx(), tt.appID, tt.speed, tt.learnedWords, tt.todayLearnCnt)
require.Nil(t, err)
require.Equal(t, tt.expect, resp)
})
}
}