Go 语言规范

262 阅读6分钟

简介

在我们日常开发中,代码规范是非常重要的,也是每个开发者都需要共同去遵守的,确保代码的规范性。每个公司可能都会基于官方的规则基础上然后再结合公司的自身要求进行定义。以下是以官方的代码规范进行梳理。

代码注释

Package注释

  • 每一个包都必须有包的注解,含有多个文件的包,只需要在其中一个文件进行注释
  • 可以使用区间注释/**/或者行注释//进行
  • 注释的格式: Package 包名 包信息描述
  • 包名与包的注解间不能有空行
  • 首字母必须大写 区间注释
/*
Package regexp implements a simple library for regular expressions.

The syntax of the regular expressions accepted is:

    regexp:
        concatenation { '|' concatenation }
    concatenation:
        { closure }
    closure:
        term [ '*' | '+' | '?' ]
    term:
        '^'
        '$'
        '.'
        character
        '[' [ '^' ] character-ranges ']'
        '(' regexp ')'
*/
package regexp

行注释

// Package path implements utility routines for
// manipulating slash-separated filename paths.

Interface注释

  • interface定义上面紧跟这注释,注释以定义interface name开头,然后以句号结尾
    // ReadWriter is the interface that combines the Reader and Writer   interfaces.
    type ReadWriter interface {
        Reader
        Writer
    }
    

Struct注解

  • struct定义上面紧跟着注释,注释以定义struct name开头,然后以句号结尾
    // Request represents a request to run a command.
    type Request struct { ...
    

Func注解

  • func定义上面紧跟着注释,注释以定义func name开头,然后以句号结尾
    // Encode writes the JSON encoding of req to w.
    func Encode(w io.Writer, req *Request) { ...
    

命名

缩写词

  • 命名中缩写词需要保持大小写一致,推荐“URL”或“NATO”、“appID”、"XMLHTTPRequest".

包名(Package)

  • 使用小写字母进行命名,不使用下划线或者驼峰进行命名
  • 包名的命令需要简短,不建议使用超长的命名
  • 命名尽量不要和标准库的命名冲突
  • 包名和其目录名保持一致
import "bytes"

接口名(Interface)

  • 使用驼峰命名法,首字母大小写代表访问权限
  • 使用名词进行命名
  • 只有一个方法的接口名,通过为方法的名称后面加er。例如:方法名为ReadWrite对应的接口名为Reader或 Writer
type Writer interface {
   Write() error
}

接收者(Receiver Names)

  • 用其类型的一两个字母缩写作为接受者的命名 不推荐:
    func (file *File) Read(buf []byte) (n int, err error)
    
    推荐
    func (f *File) Read(buf []byte) (n int, err error)
    
  • 用类型的一两个字母作为接受者的命名,在上下文中都需要保持一致,不出现某处定义为“c”,另外一处定义为“cl”
  • 不推荐使用self、this等词作为接收者的命名,这些是面向对象经典的标识符

初始化

  • 定义空的slice

    不推荐:

    t := []string{}
    

    推荐:

    var t []string
    
  • 对struct的属性赋值的时候,推荐在初始化的时候进行赋值

    不推荐:

    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = nil
    f.nepipe = 0
    

    推荐:

    f := File{fd, name, nil, 0}
    return &f
    
  • 初始化的值直接作为返回值,推荐直接return初始化的实例,不建议定义局部变量再返回实例

    不推荐:

    f := File{fd, name, nil, 0}
    return &f
    

    推荐:

    return &File{fd, name, nil, 0}
    
  • 初始化的时候,某属性的值为0或nil则不需要再进行赋值

    不推荐:

    return &File{fd, name, nil, 0}
    

    推荐:

    return &File{fd: fd, name: name}
    

控制结构(Control structures)

If

  • 推荐局部变量的初始化和if判断在同一行进行编写
    if err := file.Chmod(0664); err != nil {
        log.Print(err)
        return err
    }
    
  • 利用breakcontinuegoto, or return,减少不必要的else
    f, err := os.Open(name)
    if err != nil {
        return err
    }
    codeUsing(f)
    

For

  • 使用简短局部变量声明,使在循环中声明索引变量变得容易

    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
    
  • 使用range进行遍历array, slice, string, map, channel读取

    // 需要遍历的key和value
    for key, value := range oldMap {
        newMap[key] = value
    }
    
    // 只需要遍历的key
    for key := range m {
        if key.expired() {
            delete(m, key)
        }
    }
    
    // 只需要遍历的value
    sum := 0
    for _, value := range array {
        sum += value
    }
    

imports

  • 避免包名的重命名,除非是有包名的冲突
  • 包的导入需要分区以空行隔开,标准库为第一组
    import (
      "fmt"
      "hash/adler32"
      "os"
    
      "appengine/foo"
      "appengine/user"
    
      "github.com/foo/bar"
      "rsc.io/goversion/version"
    )
    
  • 使用语法 import _ "pkg",只能在主包或者测试中导入
  • import . 用于解决测试中的循环依赖非常有用
    package foo_test
    
    import (
      "bar/testutil" // also imports "foo"
      . "foo"
    )
    

Package

  • 新增包的时候,需要添加使用可运行的使用示例或者完整链路调用的简单单测

Func

参数

  • 若函数有多个参数并且参数中包含了ctx,需要把ctx作为第一个参数
    func F(ctx context.Context, /* other arguments */) {}
    

返回值

  • 函数只有一个返回值时,不要使用括号将其括起来

    不推荐:

    func (n *Node) Parent1() (node *Node) {}
    

    推荐

    func (n *Node) Parent1() *Node {}
    
  • 函数返回两个或两个以上的返回值时,需要指定返回值的名称和类型

    不推荐:

    func (f *Foo) Location() (float64, float64, error)
    

    推荐

    // Location returns f's latitude and longitude.
    // Negative values mean south and west, respectively.
    func (f *Foo) Location() (lat, long float64, err error)
    
  • 函数的处理结果建议带上内部错误值 不推荐:

    func (n *Node) Parent2() (node *Node) {}
    

    推荐:

    func (n *Node) Parent2() (node *Node, err error) {}
    

Defer

  • 存在资源的管理时,应该紧跟这defer进行资源释放
  • 先进行资源创建结果的判断,在进行defer资源释放
// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close()  // f.Close will run when we're finished.

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...) // append is discussed later.
        if err != nil {
            if err == io.EOF {
                break
            }
            return "", err  // f will be closed if we return here.
        }
    }
    return string(result), nil // f will be closed if we return here.
}

错误处理

error处理

  • 错误字符串不应大写开头(除非以专有名词或首字母缩写词开头)

    不推荐:

    fmt.Errorf("Something bad")

    推荐:

    fmt.Errorf("something bad")

  • 对于函数返回的error,不要使用_变量将其丢弃掉,除非特殊情况需要使用panic

painc处理

  • 对于正常的错误处理不用使用panic,推荐使用使用error

单测

  • 单测文件名以_test.go结尾, ***_test.go

  • 测试方法以Test开头,TestXXX
    1、struct方法对应的测试方法的命名为 TestStructNameFuncName() 例如:func (d *Demo)DemoFunc()对应的测试方法命名为 TestDemoFunc()

    2、函数对应的测试方法命令 TestFuncName 例如:func DemoFunc() 对应的测试方法命名为 TestDemoFunc()

  • 测试用例必须断言

  • 测试应该失败,并显示有用的消息,说明哪里出了问题、输入了什么、实际得到了什么以及预期什么。

    if got != tt.want {
      t.Errorf("Foo(%q) = %d; want %d", tt.in, got, tt.want) // or Fatalf,   if test can't test anything more past this point
    }
    

参考材料

1.github.com/golang/go/w…
2.go.dev/doc/effecti…