GitHub - marmotedu/geekbang-go: 极客时间 《Go 语言项目开发实战》课程补充教程。
在Go项目开发中,一个好的编码规范可以极大的提高代码质量。为了帮你节省时间和精力,这里我整理了一份清晰、可直接套用的 Go 编码规范,供你参考。
这份规范,是我参考了Go官方提供的编码规范,以及Go社区沉淀的一些比较合理的规范之后,加入自己的理解总结出的,它比很多公司内部的规范更全面,你掌握了,以后在面试大厂的时候,或者在大厂里写代码的时候,都会让人高看你一眼,觉得你code很专业。
这份编码规范中包含代码风格、命名规范、注释规范、类型、控制结构、函数、GOPATH 设置规范、依赖管理和最佳实践九类规范。如果你觉得这些规范内容太多了,看完一遍也记不住,这完全没关系。你可以多看几遍,也可以在用到时把它翻出来,在实际应用中掌握。这篇特别放送的内容,更多是作为写代码时候的一个参考手册。
gofmt
进行格式化。goimports
进行格式化(建议将代码Go代码编辑器设置为:保存时运行 goimports
)。import ../util/net
。// bad "github.com/dgrijalva/jwt-go/v4" //good jwt "github.com/dgrijalva/jwt-go/v4"
- 导入的包建议进行分组,匿名包的引用使用一个新的分组,并对匿名包引用进行说明。
import ( // go 标准包 "fmt" // 第三方包 "github.com/jinzhu/gorm" "github.com/spf13/cobra" "github.com/spf13/viper" // 匿名包单独分组,并对匿名包引用进行说明 // import mysql driver _ "github.com/jinzhu/gorm/dialects/mysql" // 内部包 v1 "github.com/marmotedu/api/apiserver/v1" metav1 "github.com/marmotedu/apimachinery/pkg/meta/v1" "github.com/marmotedu/iam/pkg/cli/genericclioptions" )
当函数中需要使用到多个变量时,可以在函数开始处使用var
声明。在函数外部声明必须使用 var
,不要采用 :=
,容易踩到变量的作用域的问题。
var ( Width int Height int )
&T{}
代替new(T)
,以使其与结构体初始化一致。// bad sptr := new(T) sptr.Name = "bar" // good sptr := &T{Name: "bar"}
type User struct{ Username string Email string } user := User{ Username: "colin", Email: "[email protected]", }
// bad import "a" import "b" // good import ( "a" "b" )
v := make(map[int]string, 4) v := make([]string, 0, 4)
// bad var _s string = F() func F() string { return "A" } // good var _s = F() // 由于 F 已经明确了返回一个字符串类型,因此我们没有必要显式指定_s 的类型 // 还是那种类型 func F() string { return "A" }
_
作为前缀。// bad const ( defaultHost = "127.0.0.1" defaultPort = 8080 ) // good const ( _defaultHost = "127.0.0.1" _defaultPort = 8080 )
// bad type Client struct { version int http.Client } // good type Client struct { http.Client version int }
error
作为函数的值返回,必须对error
进行处理,或将返回值赋值给明确忽略。对于defer xx.Close()
可以不用显式处理。func load() error { // normal code } // bad load() // good _ = load()
error
作为函数的值返回且有多个返回值的时候,error
必须是最后一个参数。// bad func load() (error, int) { // normal code } // good func load() (int, error) { // normal code }
// bad if err != nil { // error code } else { // normal code } // good if err != nil { // error handling return err } // normal code
// bad if v, err := foo(); err != nil { // error handling } // good v, err := foo() if err != nil { // error handling }
// bad
v, err := foo()
if err != nil || v == nil {
// error handling
return err
}
// good
v, err := foo()
if err != nil {
// error handling
return err
}
if v == nil {
// error handling
return errors.New("invalid value v")
}
v, err := f() if err != nil { // error handling return // or continue. }
// bad errors.New("Redis connection failed") errors.New("redis connection failed.") // good errors.New("redis connection failed")
- 告诉用户他们可以做什么,而不是告诉他们不能做什么。
- 当声明一个需求时,用must 而不是should。例如,`must be greater than 0、must match regex '[a-z]+'`。
- 当声明一个格式不对时,用must not。例如,`must not contain`。
- 当声明一个动作时用may not。例如,`may not be specified when otherField is empty、only name may be specified`。
- 引用文字字符串值时,请在单引号中指示文字。例如,`ust not contain '..'`。
- 当引用另一个字段名称时,请在反引号中指定该名称。例如,must be greater than `request`。
- 指定不等时,请使用单词而不是符号。例如,`must be less than 256、must be greater than or equal to 0 (不要用 larger than、bigger than、more than、higher than)`。
- 指定数字范围时,请尽可能使用包含范围。
- 建议 Go 1.13 以上,error 生成方式为 `fmt.Errorf("module xxx: %w", err)`。
log.Fatal
来记录错误,这样就可以由log来结束程序,或者将panic抛出的异常记录到日志文件中,方便排查问题。example_test.go
。func (b *Bar) Foo
,单测函数可以为 func TestBar_Foo
。// bad t := n.(int) // good t, ok := n.(int) if !ok { // error handling }
、、=============、=============、=============
命名规范是代码规范中非常重要的一部分,一个统一的、短小的、精确的命名规范可以大大提高代码的可读性,也可以借此规避一些不必要的Bug。
net/url
,而不是net/urls
。MixedCaps
或者mixedCaps
。xxxx.pb.go
)和为了对相关测试用例进行分组,而采用的下划线(如TestMyFunction_WhatIsBeingTested
)排除此规则。MixedCaps
或者mixedCaps
。Node
、NodeSpec
。// User 多行声明 type User struct { Name string Email string } // 多行初始化 u := User{ UserName: "colin", Email: "[email protected]", }
例如:
// Seeking to an offset before the start of the file is an error.
// Seeking to any positive offset is legal, but the behavior of subsequent
// I/O operations on the underlying object is implementation-dependent.
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
Reader
Writer
}
下面列举了一些常见的特有名词。
// A GonicMapper that contains a list of common initialisms taken from golang/lint
var LintGonicMapper = GonicMapper{
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTP": true,
"HTTPS": true,
"ID": true,
"IP": true,
"JSON": true,
"LHS": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SSH": true,
"TLS": true,
"TTL": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XSRF": true,
"XSS": true,
}
var hasConflict bool var isExist bool var canManage bool var allowGitHook bool
xxx.pb.go
里面的Id)// Code defines an error code type. type Code int // Internal errors. const ( // ErrUnknown - 0: An unknown error occurred. ErrUnknown Code = iota // ErrFatal - 1: An fatal error occurred. ErrFatal )
type ExitError struct { // .... }
var ErrFormat = errors.New("unknown format")
格式为 // 名称 描述.
。例如:// bad // logs the flags in the flagset. func PrintFlags(flags *pflag.FlagSet) { // normal code } // good // PrintFlags logs the flags in the flagset. func PrintFlags(flags *pflag.FlagSet) { // normal code }
所有注释掉的代码在提交code review前都应该被删除,否则应该说明为什么不删除,并给出后续处理建议。
在多段注释之间可以使用空行分隔加以区分,如下所示:
// Package superman implements methods for saving the world. // // Experience has shown that a small number of procedures can prove // helpful when attempting to save the world. package superman
// Package 包名 包描述
,例如:// Package genericclioptions contains flags which can be added to you command, bound, completed, and produce // useful helper functions. package genericclioptions
格式为// 变量名 变量描述
,例如:// ErrSigningMethod defines invalid signing method error. var ErrSigningMethod = errors.New("Invalid signing method")
// Code must start with 1xxxxx. const ( // ErrSuccess - 200: OK. ErrSuccess int = iota + 100001 // ErrUnknown - 500: Internal server error. ErrUnknown // ErrBind - 400: Error occurred while binding the request body to the struct. ErrBind // ErrValidation - 400: Validation failed. ErrValidation )
// 结构体名 结构体描述.
。// User represents a user restful resource. It is also used as gorm model. type User struct { // Standard object's metadata. metav1.ObjectMeta `json:"metadata,omitempty"` Nickname string `json:"nickname" gorm:"column:nickname"` Password string `json:"password" gorm:"column:password"` Email string `json:"email" gorm:"column:email"` Phone string `json:"phone" gorm:"column:phone"` IsAdmin int `json:"isAdmin,omitempty" gorm:"column:isAdmin"` }
每个需要导出的函数或者方法都必须有注释,格式为// 函数名 函数描述.,例如:
// BeforeUpdate run before update database record. func (p *Policy) BeforeUpdate() (err error) { // normal code return nil }
// 类型名 类型描述.
,例如:// Code defines an error code type. type Code int
// bad if s == "" { // normal code } // good if len(s) == 0 { // normal code }
[]byte
/string
相等比较。// bad var s1 []byte var s2 []byte ... bytes.Equal(s1, s2) == 0 bytes.Equal(s1, s2) != 0 // good var s1 []byte var s2 []byte ... bytes.Compare(s1, s2) == 0 bytes.Compare(s1, s2) != 0
// bad regexp.MustCompile("\\.") // good regexp.MustCompile(`\.`)
// bad if len(slice) = 0 { // normal code } // good if slice != nil && len(slice) == 0 { // normal code }
上面判断同样适用于map、channel。
// bad s := []string{} s := make([]string, 0) // good var s []string
// bad var b1, b2 []byte for i, v := range b1 { b2[i] = v } for i := range b1 { b2[i] = b1[i] } // good copy(b2, b1)
// bad var a, b []int for _, v := range a { b = append(b, v) } // good var a, b []int b = append(b, a...)
struct以多行格式初始化。
type user struct { Id int64 Name string } u1 := user{100, "Colin"} u2 := user{ Id: 200, Name: "Lex", }
if err := loadConfig(); err != nil { // error handling return err }
var isAllow bool if isAllow { // normal code }
sum := 0 for i := 0; i < 10; i++ { sum += 1 }
// bad for file := range files { fd, err := os.Open(file) if err != nil { return err } defer fd.Close() // normal code } // good for file := range files { func() { fd, err := os.Open(file) if err != nil { return err } defer fd.Close() // normal code }() }
for key := range keys { // normal code }
sum := 0 for _, value := range array { sum += value }
switch os := runtime.GOOS; os { case "linux": fmt.Println("Linux.") case "darwin": fmt.Println("OS X.") default: fmt.Printf("%s.\n", os) }
func coordinate() (x, y float64, err error) { // normal code }
rep, err := http.Get(url) if err != nil { return err } defer resp.Body.Close()
// PI ... const Prise = 3.14 func getAppleCost(n float64) float64 { return Prise * n } func getOrangeCost(n float64) float64 { return Prise * n }
type LogHandler struct { h http.Handler log *zap.Logger } var _ http.Handler = LogHandler{}
这里向你介绍了九类常用的编码规范。但今天的最后,我要在这里提醒你一句:规范是人定的,你也可以根据需要,制定符合你项目的规范,但同时我也建议你采纳这些业界沉淀下来的规范,并通过工具来确保规范的执行。