go程式設計規範
匯入標準庫、第三方或其它包
符合go fmt
規範
除標準庫外,Go 語言的匯入路徑基本上依賴程式碼託管平臺上的 URL 路徑,因此一個原始檔需要匯入的包有 4 種分類:標準庫、第三方包、組織內其它包和當前包的子包。
基本規則:
- 如果同時存在 2 種及以上,則需要使用分割槽來匯入。每個分類使用一個分割槽,採用空行作為分割槽之間的分割。
-
在非測試檔案(
*_test.go
)中,禁止使用.
來簡化匯入包的物件呼叫。 -
禁止使用相對路徑匯入(
./subpackage
),所有匯入路徑必須符合go get
標準。
下面是一個完整的示例:
import ( "flag" "github.com/go-openapi/spec" "github.com/robfig/cron" "xxx" )
註釋規範
符合github.com/golang/lint/golint
規範
- 所有匯出物件都需要註釋說明其用途;非匯出物件根據情況進行註釋。
- 如果物件可數且無明確指定數量的情況下,一律使用單數形式和一般進行時描述;否則使用複數形式。
- 包、函式、方法和型別的註釋說明都是一個完整的句子。
- 句子型別的註釋首字母均需大寫;短語型別的註釋首字母需小寫。
包級別
-
包級別的註釋就是對包的介紹,只需在同個包的任一原始檔中說明即可有效。
-
對於
main
包,一般只有一行簡短的註釋用以說明包的用途,且以專案名稱開頭:// Gogs (Go Git Service) is a painless self-hosted Git Service. package main
-
對於一個複雜專案的子包,一般情況下不需要包級別註釋,除非是代表某個特定功能的模組。
-
對於簡單的非
main
包,也可用一行註釋概括。 -
對於相對功能複雜的非
main
包,一般都會增加一些使用示例或基本說明,且以Package <name>
開頭:/* 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
-
特別複雜的包說明,可單獨建立ofollow,noindex" target="_blank">
doc.go
檔案來加以說明。
結構、介面及其它型別
-
型別的定義一般都以單數形式描述:
// Request represents a request to run a command. type Request struct { ...
-
如果為介面,以
er
作為字尾,介面的實現則去掉er
,則一般以以下形式描述:// FileInfo is the interface that describes a file and is returned by Stat and Lstat. type Reader interface { ...
函式與方法
-
函式與方法的註釋需以函式或方法的名稱作為開頭:
// Post returns *BeegoHttpRequest with POST method.
-
如果一句話不足以說明全部問題,則可換行繼續進行更加細緻的描述:
// 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 { ...
其它說明
-
當某個部分等待完成時,可用
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)
命名規則
檔名
-
整個應用或包的主入口檔案應當是
main.go
或與應用名稱簡寫相同。例如:Gogs
的主入口檔名為gogs.go
。普通檔案命名應當是全部小寫context.go
如複雜檔名應當是使用下劃線分詞file_reader.go
函式或方法
-
若函式或方法為判斷型別(返回值主要為
bool
型別),則名稱應以Has
,Is
等判斷性動詞開頭:func HasPrefix(name string, prefixes []string) bool { ... } func IsEntry(name string, entries []string) bool { ... }
常量
-
常量均需使用全部大寫字母組成,並使用下劃線分詞:
const APP_VER = "0.7.0.1110 Beta"
-
如果是列舉型別的常量,需要先建立相應型別:
type Scheme string const ( HTTPScheme = "http" HTTPS Scheme = "https" )
-
如果模組的功能較為複雜、常量名稱容易混淆的情況下,為了更好地區分列舉型別,可以使用完整的字首:
type PullRequestStatus int const ( PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota PULL_REQUEST_STATUS_CHECKING PULL_REQUEST_STATUS_MERGEABLE )
變數
-
變數命名基本上遵循相應的英文表達或簡寫。
-
在相對簡單的環境(物件數量少、針對性強)中,可以將一些名稱由完整單詞簡寫為單個字母,例如:
-
user
可以簡寫為u
-
userID
可以簡寫uid
-
-
若變數型別為
bool
型別,則名稱應以Has
,Is
開頭:var isExist bool var hasConflict bool
-
上條規則也適用於結構定義:
// Webhook represents a web hook object. type Webhook struct { IDint64 `gorm:"id"` RepoIDint64 OrgIDint64 URLstring `gorm:"url TEXT"` ContentTypeHookContentType Secretstring `gorm:"TEXT"` Eventsstring `gorm:"TEXT"` *HookEvent`gorm:"-"` IsSSLbool `gorm:"is_ssl"` IsActivebool HookTaskType HookTaskType Metastring`gorm:"TEXT"` // store hook-specific attributes LastStatusHookStatus // Last delivery status Createdtime.Time`gorm:"CREATED"` Updatedtime.Time`gorm:"UPDATED"` }
變數命名慣例
變數名稱一般遵循駝峰法,但遇到特有名詞時,需要遵循以下規則:
-
如果變數為私有,且特有名詞為首個單詞,則使用小寫,如
apiClient
。 -
其它情況都應當使用該名詞原有的寫法,如
APIClient
、repoID
、UserID
。
下面列舉了一些常見的特有名詞:
// A GonicMapper that contains a list of common initialisms taken from golang/lint var LintGonicMapper = GonicMapper{ "API":true, "ASCII": true, "CPU":true, "CSS":true, "DNS":true, "EOF":true, "GUID":true, "HTML":true, "HTTP":true, "HTTPS": true, "ID":true, "IP":true, "JSON":true, "LHS":true, "QPS":true, "RAM":true, "RHS":true, "RPC":true, "SLA":true, "SMTP":true, "SSH":true, "TLS":true, "TTL":true, "UI":true, "UID":true, "UUID":true, "URI":true, "URL":true, "UTF8":true, "VM":true, "XML":true, "XSRF":true, "XSS":true, }
宣告語句
函式或方法
函式或方法的引數排列順序遵循以下幾點原則(從左到右):
- 引數的重要程度與邏輯順序
- 簡單型別優先於複雜型別
- 儘可能將同種型別的引數放在相鄰位置,則只需寫一次型別
示例
以下宣告語句,User
型別要複雜於string
型別,但由於Repository
是User
的附屬品,首先確定User
才能繼而確定Repository
。因此,User
的順序要優先於repoName
。
func IsRepositoryExist(user *User, repoName string) (bool, error) { ...
程式碼指導
基本約束
-
所有應用的
main
包需要有APP_VER
常量表示版本,格式為X.Y.Z.Date [Status]
,例如:0.7.6.1112 Beta
。 -
單獨的庫需要有函式
Version
返回庫版本號的字串,格式為X.Y.Z[.Date]
。 -
當單行程式碼超過 80 個字元時,就要考慮分行。分行的規則是以引數為單位將從較長的引數開始換行,以此類推直到每行長度合適:
So(z.ExtractTo( path.Join(os.TempDir(), "testdata/test2"), "dir/", "dir/bar", "readonly"), ShouldBeNil)
-
當單行宣告語句超過 80 個字元時,就要考慮分行。分行的規則是將引數按型別分組,緊接著的宣告語句的是一個空行,以便和函式體區別:
// NewNode initializes and returns a new Node representation. func NewNode( importPath, downloadUrl string, tp RevisionType, val string, isGetDeps bool) *Node { n := &Node{ Pkg: Pkg{ ImportPath: importPath, RootPath:GetRootPath(importPath), Type:tp, Value:val, }, DownloadURL: downloadUrl, IsGetDeps:isGetDeps, } n.InstallPath = path.Join(setting.InstallRepoPath, n.RootPath) + n.ValSuffix() return n }
-
定義物件函式時,指標用p:
func (p *Node)NewNode(){}
-
分組宣告一般需要按照功能來區分,而不是將所有型別都分在一組:
const ( // Default section name. DEFAULT_SECTION = "DEFAULT" // Maximum allowed depth when recursively substituing variable names. _DEPTH_VALUES = 200 ) type ParseError int const ( ERR_SECTION_NOT_FOUND ParseError = iota + 1 ERR_KEY_NOT_FOUND ERR_BLANK_SECTION_NAME ERR_COULD_NOT_PARSE )
測試用例
- 單元測試都必須使用GoConvey 編寫,且輔助包覆蓋率必須在 80% 以上。
使用示例
-
為輔助包書寫使用示例的時,檔名均命名為
example_test.go
。 -
測試用例的函式名稱必須以
Test_
開頭,例如:Test_Logger
。 -
如果為方法書寫測試用例,則需要以
Text_<Struct>_<Method>
的形式命名,例如:Test_Macaron_Run
。