Go編碼規範指南

astaxie發表於2016-10-09

Go編碼規範指南

序言

看過很多方面的編碼規範,可能每一家公司都有不同的規範,這份編碼規範是寫給我自己的,同時希望我們公司內部同事也能遵循這個規範來寫Go程式碼。

如果你的程式碼沒有辦法找到下面的規範,那麼就遵循標準庫的規範,多閱讀標準庫的原始碼,標準庫的程式碼可以說是我們寫程式碼參考的標杆。

格式化規範

go預設已經有了gofmt工具,但是我們強烈建議使用goimport工具,這個在gofmt的基礎上增加了自動刪除和引入包.

go get golang.org/x/tools/cmd/goimports

不同的編輯器有不同的配置, sublime的配置教程:http://michaelwhatcott.com/gosublime-goimports/

LiteIDE預設已經支援了goimports,如果你的不支援請點選屬性配置->golangfmt->勾選goimports

儲存之前自動fmt你的程式碼。

行長約定

一行最長不超過80個字元,超過的請使用換行展示,儘量保持格式優雅。

go vet

vet工具可以幫我們靜態分析我們的原始碼存在的各種問題,例如多餘的程式碼,提前return的邏輯,struct的tag是否符合標準等。

go get golang.org/x/tools/cmd/vet

使用如下:

go vet .

package名字

保持package的名字和目錄保持一致,儘量採取有意義的包名,簡短,有意義,儘量和標準庫不要衝突。

import 規範

import在多行的情況下,goimports會自動幫你格式化,但是我們這裡還是規範一下import的一些規範,如果你在一個檔案裡面引入了一個package,還是建議採用如下格式:

import (
    "fmt"
)

如果你的包引入了三種型別的包,標準庫包,程式內部包,第三方包,建議採用如下方式進行組織你的包:

import (
    "encoding/json"
    "strings"

    "myproject/models"
    "myproject/controller"
    "myproject/utils"

    "github.com/astaxie/beego"
    "github.com/go-sql-driver/mysql"
)   

有順序的引入包,不同的型別採用空格分離,第一種實標準庫,第二是專案包,第三是第三方包。

在專案中不要使用相對路徑引入包:

// 這是不好的匯入
import “../net”

// 這是正確的做法
import “github.com/repo/proj/src/net”

變數申明

變數名採用駝峰標準,不要使用_來命名變數名,多個變數申明放在一起

var (
    Found bool
    count int
)

在函式外部申明必須使用var,不要採用:=,容易踩到變數的作用域的問題。

自定義型別的string迴圈問題

如果自定義的型別定義了String方法,那麼在列印的時候會產生隱藏的一些bug

type MyInt int
func (m MyInt) String() string { 
    return fmt.Sprint(m)   //BUG:死迴圈
}

func(m MyInt) String() string { 
    return fmt.Sprint(int(m))   //這是安全的,因為我們內部進行了型別轉換
}

避免返回命名的引數

如果你的函式很短小,少於10行程式碼,那麼可以使用,不然請直接使用型別,因為如果使用命名變數很 容易引起隱藏的bug

func Foo(a int, b int) (string, ok){

}

當然如果是有多個相同型別的引數返回,那麼命名引數可能更清晰:

func (f *Foo) Location() (float64, float64, error)

下面的程式碼就更清晰了:

// Location returns f's latitude and longitude.
// Negative values mean south and west, respectively.
func (f *Foo) Location() (lat, long float64, err error)

錯誤處理

錯誤處理的原則就是不能丟棄任何有返回err的呼叫,不要採用_丟棄,必須全部處理。接收到錯誤,要麼返回err,要麼實在不行就panic,或者使用log記錄下來

error 資訊

error的資訊不要採用大寫字母,儘量保持你的錯誤簡短,但是要足夠表達你的錯誤的意思。

長句子列印或者呼叫,使用引數進行格式化分行

我們在呼叫fmt.Sprint或者log.Sprint之類的函式時,有時候會遇到很長的句子,我們需要在引數呼叫處進行多行分割:

下面是錯誤的方式:

log.Printf(“A long format string: %s %d %d %s”, myStringParameter, len(a),
    expected.Size, defrobnicate(“Anotherlongstringparameter”,
        expected.Growth.Nanoseconds() /1e6))

應該是如下的方式:

log.Printf( 
    “A long format string: %s %d %d %s”, 
    myStringParameter,
    len(a),
    expected.Size,
    defrobnicate(
        “Anotherlongstringparameter”,
        expected.Growth.Nanoseconds()/1e6, 
    ),
)   

注意閉包的呼叫

在迴圈中呼叫函式或者goroutine方法,一定要採用顯示的變數呼叫,不要再閉包函式裡面呼叫迴圈的引數

fori:=0;i<limit;i++{
    go func(){ DoSomething(i) }() //錯誤的做法
    go func(i int){ DoSomething(i) }(i)//正確的做法
}

http://golang.org/doc/articles/race_detector.html#Race_on_loop_counter

在邏輯處理中禁用panic

在main包中只有當實在不可執行的情況採用panic,例如檔案無法開啟,資料庫無法連線導致程式無法 正常執行,但是對於其他的package對外的介面不能有panic,只能在包內採用。

強烈建議在main包中使用log.Fatal來記錄錯誤,這樣就可以由log來結束程式。

註釋規範

註釋可以幫我們很好的完成文件的工作,寫得好的註釋可以方便我們以後的維護。詳細的如何寫註釋可以 參考:http://golang.org/doc/effective_go.html#commentary

bug註釋

針對程式碼中出現的bug,可以採用如下教程使用特殊的註釋,在godocs可以做到註釋高亮:

// BUG(astaxie):This divides by zero. 
var i float = 1/0

http://blog.golang.org/2011/03/godoc­documenting­go­code.html

struct規範

struct申明和初始化格式採用多行:

定義如下:

type User struct{
    Username  string
    Email     string
}

初始化如下:

u := User{
    Username: "astaxie",
    Email:    "astaxie@gmail.com",
}

recieved是值型別還是指標型別

到底是採用值型別還是指標型別主要參考如下原則:

func(w Win) Tally(playerPlayer)int    //w不會有任何改變 
func(w *Win) Tally(playerPlayer)int    //w會改變資料

更多的請參考:https://code.google.com/p/go-wiki/wiki/CodeReviewComments#Receiver_Type

帶mutex的struct必須是指標receivers

如果你定義的struct中帶有mutex,那麼你的receivers必須是指標

參考資料

  1. https://code.google.com/p/go-wiki/wiki/CodeReviewComments

  2. http://golang.org/doc/effective_go.html

相關文章