你好,我是孔令飞。
我们在上一讲学习了“写出优雅Go项目的方法论”,那一讲内容很丰富,是我多年Go项目开发的经验沉淀,需要你多花一些时间好好消化吸收。吃完大餐之后,咱们今天来一期特别放送,就是上一讲我提到过的编码规范。这一讲里,为了帮你节省时间和精力,我会给你一份清晰、可直接套用的 Go 编码规范,帮助你编写一个高质量的 Go 应用。
这份规范,是我参考了Go官方提供的编码规范,以及Go社区沉淀的一些比较合理的规范之后,加入自己的理解总结出的,它比很多公司内部的规范更全面,你掌握了,以后在面试大厂的时候,或者在大厂里写代码的时候,都会让人高看你一眼,觉得你code很专业。
这份编码规范中包含代码风格、命名规范、注释规范、类型、控制结构、函数、GOPATH 设置规范、依赖管理和最佳实践九类规范。如果你觉得这些规范内容太多了,看完一遍也记不住,这完全没关系。你可以多看几遍,也可以在用到时把它翻出来,在实际应用中掌握。这篇特别放送的内容,更多是作为写代码时候的一个参考手册。
代码都必须用 gofmt
进行格式化。
运算符和操作数之间要留空格。
建议一行代码不超过120个字符,超过部分,请采用合适的换行方式换行。但也有些例外场景,例如import行、工具自动生成的代码、带tag的struct字段。
文件长度不能超过800行。
函数长度不能超过80行。
import规范
// 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 (
Width int
Height int
)
// bad
sptr := new(T)
sptr.Name = "bar"
// good
sptr := &T{Name: "bar"}
type User struct{
Username string
Email string
}
user := User{
Username: "colin",
Email: "colin404@foxmail.com",
}
// 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.
}
// use v
错误描述建议
request
。fmt.Errorf("module xxx: %w", err)
。 // bad
errors.New("Redis connection failed")
errors.New("redis connection failed.")
// good
errors.New("redis connection failed")
log.Fatal
来记录错误,这样就可以由log来结束程序,或者将panic抛出的异常记录到日志文件中,方便排查问题。example_test.go
。func (b *Bar) Foo
,单测函数可以为 func TestBar_Foo
。type assertion 的单个返回值针对不正确的类型将产生 panic。请始终使用 “comma ok”的惯用法。
// bad
t := n.(int)
// good
t, ok := n.(int)
if !ok {
// error handling
}
// normal code
命名规范是代码规范中非常重要的一部分,一个统一的、短小的、精确的命名规范可以大大提高代码的可读性,也可以借此规避一些不必要的Bug。
net/url
,而不是net/urls
。// User 多行声明
type User struct {
Name string
Email string
}
// 多行初始化
u := User{
UserName: "colin",
Email: "colin404@foxmail.com",
}
接口命名的规则,基本和结构体命名规则保持一致:
例如:
// 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
// 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
}
// 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)
}
goto
。传入变量和返回变量以小写字母开头。
函数参数个数不能超过5个。
函数分组与顺序
尽量采用值传递,而非指针传递。
传入参数是 map、slice、chan、interface ,不要传递指针。
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{}
这一讲,我向你介绍了九类常用的编码规范。但今天的最后,我要在这里提醒你一句:规范是人定的,你也可以根据需要,制定符合你项目的规范。这也是我在之前的课程里一直强调的思路。但同时我也建议你采纳这些业界沉淀下来的规范,并通过工具来确保规范的执行。
今天的内容就到这里啦,欢迎你在下面的留言区谈谈自己的看法,我们下一讲见。
评论