编码规范

此开源图书由ithaiq原创,创作不易转载请注明出处

代码格式化

注意:代码都必须用 gofmt 进行格式化,文件长度800行,函数长度80行

包引用

// 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 (
	Width  int
	Height int
)
// 指针初始化
// bad
sptr := new(T)
sptr.Name = "bar"

// good
sptr := &T{Name: "bar"}

//struct 声明和初始化格式采用多行
type User struct{
    Username  string
    Email     string
}
user := User{
	Username: "colin",
	Email: "colin404@foxmail.com",
}

//尽可能指定容器容量,以便为容器预先分配内存
v := make(map[int]string, 4)
v := make([]string, 0, 4)

//var变量声明
// bad
var _s string = F()
// good
var _s = F()

//对于未导出的顶层常量和变量,使用_作为前缀。
// 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
}

错误处理

// bad
load()

// good
 _ = load()

//error作为函数的值返回且有多个返回值的时候,error必须是最后一个参数
//尽早进行错误处理,并尽早返回,减少嵌套
//错误要单独判断,不与其他逻辑组合判断。
//错误描述告诉用户他们可以做什么must,而不是告诉他们不能做什么
fmt.Errorf("module xxx: %w", err)
//小写开头 无标点结尾
errors.New("redis connection failed")

panic使用:业务逻辑处理中禁止使用panic; 在main包中使用 log.Fatal 来记录错误。

命名规范

  • 包名

    • 包名必须和目录名一致

    • 包名全部小写,没有大写或下划线,使用多级目录来划分层级

    • 项目名可以通过中划线来连接多个单词。

    • 包名以及包所在的目录名,不要使用复数。

    • 不要用 common、util、shared 或者 lib 这类宽泛的、无意义的包名

    • 包名要简单明了,例如 net、time、log

  • 函数命名

    • MixedCaps或者mixedCaps 控制权限

  • 文件名

    • 文件名要简短有意义、文件名应小写,并使用下划线分割单词

  • 接口命名

    • 单个函数的接口名以 “er"”作为后缀

  • 变量命名

    • 特有名词(全大写)

    // 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,
    }
    • 若变量类型为bool类型,则名称应以Has,Is,Can或Allow开头

    • 局部变量应当尽可能短小 buf⇒buffer

  • 常量命名

    // 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
    )
  • error命名

    Error类型应该写成XXXError的形式。
    type ExitError struct {
    	// ....
    }
    Error变量写成ErrXXX的形式。
    var ErrFormat = errors.New("unknown format")

基础类型

  • 字符串

    // 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
    
    strings.Compare()
  • 空slice map、channel 判断

    // bad
    if len(slice) = 0 {
        // normal code
    }
    
    // good
    if slice != nil && len(slice) == 0 {
        // normal code
    }
  • slice

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

流程控制

  • 不要在 for 循环里面使用 defer,defer只有在函数退出时才会执行

// 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
	}()
}
  • range

for key := range keys {
// normal code
}
sum := 0
for _, value := range array {
    sum += value
}
  • goto不要用

函数

  • 尽量采用值传递,而非指针传递

  • 传入参数是 map、slice、chan、interface ,不要传递指针

  • 如果函数返回相同类型的两个或三个参数,或者如果从上下文中不清楚结果的含义,使用命名返回,其他情况不建议使用命名返回

    func coordinate() (x, y float64, err error) {
    	// normal code
    }
  • 先判断是否错误,再defer释放资源

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

依赖管理

  • Go 1.11 以上必须使用 Go Modules。

  • 使用Go Modules作为依赖管理的项目时,不建议提交vendor目录。

  • 使用Go Modules作为依赖管理的项目时,必须提交go.sum文件

最佳实践

  • 尽量少用全局变量,而是通过参数传递

  • 编译验证接口

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

    • string 表示的是不可变的字符串变量,对 string 的修改是比较重的操作,基本上都需要重新申请内存。所以,如果没有特殊需要,需要修改时多使用 []byte。

    • 优先使用 strconv 而不是 fmt

  • 注意事项

    • append 要小心自动分配内存,append 返回的可能是新分配的地址。

    • 如果要直接修改 map 的 value 值,则 value 只能是指针,否则要覆盖原来的值。

    • map 在并发中需要加锁。

    • 编译过程无法检查 interface{} 的转换,只能在运行时检查,小心引起 panic。

最后更新于

这有帮助吗?