gofmt
所有代碼在發佈前均使用gofmt進行修正。
Comment Sentences(註釋應當是一個完整的句子)
所有的註釋都應該是一個完整的句子。句子應該以主語開頭,句號結尾。
這樣做,能使註釋在轉化成godoc時有一個不錯的格式。
Declaring Empty Slices(聲明空數組分片)
當你需要時,聲明空的數組分片。
這是一個推薦的做法:
var t []string
這是不好的:
t := []string{}
原因是,前者能避免分配內存空間。有些時候,可能你從沒向這個數組分片裏面append元素
Doc Comments(文檔註釋)
Go提供兩種註釋風格,C的塊註釋風格/**/,C++的行註釋風格//
每一個包都應該有包註釋,位於文件的頂部,在包名出現之前。
如果一個包有多個文件,包註釋只需要出現在一個文件的頂部即可。
包註釋建議使用C註釋風格,如果這個包特別簡單,需要的註釋很少,也可以選擇使用C++註釋風格。
每個public函數都應該有註釋,註釋句子應該以該函數名開頭,如:
// Compile parses a regular expression and returns, if successful,
// a Regexp that can be used to match against text.
func Compile(str string) (*Regexp, error) {
這樣做的好處是,但你要查找某個public函數的註釋時,grep函數名即可
Don’t Panic(不要拋出panic)
儘量不要使用panic處理錯誤。函數應該設計成多返回值,其中包括返回相應的error類型
Error Strings(error提示)
錯誤提示不需要大寫字母開頭的單詞,即使是句子的首字母也不需要。除非那是個專有名詞或者縮寫。
同時,錯誤提示也不需要以句號結尾,因爲通常在打印完錯誤提示後還需要跟隨別的提示信息。
Handle Errors(處理錯誤)
不要將error賦值給匿名變量_(因爲你不可以使用匿名變量,當把error賦值給匿名變量後,相當於拋棄了這個error)。
如果一個函數返回error,一定要檢查它是否爲空,判斷函數調用是否成功。如果不爲空,說明發生了錯誤,一定要處理它。
直接將它返回給上級調用,進入一段錯誤處理邏輯,甚至panic(不建議這麼做),都是可以的。
Imports
當import多個包時,應該對包進行分組。同一組的包之間不需要有空行,不同組之間的包需要一個空行。標準庫的包應該放在第一組。
goimports這個工具能直接幫你修正import包的規範。
以下是一個不錯的import示例:
package main
import (
"fmt"
"hash/adler32"
"os"
"appengine/foo"
"appengine/user"
"code.google.com/p/x/y"
"github.com/foo/bar"
)
Import Dot(Golang特性:import . 包名)
在測試中,我們很可能會使用這個特性,該特性能讓我們避免循環引用問題,思考一下下面的例子:
package foo_test
import (
"bar/testutil" // also imports "foo"
. "foo"
)
以上例子,該測試文件不能定義在於foo包裏面,因爲它import了bar/testutil,而bar/testutilimport了foo,這將構成循環引用。
所以我們需要將該測試文件定義在foo_test包中。使用了import . "foo"後,該測試文件內代碼能直接調用foo裏面的函數而不需要顯式地寫上包名。
但import dot這個特性,建議只在這種場景下使用,因爲它會大大增加代碼的理解難度。
Indent Error Flow(儘可能減少正常邏輯代碼的縮進)
但函數調用返回錯誤時,我們需要判斷錯誤是否爲空,若不爲空要進入錯誤處理的代碼分支,結束後再進入正常邏輯代碼。
應當儘可能減少正常邏輯代碼的縮進,這有利於提高代碼的可讀性,便於快速分辨出哪些還是正常邏輯代碼,例如:
這是一個不好的代碼風格,正常邏輯代碼被縮進在else分支裏面:
if err != nil {
// error handling
} else {
// normal code
}
這是一個不錯的代碼風格,沒有增加正常邏輯代碼的縮進:
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
另一種常見的情況,如果我們需要用函數的返回值來初始化某個變量,應該把這個函數調用單獨寫在一行,例如:
這是一個不好的代碼風格,函數調用,初始化變量x,判斷錯誤是否爲空都在同一行,並增加了正常邏輯代碼的縮進:
if x, err := f(); err != nil {
// error handling
return
} else {
// use x
}
這是一個不錯的代碼風格,將函數調用,初始化變量x寫在同一行,並且避免了正常邏輯代碼的縮進:
x, err := f()
if err != nil {
// error handling
return
}
// use x
Initialisms(首字母大寫和縮寫)
當一個單詞在代碼中,可以是全小寫的。也可以選擇首字母大寫,或者縮寫。值得注意的是,一旦該單詞選擇了首字母大寫或縮寫的風格,
就應當在整份代碼中保持這種風格,不要首字母大寫和縮寫兩種風格混用。
以URL爲例,如果選擇了縮寫URL這種風格,則應在整份代碼中保持,以下命名都是不錯的:URLPony,urlPony;切勿使用UrlPony這樣的風格。
以ID爲例子,如果選擇了縮寫ID這種風格,以下命名是不錯的:appleID;切勿使用appleId。
Line Length(代碼行長度)
在Golang中,沒有嚴格限制代碼行長度,但我們應該儘量避免一行內寫過長的代碼,以及將長代碼進行斷行。
每行不超過80個字符,依然是一個不錯的建議。
Mixed Caps(駝峯式命名)
Go建議使用駝峯式命名,不建議使用下劃線命名。
大寫開頭表示public,小寫開頭表示private。
Named Result Parameters(給函數返回值命名)
給函數返回值命名,是一條適用於任何場景的建議,我們直接來看對比例子:
這是一個不好的代碼風格,我們只知道函數返回的類型,但不知道每個返回值的名字:
func (n *Node) Parent1() *Node
func (n *Node) Parent2() (*Node, error)
這是一個不錯的代碼風格,我們準確知道每個返回值的名字:
func (n *Node) Parent1() (node *Node)
func (n *Node) Parent2() (node *Node, err error)
這條建議幾乎不需要過多的解釋。尤其對於一種場景,當你需要在函數結束的defer中對返回值做一些事情,給返回值名字實在是太必要了。
Package Names(包命名)
包名應該是全小寫單詞,不要使用下劃線;包名應該儘可能簡短,長單詞並不有助於可讀性。
Pass Values(傳遞值而不是指針)
不要爲了節省一點空間就傳遞指針而不是傳遞值。
除非要傳遞的是一個龐大的結構體或者可預知在將來會變得非常龐大的結構體,指針是一個不錯的選擇。
Receiver Names(接受者命名)
結構體函數中,接受者的命名不應該採用 me,this,self等通用的名字,而應該採用簡短的(1或2個字符)並且能反映出結構體名的命名風格。
例如,結構體名爲Client,接受者可以命名爲c或者cl。
這樣做的好處是,當生成了godoc後,過長或者過於具體的命名,會影響搜索體驗。
當接受者的名字足夠簡單和短時,它幾乎可能出現在每一行中(例如c,無處不在),它不必像參數命名那麼具體,因爲我們幾乎不關心接受者的名字。
另外,當一個結構體函數中的接受者命名要保持一致。上例中,如果使用了c,則不要使用cl
Receiver Type(接受者類型)
編寫結構體函數時,接受者的類型到底是選擇值還是指針通常難以決定。
一條萬能的建議:如果你不知道要使用哪種傳遞時,請選擇指針傳遞吧!
以下是一些不錯的建議:
- 當接受者是map, chan, func, 不要使用指針傳遞,因爲它們本身就是引用類型。
- 當接受者是slice,而函數內部不會對slice進行切片或者重新分配空間,不要使用指針傳遞。
- 當函數內部需要修改接受者,必須使用指針傳遞。
- 當接受者是一個結構體,並且包含了sync.Mutex或者類似的用於同步的成員。必須使用指針傳遞,避免成員拷貝。
- 當接受者類型是一個結構體並且很龐大,或者是一個大數組,建議使用指針傳遞來提高性能。
- 當接受者是結構體,數組或slice,並且其中的元素是指針,並且函數內部可能修改這些元素,那麼使用指針傳遞是個。不錯的選擇,這能使得函數的語義更加明確。
- 當接受者是小型結構體,小數組,並且不需要修改裏面的元素,裏面的元素又是一些基礎類型,使用值傳遞是個不錯的選擇。
Variable Names(變量命名)
- 變量命名應該儘可能短,尤其是局部變量。
- 對於一些特殊的變量以及全局變量,我們可能需要對它有更多的描述,使用長命名是個不錯的建議。