Golang编码规范
下面是转换为 Markdown 格式的文本(包含Markdown标签):
前言
1.1. 一般信息【重要必读】
文档内容可能会与您的喜好冲突,请尽量用包容的心态来接受;不合理之处,请评论指出。
1.2. 如何使用本编程规范
本规范的层次结构
本规范可分为三大部分,分别对 Go 语法、风格、编程实践作出规定与建议。
每一部分有若干专题,每一专题下有若干条目。
条目是规范的基本组成部分,每一条目由规定、定义、解释、示例、参考等项组成。
条目的级别和编号
本规范的条目分两个级别:
RULE
:要求所有程序必须遵守,不得违反ADVICE
:建议遵守,除非确有特殊情况
本规范所有条目都有编号,用来标识单条规范并用于自动化检查工具错误提示。
标号由 “RULE
“ 或 “ADVICE
“ 和三位数字标识,例如 RULE001
, ADVICE001
1.3. 说明
参考下列规范并结合自己的业务场景:
- 参考 “百度 Golang 编程规范 V1.1”
- 参考 golang.org/doc/effective_go.html
- 参考 github.com/golang/go/wiki/CodeRevi...
2. 语言规范
2.1 true/false 求值
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 Receiver
2.2.1 Receiver Type
RULE003: 如果 receiver 是 map
、函数
或 chan
类型,类型不可以是指针
RULE004: 如果 receiver 是 slice
,并且方法不会进行 reslice 或者重新分配 slice,类型不可以是指针
RULE005: 如果 receiver 是 struct
,且包含 sync.Mutex
类型字段,则必须使用指针避免拷贝。
RULE006: 如果 receiver 是比较大的 struct
/array
,建议使用指针,这样会更有效率
ADVICE001: 如果 receiver 是 struct
、array
或 slice
,其中指针元素所指的内容可能在方法内被修改,建议使用指针类型
ADVICE002: 如果 receiver 是比较小的 struct
/array
,建议使用 value 类型
解释:
关于 receiver 的定义详见 Receiver 定义:The receiver is specified via an extra parameter section preceding 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 命名
ADVICE003: 尽量简短并有意义。
RULE007: 禁止使用 “this”、”self” 等面向对象语言中特定的叫法。
ADVICE004: 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 {
// ...
}
2.3 类型申明
RULE008: 申明 slice
时,建议使用 var
方式申明,不建议使用大括号的方式,如果使用 make
方式,最好能够预估 slice
的大小和容量
解释:var
方式申明在 slice
不被 append
的情况下避免了内存分配示例,make
指定容量会避免 append
扩容
RULE009: struct
声明和初始化格式采用多行
RULE010: map
、chan
均使用 make
方式初始化,可以理解返回的是个指针,slice
有点区别,在扩容时,地址会变化
示例:
// GOOD:
var t []string
// 指定容量
res := make([]string, 0, 10)
//定义如下:
type User struct {
Username string
Email string
}
u := User{
Username: "astaxie",
Email: "webben@gmail.com",
}
// BAD:
t := []string{}
res := make([]string)
2.4 Error Handler
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
}
2.5 自定义类型的 String
循环问题
RULE014: 自定义的类型定义了 String
方法,那么在打印的时候会产生隐藏的一些 bug
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: 死循环
}
2.6 Typical Data Races - 数据竞争
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()
}()
}
}
2.7 引用第三方包需要验证
RULE018: 业务中引入任何第三方包之前,需要调研对比各个包之间的性能差异,使用范围(如 star 数量)
例如,对于 JSON 包,官方的 encoding/json
包性能较差,可以选择使用 jsoniter
包(github.com/json-iterator/go
)来提高效率。
2.8 字符串使用注意事项
RULE019: 避免循环内进行字符串拼接时,如果避免不了,采用 bytes.Buffer
方式,预估内部的 Grow
次数,避免内存的频繁申请。
RULE020: 字符串的传值是传引用,任何修改都会导致重新分配空间。
RULE021: 少量字符串可使用 +
或 string
包操作,尽量少用 fmt.Sprintf
。
RULE022: 大量字符串的传参要传指针。
2.9 embedding 的使用
ADVICE005: embedding 只用于 “is a” 的语义下,而不用于
“has a” 的语义下。
ADVICE006: 一个定义内,多于一个的 embedding 尽量少用。
解释:
- 语义上 embedding 是一种 “继承关系”,而不是 “成员关系”。
- 一个定义内有多个 embedding,则很难判断某个成员变量或函数是从哪里继承得到的。
- 一个定义内有多个 embedding,危害和在 Python 中使用 “from xxx import *” 是类似的。
示例:
// GOOD:
type Automobile struct {
// ...
}
type Engine struct {
// ...
}
// 正确的定义
type Car struct {
Automobile // Car is a Automobile
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
}
2.10 完善单元测试和性能测试
在进行单元测试和性能测试时,需要注意以下几点:
- 测试应该会失败,并提供有用的消息,说明错误、输入内容、实际结果以及预期结果。可以使用
t.Errorf
或t.Fatalf
输出错误信息。 - 对于公共功能函数,应该进行性能测试,了解关键节点的 IO 代价,可以使用
benchmark
进行性能测试。
示例:
// 单元测试
func TestFoo(t *testing.T) {
tests := []struct {
in string
want int
}{
{"input1", 10},
{"input2", 20},
// more test cases...
}
for _, tt := range tests {
got := Foo(tt.in)
if got != tt.want {
t.Errorf("Foo(%q) = %d; want %d", tt.in, got, tt.want)
// 或者使用 Fatalf,如果测试在此处之后无法继续测试
// t.Fatalf("Foo(%q) = %d; want %d", tt.in, got, tt.want)
}
}
}
// 性能测试
func BenchmarkBar(b *testing.B) {
for i := 0; i < b.N; i++ {
// 运行需要测试性能的代码
Bar()
}
}
2.11 业务需要梳理接口IO消耗
在访问存储、Redis、数据库等资源时,需要梳理业务接口的 IO 代价,并及时调整设计和实现,以实现最小化的 IO 消耗。
这意味着要评估每个接口对 IO 的影响,并寻找优化的机会,例如批量操作、缓存数据、使用异步处理等方式来减少对资源的频繁访问。
优化 IO 消耗可以提高系统的性能和响应速度,并减少资源的使用。
3.1 Go 文件布局
在编写 Go 文件时,建议按照以下顺序布局文件的内容:
- General Documentation: 对整个模块和功能的完整描述注释,写在文件头部。
- package:当前 package 定义
- imports:包含的头文件
- Constants:常量
- Typedefs: 类型定义
- Globals:全局变量定义
- functions:函数实现
- public functions :首字符大写的函数
- private functions:首字母小写的函数
对于以上各个部分,采用单个空行进行分割,并按照以下准则进行处理:
- 多个类型定义之间使用单个空行进行分割。
- 多个函数之间使用单个空行进行分割。
此外,建议在函数内部不同的业务逻辑处理之间也使用单个空行进行分割。
对于较多的常量或变量,可以按照业务进行分组,并在组之间使用单个空行进行分割。
实例:
//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() {
//...
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
这个应该直接报错吧