以后版本: v1.0.20201106

GitHub: shockerli/go-code-guide

命名规定

  • 站在调用者的角度,包不是给你本人用的
  • 简洁、且见名知义
  • 采纳通用、公众熟知的缩写命名。比方buf而不是bufio
  • 如果缩写的名字会产生歧义,则放弃或换个

文件名

整个利用或包的主入口文件该当是 main.go,或与利用名称简写雷同。

比方:spiker 包的主入口文件是 spiker.go,利用的主入口文件是 main.go

包名

  • 包名与目录名统一

    如果一个目录下同时呈现多个 package,则编译失败:

    found packages pkg (a.go) and pb (b.go) in XXX
  • 大多数应用命名导入的状况下,不须要重命名

    少让调用者去起别名,除非名字太烂

  • 全副小写,没有下划线、大写。谬误示例MyPackagemy_packagemyPackage
  • 不必复数。例如net/url,而不是net/urls
  • 不必信息量有余的名字。谬误示例commonlibutil

导入包

  • 如果程序包名称与导入门路的最初一个元素不匹配,则必须应用导入别名
import (    client "example.com/client-go"    trace "example.com/trace/v2")
  • 在所有其余状况下,除非导入之间有间接抵触,否则应防止导入别名
import (    "net/http/pprof"    gpprof "github.com/google/pprof")
  • 如遇重名,请保留规范包而别名自定义或第三方包
  • 在非测试文件(*_test.go)中,禁止应用 . 来简化导入包的对象调用
  • 禁止应用相对路径导入(./subpackage),所有导入门路必须合乎 go get 规范

驼峰命名法

常量、变量、类型、构造体、接口、函数、办法、属性等,全副应用驼峰法 MixedCaps 或 mixedCaps。

下划线结尾的命名更不容许,Go 语言的公私有对立用大小写结尾来辨别。

但有个例外,为了对相干的测试用例进行分组,函数名可能蕴含下划线,如:TestMyFunction_WhatIsBeingTested。

Bad:

const ROLE_NAME = 10

Good:

const RoleName = 10

常量

  • 如果是枚举类型的常量,须要先创立相应类型
type Scheme stringconst (    Http  Scheme = "http"    Https Scheme = "https")
  • 如果模块的性能较为简单、常量名称容易混同的状况下,为了更好地区分枚举类型,能够应用残缺的前缀
type Symbol stringconst (    SymbolAdd Symbol = "+"    SymbolSub Symbol = "-")

变量

  • 在绝对简略的环境(对象数量少、针对性强)中,能够将一些名称由残缺单词简写为单个字母

    • user 能够简写为 u
    • userId 能够简写 uid
  • 若变量类型为 bool 类型,则名称应以 HasIsCanAllow 结尾
var isExist boolvar hasConflict boolvar canManage boolvar allowGitHook bool

URL

  • URL 命名全副小写
  • 用正斜杠 / 表明层级关系
  • 应用连字符 - 来进步长门路中名称的可读性
  • 不得在 URL 中应用下划线 _
  • URL 结尾不应蕴含正斜杠 /
  • 文件扩展名不应蕴含在 URL 中
  • URL 需见名知意,但不可裸露服务器架构

Bad:

/GetUserInfo/photos_path/My-Folder/my-doc//user/user-list

Good:

/user/list/user/operator-logs

函数/办法名

  • 不要画龙点睛
  • 长命名并不会使其更具可读性,一份有用的阐明文档通常比额定的长名更有价值

Bad:

once.DoOrWaitUntilDone(f)

Good:

once.Do(f)
  • 在 pkg 包中名为 New 的函数会返回一个 pkg.Pkg 类型的值
q := list.New()  // q is a *list.List
  • 当 pkg 包中某个函数的返回值类型为 pkg.Pkg (或 *pkg.Pkg )时,函数名应省略类型名
start := time.Now()                                  // start is a time.Timet, err := time.Parse(time.Kitchen, "6:06PM")         // t is a time.Time
  • 当函数返回的值类型为 pkg.T 且 T 不为 Pkg 时,函数名应蕴含 T 以便让用户代码更易了解
ticker := time.NewTicker(d)          // ticker is a *time.Tickertimer := time.NewTimer(d)            // timer is a *time.Timer
  • 获取器/设置器

    Go 并不对获取器(getter)和设置器(setter)提供主动反对。针对某个变量或字段,获取器名字无需携带 Get,设置器名字以 Set 结尾。

    若你有个名为 owner (小写,未导出)的字段,其获取器该当名为 Owner(大写,可导出)而非 GetOwner。

Bad:

owner := obj.GetOwner()if owner != user {    obj.SettingOwner(user)}

Good:

owner := obj.Owner()if owner != user {    obj.SetOwner(user)}
  • 若函数或办法为判断类型(返回值次要为 bool 类型),则名称应以 HasIsCanAllow 等判断性动词结尾
func HasPrefix(name string, prefixes []string) bool { ... }func IsEntry(name string, entries []string) bool { ... }func CanManage(name string) bool { ... }func AllowGitHook() bool { ... }

接口名

依照约定,只蕴含一个办法的接口该当以该办法的名称加上 -er 后缀来命名,如 Reader、Writer、Formatter/CloseNotifier 等。

名词用于接口名,动词用于接口的办法名。

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

Error

  • Error 类型的命名以 Error 结尾
type ParseError struct {    Line, Col int}
  • Error 类型的变量,以 Err结尾
var ErrBadAction = errors.New("somepkg: a bad action was performed")
  • 返回类型为 Error 的变量缩写采纳 err
func foo() {    res, err := somepkgAction()    if err != nil {        if err == somepkg.ErrBadAction {        }        if pe, ok := err.(*somepkg.ParseError); ok {             line, col := pe.Line, pe.Col             // ....        }    }}

其余

包内容的名字不能够包名结尾,因为无需反复包名

http 包提供的 HTTP 服务名为 http.Server ,而非 HTTPServer 。用户代码通过 http.Server 援用该类型,因而没有歧义。

不同包中的类型名能够雷同,因为客户端可通过包名辨别它们

例如,规范库中含有多个名为 Reader 的类型,包含 jpeg.Readerbufio.Readercsv.Reader。每个包名搭配 Reader 都是个不错的类型名。

名词缩写表

缩写名阐明
ctxContext 或相干,比方 gin.Context

分号

Go 其实也是用分号(;)来完结语句,但 Go 与 JavaScript 一样不倡议给繁多语句开端加分号,因为编译器会主动加分号。

像如下语句是齐全能够的:

go func() { for { dst <- <-src } }()

通常 Go 程序只在诸如 for 循环子句这样的中央应用分号,以此来将初始化器、条件及增量元素离开。如果你在一行中写多个语句,也须要用分号隔开。

if err := f(); err != nil {    g()}

也是因为这个起因,函数或管制语句的左大括号绝不能放在下一行。

if i < f()  // 报错{           // 报错    g()}

圆括号

控制结构(if、for 和 switch)不须要圆括号,语法上就不须要

文档

README、我的项目文档、接口文档等,中文文档的排版参考:中文文案排版指北

正文

  • 所有导出对象必须正文阐明其用处,非导出对象依据状况进行正文
  • 如果对象可数且无明确指定数量的状况下,一律应用复数模式和个别进行时形容,否则应用复数模式
  • 包、函数、办法和类型的正文阐明都是一个残缺的句子
  • 句子类型的正文首字母均需大写,短语类型的正文首字母需小写
  • 正文的单行长度不能超过 80 个字符,超过请强制换行
  • 可导出对象的正文,必须以对象的名称作为结尾
// FileInfo is the interface that describes a file and is returned by Stat and Lstattype FileInfo interface { ...// HasPrefix returns true if name has any string in given slice as prefixfunc HasPrefix(name string, prefixes []string) bool { ...

单行正文&多行正文

  • 两种正文格调,单行正文 //,多行正文 /* ... */
  • 多行正文仅用于包级别的文档正文,除此之外请用单行正文。包正文个别搁置到 doc.go 文件,且该文件仅蕴含文档正文内容
  • 单行正文符号与内容之间,请用一个空格隔开

Bad:

//Comments

Good:

// Comments

GoLand 可设置主动格式化:

Preferences > Editor > Code Style > Go > Other 勾选上 Add leading space to comments

包正文

  • 包级别的正文就是对包的介绍,只需在同个包的任一源文件中阐明即可无效
  • 对于 main 包,个别只有一行简短的正文用以阐明包的用处,且以项目名称结尾
// Write project descriptionpackage main
  • 对于一个简单我的项目的子包,个别状况下不须要包级别正文,除非是代表某个特定性能的模块
  • 对于简略的非 main 包,也可用一行正文概括
  • 对于绝对性能简单的非 main 包,个别都会减少一些应用示例或根本阐明,且以 Package <name> 结尾
/*Package http provides HTTP client and server implementations....*/package http
  • 特地简单的包阐明,可独自创立 doc.go 文件来加以阐明

函数与办法

  • 如果一句话不足以阐明全副问题,则可换行持续进行更加粗疏的形容
// Copy copies file from source to target path.// It returns false and error when error occurs in underlying function calls.
  • 若函数或办法为判断类型(返回值次要为 bool 类型),则以 <name> returns true if 结尾
// HasPrefix returns true if name has any string in given slice as prefix.func HasPrefix(name string, prefixes []string) bool { ...

构造、接口及其它类型

  • 类型的定义个别都以复数模式形容:
// Request represents a request to run a command.type Request struct { ...
  • 如果为接口,则个别以以下模式形容:
// FileInfo is the interface that describes a file and is returned by Stat and Lstat.type FileInfo interface { ...
  • 如果构造体属性较多,需对属性增加正文
// Var variable for expressiontype Var struct {    Key   string      `json:"key"`   // variable key    Value interface{} `json:"value"` // value    Desc  string      `json:"desc"`  // variable description}

其余阐明

  • 当某个局部期待实现时,可用 TODO: 结尾的正文来揭示保护人员。
  • 当某个局部存在已知问题进行须要修复或改良时,可用 FIXME: 结尾的正文来揭示保护人员。
  • 当须要特地阐明某个问题时,可用 NOTE: 结尾的正文:
// NOTE: os.Chmod and os.Chtimes don't recognize symbolic link,// which will lead "no such file or directory" error.return os.Symlink(target, dest)

格式化

咱们没有太多可选的余地,因为 Go 曾经标准好了,在 Go 世界没有此类和平。

缩进

缩进对立采纳4个空格,禁用制表符。

EditorConfig 设置:

[{Makefile,go.mod,go.sum,*.go}]indent_style = tabindent_size = 4

GoLand 设置:

Preferences > Editor > Code Style > Go > Tabs and Indents

空行

  • 适当减少空行以放弃代码段落清晰

其余

函数分组与程序

  • 函数应按粗略的调用程序排序
  • 同一文件中的函数应按接收者分组
  • 导出的函数应先呈现在文件中,放在 structconstvar 定义的前面。
  • 在定义类型之后,但在接收者的其余办法之前,可能会呈现一个 newXYZ() / NewXYZ()
  • 因为函数是按接收者分组的,因而一般工具函数应在文件开端呈现。
  • 因而,个别一个 struct 及相干办法组织为一个文件。

Bad:

func (s *something) Cost() {    return calcCost(s.weights)}type something struct{ ... }func calcCost(n int[]) int {...}func (s *something) Stop() {...}func newSomething() *something {    return &something{}}

Good:

type something struct{ ... }func newSomething() *something {    return &something{}}func (s *something) Cost() {    return calcCost(s.weights)}func (s *something) Stop() {...}func calcCost(n int[]) int {...}

缩小嵌套

Bad:

for _, v := range data {    if v.F1 == 1 {        v = process(v)        if err := v.Call(); err == nil {            v.Send()        } else {            return err        }    } else {        log.Printf("Invalid v: %v", v)    }}

Good:

for _, v := range data {    if v.F1 != 1 {        log.Printf("Invalid v: %v", v)        continue    }    v = process(v)    if err := v.Call(); err != nil {        return err    }    v.Send()}

不必要的 else

  • 少数时候,咱们能够把 else 分支里的代码提取为初始化。

Bad:

var a intif b {    a = 100} else {    a = 10}

Good:

a := 10if b {    a = 100}

全局变量申明

  • 全局变量,必须应用 var 关键字
  • 请勿指定类型,除非它与表达式的类型不同

Bad:

var a string = "abc"var s string = F()    func F() string { return "A" }

Good:

var a = "abc"// 因为 F() 曾经明确了返回一个字符串类型,因而咱们没有必要显式指定 s 的类型var s = F()    func F() string { return "A" }
  • 如果表达式的类型与所需的类型不齐全匹配,请指定类型
type myError struct{}func (myError) Error() string { return "error" }func F() myError { return myError{} }var err error = F()// F() 返回一个 myError 类型的实例,然而咱们要 error 类型

局部变量申明

  • 如果将变量明确设置为某个值,则应应用短变量申明模式(:=

Bad:

var s string = "abc"

Good:

s := "abc"
  • 如果变量专用于援用,则应用 var 关键字更适合
func s() {    var s string    f(&s)}
  • 如果变量是返回值,则定义在函数返回类型中
func f(list []int) (filtered []int) {    for _, v := range list {        if v > 10 {            filtered = append(filtered, v)        }    }    return}

import 包导入分组与排序

  • 同一文件,如果导入多个包,对其进行分组
  • 规范包第三方包自定义包,别离分组、空行分隔、排序

Bad:

import "a"import "golang.org/x/sys"import "runtime"import "github.com/gin-gonic/gin"import "b"import "fmt"

Good:

import (    "fmt"    "runtime"    "a"    "b"    "github.com/gin-gonic/gin"    "golang.org/x/sys")

GoLand 设置如下:

类似的申明进行分组

对于 varconsttype 等申明语句:

  • 将类似的申明放在一个组内

Bad:

const a = 1const b = 2var a = 1var b = 2type Area float64type Volume float64

Good:

const (    a = 1    b = 2)var (    a = 1    b = 2)type (    Area float64    Volume float64)
  • 仅将相干的申明放在一组,不要将不相干的申明放在一组

Bad:

type Operation intconst (    Add Operation = iota + 1    Subtract    Multiply    RoleName = "Role Name")

Good:

type Operation intconst (    Add Operation = iota + 1    Subtract    Multiply)const RoleName = "Role Name"
  • 分组应用的地位没有限度,函数内也可应用分组

Bad:

func f() string {    var red = color.New(0xff0000)    var green = color.New(0x00ff00)    var blue = color.New(0x0000ff)    // ...}

Good:

func f() string {    var (        red   = color.New(0xff0000)        green = color.New(0x00ff00)        blue  = color.New(0x0000ff)    )    // ...}

函数/办法的参数/返回类型程序

  • 简略类型优先于简单类型

Bad:

func F(u User, n int) {}

Good:

func F(n int, u User) {}
  • 尽可能将同种类型的参数放在相邻地位,则只需写一次类型

Bad:

func F(a int, c string, b int) {}

Good:

func F(a, b int, c string) {}
  • error 永远在最初一个返回类型

Bad:

func F() (error, int) {}

Good:

func F() (int, error) {}

构造体属性程序

咱们先看下示例:

构造体A - 定义:

struct {    a string    c string    b bool    d bool}

构造体A - 大小为40,内存布局图:

比照,构造体B - 定义:

struct {    a string    b bool    c string    d bool}

构造体B - 大小为48,内存布局图:

咱们发现,构造体的属性程序不同,占用的内存大小和布局是齐全不同的

那咱们因而约定:将雷同类型的属性尽量搁置在一起。即,举荐构造体A中的定义程序。

构造体中的嵌入

嵌入式类型(例如mutex)应位于构造体内的字段列表的顶部,并且必须有一个空行将嵌入式字段与惯例字段分隔开。

Bad:

type Client struct {    version int    http.Client}

Good:

type Client struct {    http.Client    version int}

初始化构造体时必须指定字段名

必须在初始化构造体时指定字段名,否则相干工具和 Review 都不给过。如果不指定,会对代码重构造成不可预期的结果。

Bad:

k := User{"John", "Doe", true}

Good:

k := User{    FirstName: "John",    LastName: "Doe",    Admin: true,}
惟一例外:如果有3个或更少的字段,则能够在测试表中省略字段名
tests := []struct{}{    op Operation    want string}{    {Add, "add"},    {Subtract, "subtract"},}

放大变量作用域

  • 如果有可能,尽量放大变量作用范畴,除非它与缩小嵌套的规定抵触

Bad:

err := ioutil.WriteFile(name, data, 0644)if err != nil {    return err}

Good:

if err := ioutil.WriteFile(name, data, 0644); err != nil {    return err}
  • 如果须要在 if 之外应用函数调用的后果,则不应尝试放大范畴

Bad:

if data, err := ioutil.ReadFile(name); err == nil {    err = cfg.Decode(data)    if err != nil {    return err    }    fmt.Println(cfg)    return nil} else {    return err}

Good:

data, err := ioutil.ReadFile(name)if err != nil {    return err}if err := cfg.Decode(data); err != nil {    return err}fmt.Println(cfg)return nil

Error 信息不应大写或标点符号完结

Bad:

fmt.Errorf("Something bad.")

Good:

fmt.Errorf("something bad")

Error 形容信息是须要被包裹或援用形容的,那么上面的代码将通知咱们为何不应如此:

log.Printf("Reading %s: %v", filename, err)

slice

nil 是一个无效长度为 0 的 slice
  • 零值切片可立刻应用,无需调用make创立

Bad:

nums := []int{}// or, nums := make([]int, 0)if add1 {    nums = append(nums, 1)}

Good:

var nums []intif add1 {    nums = append(nums, 1)}
  • 要查看切片是否为空,请始终应用 len(s) == 0,不要查看 nil

Bad:

func isEmpty(s []string) bool {    return s == nil}

Good:

func isEmpty(s []string) bool {    return len(s) == 0}
  • 对于须要序列化的切片,则必须应用make初始化

Bad:

var v []ints, _ := json.Marshal(v)println(string(s))// output: null

Good:

v := make([]int, 0)s, _ := json.Marshal(v)println(string(s))// output: []

参考资料

  • Package names - Go Blog
  • Style guideline for Go packages
  • Organizing Go code - Go Blog
  • How to Write Go Code - Go Blog
  • Effective Go
  • Go Code Convention
  • Go Wiki - Errors
  • Uber Go Style Guide

感谢您的浏览,感觉内容不错,点个赞吧 ????

原文地址: https://shockerli.net/post/go-code-guide-style