当前版本: v1.0.20201106GitHub: shockerli/go-code-guidehtml
buf
而不是bufio
整个应用或包的主入口文件应当是 main.go
,或与应用名称简写相同。git
好比:spiker
包的主入口文件是 spiker.go
,应用的主入口文件是 main.go
github
包名与目录名一致golang
若是一个目录下同时出现多个 package
,则编译失败:express
found packages pkg (a.go) and pb (b.go) in XXX
少让调用者去起别名,除非名字太烂json
MyPackage
、my_package
、myPackage
net/url
,而不是net/urls
common
、lib
、util
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。segmentfault
下划线开头的命名更不容许,Go 语言的公私有统一用大小写开头来区分。服务器
但有个例外,为了对相关的测试用例进行分组,函数名可能包含下划线,如:TestMyFunction_WhatIsBeingTested。架构
Bad:app
const ROLE_NAME = 10
Good:
const RoleName = 10
type Scheme string const ( Http Scheme = "http" Https Scheme = "https" )
type Symbol string const ( SymbolAdd Symbol = "+" SymbolSub Symbol = "-" )
在相对简单的环境(对象数量少、针对性强)中,能够将一些名称由完整单词简写为单个字母
user
能够简写为 u
userId
能够简写 uid
bool
类型,则名称应以 Has
、Is
、Can
或 Allow
开头var isExist bool var hasConflict bool var canManage bool var allowGitHook bool
/
代表层级关系-
来提升长路径中名称的可读性_
/
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)
q := list.New() // q is a *list.List
start := time.Now() // start is a time.Time t, err := time.Parse(time.Kitchen, "6:06PM") // t is a time.Time
ticker := time.NewTicker(d) // ticker is a *time.Ticker timer := 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) }
Has
、Is
、Can
或 Allow
等判断性动词开头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
结尾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.Reader
、 bufio.Reader
和 csv.Reader
。每一个包名搭配 Reader 都是个不错的类型名。
缩写名 | 说明 |
---|---|
ctx |
Context 或相关,好比 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
、项目文档、接口文档等,中文文档的排版参考:中文文案排版指北
// FileInfo is the interface that describes a file and is returned by Stat and Lstat type FileInfo interface { ... // HasPrefix returns true if name has any string in given slice as prefix func 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 description package 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 expression type 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 = tab indent_size = 4
或 GoLand 设置:
Preferences > Editor > Code Style > Go > Tabs and Indents
- 函数应按粗略的调用顺序排序
- 同一文件中的函数应按接收者分组
struct
、const
和 var
定义的后面。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() }
Bad:
var a int if b { a = 100 } else { a = 10 }
Good:
a := 10 if 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 }
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 设置以下:
对于 var
、const
、type
等声明语句:
Bad:
const a = 1 const b = 2 var a = 1 var b = 2 type Area float64 type Volume float64
Good:
const ( a = 1 b = 2 ) var ( a = 1 b = 2 ) type ( Area float64 Volume float64 )
Bad:
type Operation int const ( Add Operation = iota + 1 Subtract Multiply RoleName = "Role Name" )
Good:
type Operation int const ( 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 }
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
Bad:
fmt.Errorf("Something bad.")
Good:
fmt.Errorf("something bad")
Error 描述信息是须要被包裹或引用描述的,那么下面的代码将告诉咱们为什么不该如此:
log.Printf("Reading %s: %v", filename, err)
nil
是一个有效长度为 0 的 slice
Bad:
nums := []int{} // or, nums := make([]int, 0) if add1 { nums = append(nums, 1) }
Good:
var nums []int if 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 }
Bad:
var v []int s, _ := json.Marshal(v) println(string(s)) // output: null
Good:
v := make([]int, 0) s, _ := json.Marshal(v) println(string(s)) // output: []
感谢您的阅读,以为内容不错,点个赞吧 😆