Go 代码规范


项目目录结构规范
PROJECT_NAME
├── README.md 介绍软件及文档入口
├── bin 编译好的二进制文件
├── doc 该项目的文档
└── src 该项目的源代码

├── main 项目主函数
├── controller 路由控制
├── module 模块
└── conf 配置文件

包名
包名用小写,使用短命名,尽量和标准库不要冲突。

接口名
单个函数的接口名以”er”作为后缀,如 Reader、Writer

接口的实现则去掉“er”

type Reader interface {
    Read(p []byte) (n int, err error)
}

两个函数的接口名综合两个函数名

type WriteFlusher interface {
    Write([]byte) (int, error)
    Flush() error
}

三个以上函数的接口名,类似于结构体名

type Car interface {
    Start([]byte) 
    Stop() error
    Recover()
}

变量
全局变量:采用驼峰命名法,仅限在包内的全局变量,包外引用需要写接口,提供调用
局部变量:驼峰式,小写字母开头

常量
常量:大写,采用下划线

if 接受初始化语句,约定如下方式建立局部变量

if err := file.Chmod(0664); err != nil {
    return err
}

for 采用短声明建立局部变量

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

range 如果只需要第一项 key,就丢弃第二个:

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

如果只需要第二项,则把第一项置为下划线

sum := 0
for _, value := range array {
    sum += value
}

return 尽早 return,一旦有错误发生,马上返回

f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

方法的接收器
名称 一般采用 strcut 的第一个字母且为小写

type T struct{} 
func (p *T)Get(){}

如果接收者是 map、slice 或者 chan,不要用指针传递

//Map
package main

import (
    "fmt"
)

type mp map[string]string

func (m mp) Set(k, v string) {
    m[k] = v
}

func main() {
    m := make(mp)
    m.Set("k", "v")
    fmt.Println(m)
}
//Channel
package main

import (
    "fmt"
)

type ch chan interface{}

func (c ch) Push(i interface{}) {
    c <- i
}

func (c ch) Pop() interface{} {
    return <-c
}

func main() {
    c := make(ch, 1)
    c.Push("i")
    fmt.Println(c.Pop())
}

如果需要对 slice 进行修改,通过返回值的方式重新赋值

//Slice
package main

import (
    "fmt"
)

type slice []byte

func main() {
    s := make(slice, 0)
    s = s.addOne(42)
    fmt.Println(s)
}

func (s slice) addOne(b byte) []byte {
    return append(s, b)
}

如果接收者是含有 sync.Mutex 或者类似同步字段的结构体,必须使用指针传递避免复制

package main

import (
    "sync"
)

type T struct {
    m sync.Mutex
}

func (t *T) lock() {
    t.m.Lock()
}

/*
Wrong !!!
func (t T) lock() {
    t.m.Lock()
}
*/

func main() {
    t := new(T)
    t.lock()
}

如果接收者是大的结构体或者数组,使用指针传递会更有效率。

package main

import (
    "fmt"
)

type T struct {
    data [1024]byte
}

func (t *T) Get() byte {
    return t.data[0]
}

func main() {
    t := new(T)
    fmt.Println(t.Get())
}

声明空的 slice 应该使用下面的格式:

var t []string

而不是这种格式:

t := []string{}

前者声明了一个 nil slice 而后者是一个长度为0的非 nil 的 slice。

package 级的 Error 变量
通常会把自定义的 Error 放在 package 级别中,统一进行维护:

var (
    ErrCacheMiss = errors.New("memcache: cache miss")
    ErrCASConflict = errors.New("memcache: compare-and-swap conflict")
    ErrNotStored = errors.New("memcache: item not stored")
    ErrServerError = errors.New("memcache: server error")
    ErrNoStats = errors.New("memcache: no statistics available")
    ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters")
    ErrNoServers = errors.New("memcache: no servers configured or available")
)

并且变量以 Err 开头

空字符串检查
不要使用下面的方式检查空字符串:

if len(s) == 0 {
    ...
}

而是使用下面的方式

if s == "" {
    ...
}

下面的方法更是语法不对:

if s == nil || s == "" {
    ...
}

非空 slice 检查
不要使用下面的方式检查空的 slice:

if s != nil && len(s) > 0 {
    ...
}

直接比较长度即可:

if len(s) > 0 {
    ...
}

省略不必要的变量

var whitespaceRegex, _ = regexp.Compile("\\s+")

可以简写为

var whitespaceRegex = regexp.MustCompile(`\s+`)

直接使用 bool 值
对于 bool 类型的变量 var b bool,直接使用它作为判断条件,而不是使用它和 true/false 进行比较

if b {
    ...
}
if !b {
    ...
}

而不是

if b == true {
    ...
}
if b == false {
    ...
}

byte/string slice 相等性比较
不要使用

var s1 []byte
var s2 []byte
...
bytes.Compare(s1, s2) == 0
bytes.Compare(s1, s2) != 0

而是:

var s1 []byte
var s2 []byte
...
bytes.Equal(s1, s2) == 0
bytes.Equal(s1, s2) != 0

检查是否包含子字符串
不要使用 strings.IndexRune(s1, 'x') > -1 及其类似的方法 IndexAny、Index 检查字符串包含,
而是使用 strings.ContainsRune、strings.ContainsAny、strings.Contains 来检查。

复制 slice
不要使用下面的复制 slice 的方式:

var b1, b2 []byte
for i, v := range b1 {
    b2[i] = v
}
for i := range b1 {
    b2[i] = b1[i]
}

而是使用内建的 copy 函数:

copy(b2, b1)

不要在 for 中使用多此一举的 true
不要这样:

for true {
}

而是要这样:

for {
}

append slice
不要这样:

var a, b []int
for _, v := range a {
    b = append(b, v)
}

而是要这样

var a, b []int
b = append(b, a...)

简化 range

var m map[string]int
for _ = range m { 
}
for _, _ = range m {
}

可以简化为

for range m {
}
分享:

评论