當前版本: 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: []
感謝您的閱讀,以爲內容不錯,點個贊吧 😆