为形成统一的 Go 编码风格,提高代码的可读性、安全性和易维护性,在 Google Golang 代码规范 的基础上,给出编码风格建议。使用时,可根据实际情况进行了调整和补充。
代码必须用 gofmt 格式化。
gofmt 使用制表符进行缩进,使用空白进行对齐。
IDE 在保存代码时可设置自动执行 gofmt,如 GoLand 的 Settings > Tools > File Watchers 中可勾选 go fmt 并指定作用范围。

一行代码不要超过120列,超过的情况,使用合理的换行方法换行。
例外场景:
采用惰性换行,换行前应尽可能地占满不留空位。
// Bad
fmt.Printf("%v %v %v %v %v %v %v %v %v %v %v %v %v %v\n",
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,89, 144, 233)
// Good
fmt.Printf("%v %v %v %v %v %v %v %v %v %v %v %v %v %v\n", 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,
89, 144, 233)// Bad
func foo() {
// func body
}
// Good
func foo() {
// func body
}// Bad
res, err := foo()
if err != nil || res.Ret != 0 {
return
}
// Good
res, err := foo()
if err != nil || res.Ret != 0 {
return
}// Bad
func foo() {
// func body
return
}
// Good
func foo() {
// func body
return
}var i int= 1 + 2 // 运算符和操作数之间要留空格
v := []float64{1.0, 2.0, 3.0}[i-i] // i-i 作为下标不留空格
fmt.Printf("%f\n", v+1) // v+1 作为入参不要留空格// Bad
if foo && (int(bar) > 0) {
// ...
}
// Good
if foo && int(bar) > 0 {
// ...
}使用反引号表示原始字符串字面值,避免转义。
// Bad
wantError := "unknown name:\"test\""
// Good
wantError := `unknown error:"test"`导入应该分为三组:
带域名的包名都属于外部包,如 github.com/xxx/xxx。内部包是指不能被外部 import 的包
// Bad
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
"myproject/models"
"myproject/controller"
)
// Good
import (
"encoding/json"
"strings"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
"myproject/models"
"myproject/controller"
)在导入内部包时,不要使用相对路径引入包,应该使用完整的路径引入包。
// Bad
import (
"../net"
)
// Good
import (
"xxxx.com/proj/net"
)包名和 git 路径名不一致时,或者多个相同包名冲突时,使用别名代替。
// Bad
import (
elastic "github.com/olivere/elastic/v7"
)
// Good
import (
elastic "github.com/olivere/elastic/v7"
)// Bad
k := User{"John", "Doe", true}
// Good
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}例外:如果有 3 个或更少的字段,则可以在测试表中省略字段名称。
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}// Bad
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
// Good
user := User{
FirstName: "John",
LastName: "Doe",
}例外:在字段名提供有意义上下文的地方可以显示指定零值。例如,表驱动测试中的测试用例可以受益于字段的名称,即使它们是零值。
tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}// Bad
var user := User{}
// Good
var user User在初始化结构引用时,请使用&T{}代替new(T)可以与结构体初始化在代码风格上保持一致。
// Bad
sval := T{Name: "foo"}
// inconsistent
sptr := new(T)
sptr.Name = "bar"
// Good
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}初始化 map 优先使用 make() 函数而不是字面量,因为这样看起来容易和申明区分开来。
// Bad
var (
// m1 读写安全
// m2 在写入时会 panic
m1 = map[T1]T2{}
m2 map[T1]T2
)
// 声明和初始化在视觉上很相似
// Good
var (
// m1 读写安全
// m2 在写入时会 panic
m1 = make(map[T1]T2)
m2 map[T1]T2
)
// 声明和初始化在视觉上是不同的尽可能的情况下,请在初始化时提供 map 容量大小。
在另一方面,如果 map 包含固定的元素列表,则使用字面量初始化 map,因为这样可以在初始化的时候指定元素。
// Bad
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3
// Good
m := map[T1]T2{
k1: v1,
k2: v2,
k3: v3,
}非零值 slice 使用make()初始化,并指定容量。
// Bad
nums := []int{}
// Good
nums := make([]int, 0, SIZE)零值切片(用 var 声明的切片)可立即使用,无需调用make()创建。
// Bad
// 非 nil 切片
nums := []int{}
// Good
// nil 切片
var nums []int本地变量声明应使用短变量声明形式(:=)
// Bad
var s = "foo"
// Good
s := "foo"尽量缩小变量作用范围。
// Bad
err := ioutil.WriteFile(name, data, 0644)
if err != nil {
return err
}
// Good
if err := ioutil.WriteFile(name, data, 0644); err != nil {
return err
}变量申明的位置尽量靠近使用的地方。
尽可能避免使用 init()。当init()是不可避免或可取的,代码应先尝试:
defer xx.Close()可以不用显式处理// 不要采用这种方式
func do() (error, int) {
}
// 要采用下面的方式
func do() (int, error) {
}// 不要采用这种方式
if err != nil {
// error handling
} else {
// normal code
}
// 而要采用下面的方式
if err != nil {
// error handling
return // or continue, etc.
}
// normal codex, err := f()
if err != nil {
// error handling
return // or continue, etc.
}
// use x// 不要采用这种方式:
x, y, err := f()
if err != nil || y == nil {
return err // 当y与err都为空时,函数的调用者会出现错误的调用逻辑
}
// 应当使用如下方式:
x, y, err := f()
if err != nil {
return err
}
if y == nil {
return fmt.Errorf("some error")
}fmt.Errorf("module xxx: %v", err),而不是 errors.New(fmt.Sprintf("module xxx: %v",err))。// 不推荐为传递 error 而在包内使用 panic。以下为反面示例
// TError 包内定义的错误类型
type TError string
// Error error接口方法
func (e TError) Error() string {
return string(e)
}
func do(str string) {
// ...
// 此处的 panic 用于传递 error
panic(TError("错误信息"))
// ...
}
// Do 包级访问入口
func Do(str string) (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(TError)
}
}()
do(str)
return nil
}package main
import (
"log"
)
func main() {
defer func() {
if err := recover(); err != nil {
// do something or record log
log.Println("exec panic error: ", err)
// log.Println(debug.Stack())
}
}()
getOne()
panic(44) //手动抛出panic
}
// getOne 模拟 slice 越界 runtime 运行时抛出的 panic
func getOne() {
defer func() {
if err := recover(); err != nil {
// do something or record log
log.Println("exec panic error: ", err)
// log.Println(debug.Stack())
}
}()
var arr = []string{"a", "b", "c"}
log.Println("hello,", arr[4])
}
// 执行结果:
2021/10/04 11:07:13 exec panic error: runtime error: index out of range [4] with length 3
2021/10/04 11:07:13 exec panic error: 44// 不要采用这种方式
t := i.(string)
// 而要采用下面的方式
t, ok := i.(string)
if !ok {
// 优雅地处理错误
}