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

更多原創文章乾貨分享,請關注公眾號
  • Go 編碼規範指南
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章