代码风格是规范代码的共同约定,本规范旨在描述编写Go代码时的注意事项,通过这些规范的定义以提高代码的可维护性及工程师的心智负担,也可高效的使用Go的特性。
规范第一版参见《Go语言开发规范》,第二版补充了大量代码风格规范,并加入了项目工程结构建议及说明。
规范基于Go的通用准则,并加入了大型开源项目如k8s等的规范。依赖的外部准则:
Effecitve Go(https://golang.org/doc/effective_go.html)
The Go Common mistakes Guide (https://github.com/golang/go/wiki/CodeReviewComments)
Uber Go Style(https://github.com/uber-go/guide/blob/master/style.md)
所有代码都应该通过 golint
和 go vet
检查,建议在IDE中设置:
保存时自动运行
goimports自动运行
golint
和go vet
来检查错误
风格规范中 【强制】
是必须遵循的规范,【建议】
则是建议遵循的规范
代码风格
基本格式
【强制】代码最大行宽为120列,超过换行
【强制】代码提交必须经过
goimports
格式化
声明&命名
【强制】变量、函数名命名必须统一采用规范的英文命名;禁止采用拼音等方式;禁止以特殊字符作为命名的开始或结束
【强制】变量、函数、包内非导出的结构体、方法使用
小驼峰
的命名方法【强制】常量、包内导出的结构体、方法、Interface使用
大驼峰
的命名方法【强制】包名、包引用别名使用
全小写
的命名方法,尽量使用短命名,不能使用下划线、中划线等字符;文件夹必须与包名保持一致;请注意以下事项:在大多数情况下引入包不需要去重命名
简单明了,命名需要能够在被导入的地方准确识别
不要使用复数。例如,
net/url
, 而不是net/urls不要使用
common
、util
、shared
或lib
之类的,这些都是不好的、表达信息不明的名称参考:PackageNames(https://blog.golang.org/package-names)、Style guideline for Go Packages ( https://rakyll.org/style-packages/ )
【强制】如果包的名称与导入路径的最后一个元素不匹配,那必须使用导入别名;在其他情况下,除非导入的包名之间有直接冲突,否则避免使用导入别名
【强制】在结构体中,专有名词使用
全大写
的命名方法,不能使用其他命名方法;常见的专有名词如:API、HTTP、URL、ID、CPU、CSS、EOF、IP、JSON、TTL、UID、URI、UTF8、XML等【建议】声明局部变量时需要明确设值,应使用短变量声明形式
:=【强制】array、map、channel 声明时使用make
【强制】将相似的声明进行分组;不相关的声明不要放在同一组内
// Bad
import "a"
import "b"
// Good
import (
"a"
"b"
)
// Bad
const A1 = "A"
const B1 = "B"
// Good
const (
A1 = "A"
B1 = "B"
)
// Bad
var a = 1
var b = 2
// Good
var (
a = 1
b = 2
)
// Bad
const (
Add Operation = iota + 1
Subtract
Multiply
ENV = "MY_ENV"
)
// Good
const (
Add Operation = iota + 1
Subtract
Multiply
)
const ENV = "MY_ENV"【强制】枚举、自定义类型的定义及声明
type OrderStatusInt int32
var OrderStatus = struct {
WaitPayment OrderStatusInt
Success OrderStatusInt
Failed OrderStatusInt
} {
WaitPayment: 1,
Success: 2,
Failed: 3,
}
函数
分组及排布
【强制】导出的函数必须排布在文件首,并在 struct、const、var定义之后
【强制】newX()、NewX() 之类的函数必须排布在声明类型之后,具有接收器的其余方法之前
【强制】普通工具函数必须排布在文件末尾
// Bad
func (s *something) Cost() {
return calcCost(s.weights)
}
type something struct{...}
func calcCost(n int) int {...}
func (s *something) Stop() {...}
func newSomething() *something {
return &something{}
}
// Good
type something struct{...}
func newSomething() *something {
return &something{}
}
func (s *something) Cost() {
return calcCost(s.weights)
}
func (s *something) Stop() {...}
func calcCost(n int) int {...}
循环
【建议】代码应通过尽可能地先处理错误情况、特殊情况,并及早返回或继续下一循环来减少嵌套,尽量减少嵌套与多个级别的代码数量
// bad
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("Invalid v:%v", v)
}
}
// good
for _, v := range data {
if v.F1 != 1 {
log.Printf("Invalid v: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}【建议】如果一个变量在if的两个分支都设置了,那应该使用单个if
// bad
var a int
if b {
a = 100
} else {
a = 10
}
// good
a := 10
if b {
a = 100
}【强制】for循环时采用简短声明建立局部变量
// bad
var i int
for i = 0; i < 5; i++ {
codeUsing(i)
}
// good
for i := 0; i < 5; i++ {
codeUsing(i)
}【强制】对于遍历数据的场景,如果只使用第一项,可以直接丢弃第二项
// bad
for k, _ := range mapper {
codeUsing(k)
}
// good
for k := range mapper {
codeUsing(k)
}【强制】switch 必须添加
default
处理;使用多个case
时,多个case写在一起
参数
【建议】函数调用时尽量避免裸参数;裸参数会降低代码可读性,所以当参数名称含义不明显时,请为参数添加
/*.*/
注释func printInfo(name string, isLocal, done bool) {
...
}
// bad
printInfo("foo", true, true)
// good
printInfo("foo", true /* isLocal */, true /* done */)这个例子更好的做法是将bool类型替换为自定义类型,从而使代码更易读且类型安全。
【强制】对于大量的(大于4个时)入参,使用结构体进行封装,并通过指针传递
【强制】对于map、slice、channel、interface的入参,不要使用指针进行传递
【强制】
context.Context
参数不能放在结构体中,且必须作为函数的第一个参数进行传递【建议】尽量避免使用函数命名返回值
// bad
func foo(a, b int) (c string, ok bool) {
c = "hello world"
ok = true
return c, ok
}
// good
func foo(a, b int) (string, bool) {
c := "hello world"
ok := true
return c, ok
}
其他
【建议】尽量缩小变量作用范围,除非这样与减少嵌套的规则冲突;如果需要在if之外使用函数调用的结构,则不应该尝试缩小范围
// bad
err := ioutil.WriteFile(name, data, 0644)
if err != nil {
return err
}
// good
if err := ioutil.WriteFile(name, data, 0644); err != nil {
return err
}【强制】引用包时不能使用省略包名的方式
// bad
import . "fmt"
Println("Hello world")
// good
import "fmt"
fmt.Println("Hello world")
结构体
【强制】嵌入式类型应该放置在结构体字段列表的顶部,并且必须以空行与常规字段隔开
// bad
type Client struct {
version int
http.Client
}
// good
type Client struct {
http.Client
version int
}【强制】初始化结构体时,必须指定字段名称
// bad
u := User{"John", "Doe", true}
// good
u := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}【强制】在初始化结构引用时,使用
&T{}
而非new(T)
,以使其与结构体初始化方式保持一致【建议】接收者名称统一采用1-3个字母,不宜太长
代码指南
【强制】不应该明确返回长度为零的切片,应该直接返回 nil
【强制】检查切片是否为空,始终使用
len(s) == 0
,不要与 nil 比较来检查【建议】零值切片可直接使用,无需调用make创建
【强制】使用原始字符串字面值,避免使用转义
// bad
wantError := "unknown name:\"test\""
// good
wantError := `unknown name: "test"`【建议】格式化字符串放在
printf
外部,并将其设置为const常量// bad
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
// good
const Msg = "Unexpected Values %v, %v\n"
fmt.Printf(msg, 1, 2)【建议】不需要一个指向接口的指针,应该直接将接口作为值传递,因为接口底层数据就是指针;如果你需要接口方法来修改这些底层数据,那必须使用指针
【建议】零值的
sync.Mutex
和sync.RWMutex
是有效的,所以不需要一个指向Mutex的指针;如果希望通过指针操作结构体,Mutex可以作为其非指针结构体字段,或者做好直接嵌入结构体中// bad
mu := new(sync.Mutex)
mu.Lock()
// good
var mu sync.Mutex
mu.Lock()
// good
type smap struct {
sync.Mutex
data map[string]string
}
func (m *smap) Get(k string) string {
m.Lock
defer m.Unlock()
return m.data[k]
}
// good
type SMap struct {
mu sync.Mutex
data map[string]string
}
fun (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Lock()
return m.data[k]
}【强制】使用
defer
来做资源的清理工作,例如文件的关闭、锁的释放等【建议】枚举变量的值应该从非零值开始
【强制】处理类型断言时,必须使用
comma ok
惯用方法// bad
t := i.(string)
// good
t, ok := i.(string)
if !ok {
// handle the error
}【强制】生产环境代码必须避免
panic
;如果发生错误,函数应该返回错误并且允许调用者决定如何处理【强制】外部通信等重要逻辑的任何异常都必须进行处理,不能使用
_
忽略// bad
actions, _ := rpc.GetActionList(ctx, &proto.ActionListRequest{})
// good
actions, err := rpc.GetActionList(ctx, &proto.ActionListRequest{})
代码性能
【建议】strconv 性能优于 fmt
// bad
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
// good
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}【建议】避免 string to byte 的转换;不要反复地从字符串字面量创建byte切片,建议执行一次转换后存储结果供后续使用
// bad
for i := 0; i < b.N; i++ {
w.Write([]byte("Hello world"))
}
// good
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data)
}
代码注释
【强制】大段说明性注释采用
/* ... */
风格【强制】单行注释采用
//
风格【强制】函数、方法的注释需要以函数或方法的名称作为开头
// bad
// 这里直接写注释
func Foo() {...}
// good
// Foo 这里才可以写注释
func Foo() {...}【建议】注释的单行列长度最大不能超过120列,超过则必须换行,一般以80列为宜
工程规范
数据库定义
【强制】数据库定义必须使用TableName方法指定表名,不推荐使用
engine.Table()// bad
package admin
type AdminUser struct {
ID int64 `xorm:"autoincr pk int(11) 'id'"`
}
// good
package admin
type User struct {
ID int64 `xorm:"autoincr pk int(11) 'id'"`
}
func (User) TableName() string {
return "admin_user"
}【强制】批量执行增删改时必须使用事务进行处理
Prorobuf定义
【强制】
.proto
定义request及response格式必须为XxxRequest
、XxxResponse
的大驼峰
的命名方式,不能使用其他格式【强制】pb的定义与生成分别放在不能的文件夹中;pb定义必须放在
protobuf
文件夹中,生成的代码根据不同语言存放在gen/xx
文件夹中【强制】pb文件使用
proto3
语法,禁止使用[json_name='']
类型的tag【强制】pb文件头定义必须遵循下面代码的规则,定义项必须都要有
syntax = "proto3";
option go_package = "gitlab.xxx.com.cn/h2/svr/gen/golang/proto";
option php_namespace = "Svr\\Proto";
option php_metadata_namespace = "Svr\\GpbMetadata";
package proto;
工程设置
【强制】所有项目必须使用
Go Module
来管理依赖包,不能选择其他依赖包管理方式,参考《Go Module 在项目中的应用》【强制】所有项目必须使用
make
来进行项目操作,禁止使用shell
文件进行项目操作【强制】新工程上线一周前需邮件发送研发中心,说明项目信息,包括但不限于配置详情、是否需要mirror环境、上线时间、数据库等信息,越详细越好
拖地先生,从事互联网技术工作,在这里每周两篇文章,一起聊聊日常的技术点滴和管理心得。

如果对你有帮助,让大家也看看呗~




