這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Golang代碼規範
參考https://golang.org/doc/effective_go.html
項目目錄結構規範
PROJECT_NAME├── README.md 介紹軟體及文檔入口├── bin 編譯好的二進位檔案,執行./build.sh自動產生,該目錄也用於程式打包├── build.sh 自動編譯的指令碼├── doc 該項目的文檔├── pack 打包後的程式放在此處├── pack.sh 自動打包的指令碼,產生類似xxxx.20170713_14:45:35.tar.gz的檔案,放在pack檔案下└── src 該項目的原始碼 ├── main 項目主函數 ├── model 項目代碼 ├── research 在實現該項目中探究的一些程式 └── vendor 存放go的庫 ├── github.com/xxx 第三方庫 └── xxx.com/obc 公司內部的公用庫
項目的目錄結構盡量做到簡明、層次清楚
檔案名稱命名規範
用小寫,盡量見名思義,看見檔案名稱就可以知道這個檔案下的大概內容,對於原始碼裡的檔案,檔案名稱要很好的代表了一個模組實現的功能。
命名規範
包名
包名用小寫,使用短命名,盡量和標準庫不要衝突
介面名
單個函數的介面名以”er”作為尾碼,如Reader,Writer
介面的實現則去掉“er”
type Reader interface { Read(p []byte) (n int, err error)}
兩個函數的介面名綜合兩個函數名
type WriteFlusher interface { Write([]byte) (int, error) Flush() error}
三個以上函數的介面名,類似於結構體名
type Car interface { Start([]byte) Stop() error Recover()}
變數
全域變數:採用駝峰命名法,僅限在包內的全域變數,包外引用需要寫介面,提供調用局部變數:駝峰式,小寫字母開頭
常量
常量:大寫,採用底線
import 規範
import在多行的情況下,goimports會自動幫你格式化,在一個檔案裡面引入了一個package,建議採用如下格式:
import ( "fmt")
如果你的包引入了三種類型的包,標準庫包,程式內部包,第三方包,建議採用如下方式進行組織你的包:
import ( "encoding/json" "strings" "myproject/models" "myproject/controller" "git.obc.im/obc/utils" "git.obc.im/dep/beego" "git.obc.im/dep/mysql")
在項目中不要使用相對路徑引入包:
// 這是不好的匯入
import “../net”
// 這是正確的做法
import “xxxx.com/proj/net”
函數名
函數名採用駝峰命名法,盡量不要使用底線
錯誤處理
error作為函數的值返回,必須儘快對error進行處理
採用獨立的錯誤流進行處理
不要採用這種方式
if err != nil { // error handling } else { // normal code }
而要採用下面的方式
if err != nil { // error handling return // or continue, etc. } // normal code
如果傳回值需要初始化,則採用下面的方式
x, err := f()if err != nil { // error handling return}// use x
Panic
在邏輯處理中禁用panic
在main包中只有當實在不可啟動並執行情況採用panic,例如檔案無法開啟,資料庫無法串連導致程式無法 正常運行,但是對於其他的package對外的介面不能有panic,只能在包內採用。建議在main包中使用log.Fatal來記錄錯誤,這樣就可以由log來結束程式。
Recover
recover用於捕獲runtime的異常,禁止濫用recover,在開發測試階段盡量不要用recover,recover一般放在你認為會有不可預期的異常的地方。
func server(workChan <-chan *Work) { for work := range workChan { go safelyDo(work) }}func safelyDo(work *Work) { defer func() { if err := recover(); err != nil { log.Println("work failed:", err) } }() // do 函數可能會有不可預期的異常 do(work)}
Defer
defer在函數return之前執行,對於一些資源的回收用defer是好的,但也禁止濫用defer,defer是需要消耗效能的,所以頻繁調用的函數盡量不要使用defer。
// Contents returns the file's contents as a string.func Contents(filename string) (string, error) { f, err := os.Open(filename) if err != nil { return "", err } defer f.Close() // f.Close will run when we're finished. var result []byte buf := make([]byte, 100) for { n, err := f.Read(buf[0:]) result = append(result, buf[0:n]...) // append is discussed later. if err != nil { if err == io.EOF { break } return "", err // f will be closed if we return here. } } return string(result), nil // f will be closed if we return here.}
控制結構
if
if接受初始化語句,約定如下方式建立局部變數
if err := file.Chmod(0664); err != nil { return err}
for
採用短聲明建立局部變數
sum := 0for i := 0; i < 10; i++ { sum += i}
range
如果只需要第一項(key),就丟棄第二個:
for key := range m { if key.expired() { delete(m, key) }}
如果只需要第二項,則把第一項置為底線
sum := 0for _, value := range array { sum += value}
return
儘早return:一旦有錯誤發生,馬上返回
f, err := os.Open(name)if err != nil { return err}d, err := f.Stat()if err != nil { f.Close() return err}codeUsing(f, d)
方法的接收器
名稱一般採用strcut的第一個字母且為小寫,而不是this,me或者self
type T struct{} func (p *T)Get(){}
如果接收者是map,slice或者chan,不要用指標傳遞
//Mappackage mainimport ( "fmt")type mp map[string]stringfunc (m mp) Set(k, v string) { m[k] = v}func main() { m := make(mp) m.Set("k", "v") fmt.Println(m)}
//Channelpackage mainimport ( "fmt")type ch chan interface{}func (c ch) Push(i interface{}) { c <- i}func (c ch) Pop() interface{} { return <-c}func main() { c := make(ch, 1) c.Push("i") fmt.Println(c.Pop())}
如果需要對slice進行修改,通過傳回值的方式重新賦值
//Slicepackage mainimport ( "fmt")type slice []bytefunc main() { s := make(slice, 0) s = s.addOne(42) fmt.Println(s)}func (s slice) addOne(b byte) []byte { return append(s, b)}
如果接收者是含有sync.Mutex或者類似同步欄位的結構體,必須使用指標傳遞避免複製
package mainimport ( "sync")type T struct { m sync.Mutex}func (t *T) lock() { t.m.Lock()}/*Wrong !!!func (t T) lock() { t.m.Lock()}*/func main() { t := new(T) t.lock()}
如果接收者是大的結構體或者數組,使用指標傳遞會更有效率。
package mainimport ( "fmt")type T struct { data [1024]byte}func (t *T) Get() byte { return t.data[0]}func main() { t := new(T) fmt.Println(t.Get())}