你好,我是孔令飞。

我们在上一讲学习了“写出优雅Go项目的方法论”,那一讲内容很丰富,是我多年Go项目开发的经验沉淀,需要你多花一些时间好好消化吸收。吃完大餐之后,咱们今天来一期特别放送,就是上一讲我提到过的编码规范。这一讲里,为了帮你节省时间和精力,我会给你一份清晰、可直接套用的 Go 编码规范,帮助你编写一个高质量的 Go 应用。

这份规范,是我参考了Go官方提供的编码规范,以及Go社区沉淀的一些比较合理的规范之后,加入自己的理解总结出的,它比很多公司内部的规范更全面,你掌握了,以后在面试大厂的时候,或者在大厂里写代码的时候,都会让人高看你一眼,觉得你code很专业。

这份编码规范中包含代码风格、命名规范、注释规范、类型、控制结构、函数、GOPATH 设置规范、依赖管理和最佳实践九类规范。如果你觉得这些规范内容太多了,看完一遍也记不住,这完全没关系。你可以多看几遍,也可以在用到时把它翻出来,在实际应用中掌握。这篇特别放送的内容,更多是作为写代码时候的一个参考手册。

1. 代码风格

1.1 代码格式

// 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"
	)

1.2 声明、初始化和定义

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
}

1.3 错误处理

func load() error {
	// normal code
}

// bad
load()

// good
 _ = load()
// 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
	// bad
	errors.New("Redis connection failed")
	errors.New("redis connection failed.")

	// good
	errors.New("redis connection failed")

1.4 panic处理

1.5 单元测试

1.6 类型断言失败处理

type assertion 的单个返回值针对不正确的类型将产生 panic。请始终使用 “comma ok”的惯用法。

// bad
t := n.(int)

// good
t, ok := n.(int)
if !ok {
	// error handling
}
// normal code

2. 命名规范

命名规范是代码规范中非常重要的一部分,一个统一的、短小的、精确的命名规范可以大大提高代码的可读性,也可以借此规避一些不必要的Bug。

2.1 包命名

2.2 函数命名

2.3 文件命名

2.4 结构体命名

// User 多行声明
type User struct {
    Name  string
    Email string
}

// 多行初始化
u := User{
    UserName: "colin",
    Email:    "colin404@foxmail.com",
}

2.5 接口命名

例如:

	// 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
	}

2.6 变量命名

下面列举了一些常见的特有名词。

// 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

2.7 常量命名

// 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
)

2.8 Error的命名

type ExitError struct {
	// ....
}
var ErrFormat = errors.New("unknown format")

3. 注释规范

// 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
}
// 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

3.1 包注释

// Package genericclioptions contains flags which can be added to you command, bound, completed, and produce
// useful helper functions.
package genericclioptions

3.2 变量/常量注释

// 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 
)

3.3 结构体注释

// 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"`
}

3.4 方法注释

// BeforeUpdate run before update database record.
func (p *Policy) BeforeUpdate() (err error) {
	// normal code
	return nil
}

3.5 类型注释

// Code defines an error code type.
type Code int

4. 类型

4.1 字符串

// 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(`\.`)

4.2 切片

// 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...)

4.3 结构体

struct以多行格式初始化。

type user struct {
	Id   int64
	Name string
}

u1 := user{100, "Colin"}

u2 := user{
    Id:   200,
    Name: "Lex",
}

5. 控制结构

5.1 if

if err := loadConfig(); err != nil {
	// error handling
	return err
}
var isAllow bool
if isAllow {
	// normal code
}

5.2 for

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
	}()
}

5.3 range

for key := range keys {
// normal code
}
sum := 0
for _, value := range array {
    sum += value
}

5.4 switch

switch os := runtime.GOOS; os {
    case "linux":
        fmt.Println("Linux.")
    case "darwin":
        fmt.Println("OS X.")
    default:
        fmt.Printf("%s.\n", os)
}

5.5 goto

6. 函数

6.1 函数参数

func coordinate() (x, y float64, err error) {
	// normal code
}

6.2 defer

rep, err := http.Get(url)
if err != nil {
    return err
}

defer resp.Body.Close()

6.3 方法的接收器

6.4 嵌套

6.5 变量命名

// PI ...
const Prise = 3.14

func getAppleCost(n float64) float64 {
	return Prise * n
}

func getOrangeCost(n float64) float64 {
	return Prise * n
}

7. GOPATH 设置规范

8. 依赖管理

9. 最佳实践

type LogHandler struct {
  h   http.Handler
  log *zap.Logger
}
var _ http.Handler = LogHandler{}

9.1 性能

9.2 注意事项

总结

这一讲,我向你介绍了九类常用的编码规范。但今天的最后,我要在这里提醒你一句:规范是人定的,你也可以根据需要,制定符合你项目的规范。这也是我在之前的课程里一直强调的思路。但同时我也建议你采纳这些业界沉淀下来的规范,并通过工具来确保规范的执行。

今天的内容就到这里啦,欢迎你在下面的留言区谈谈自己的看法,我们下一讲见。

评论