序言
看過很多方面的編碼規范,可能每一家公司都有不同的規范,這份編碼規范是寫給我自己的,同時希望我們公司內部同事也能遵循這個規范來寫Go代碼。
如果你的代碼沒有辦法找到下面的規范,那么就遵循標准庫的規范,多閱讀標准庫的源碼,標准庫的代碼可以說是我們寫代碼參考的標桿。
本文中凡是【】內為規則的都是參考的標准庫的源碼,和【】內為原則的一樣都是必須遵守的,【】內為建議的,是僅做建議遵守的。
本文參考了網上流傳的眾多開發規范帖子和標准款的規范代碼,最后梳理而成,因內容過多分為幾大篇,可選看,最后感謝那些原帖作者,文末給出了參考帖子的鏈接
目錄
- 統一規范篇
- 命名篇
- 開發篇
- 優化篇
統一規范篇
本篇主要描述了公司內部同事都必須遵守的一些開發規矩,如統一開發空間,既使用統一的開發工具來保證代碼最后的格式的統一,開發中對文件和代碼長度的控制,必須經過go語言自帶的檢測機制等。
1.1 合理規划目錄
【原則1.1】合理規划目錄,一個目錄中只包含一個包(實現一個模塊的功能),如果模塊功能復雜考慮拆分子模塊,或者拆分目錄。
說明:在Go中對於模塊的划分是基於package這個概念,可以在一個目錄中可以實現多個package,但是並不建議這樣的實現方式。主要的缺點是模塊之間的關系不清晰,另外不利於模塊功能擴展。
錯誤示例:
project
│ config.go
│ controller.go
│ filter.go
│ flash.go
│ log.go
│ memzipfile.go
│ mime.go
│ namespace.go
│ parser.go
│ router.go
│ staticfile.go
│ template.go
│ templatefunc.go
│ tree.go
│ util.go
| validation.go
| validators.go
推薦做法:
project
├─cache
│ │ cache.go
│ │ conv.go
│ │
│ └─redis
│ redis.go
├─config
│ │ config.go
│ │ fake.go
│ │ ini.go
│ └─yaml
│ yaml.go
├─logs
│ conn.go
│ console.go
│ file.go
│ log.go
│ smtp.go
└─validation
util.go
validation.go
validators.go
1.2 GOPATH設置
【建議1.2】使用單一的 GOPATH
雖說Go語言支持擁有多個 GOPATH,但多個GOPATH的情況並不具有彈性。GOPATH本身就是高度自我完備的(通過導入路徑)。有多個 GOPATH 會導致某些副作用,例如可能使用了給定的庫的不同的版本。你可能在某個地方升級了它,但是其他地方卻沒有升級。而且,我還沒遇到過任何一個需要使用多個 GOPATH 的情況。所以只使用單一的 GOPATH,這會提升你 Go 的開發進度。
許多人不同意這一觀點,接下來我會做一些澄清。像 etcd 或 camlistore 這樣的大項目使用了像 godep 這樣的工具,將所有依賴保存到某個目錄中。也就是說,這些項目自身有一個單一的 GOPATH。它們只能在這個目錄里找到對應的版本。除非你的項目很大並且極為重要,否則不要為每個項目使用不同的 GOPAHT。如果你認為項目需要一個自己的 GOPATH 目錄,那么就創建它,否則不要嘗試使用多個 GOPATH。它只會拖慢你的進度。
所有項目共用一個workspace,如下圖所示:
workspace/
├── bin
├── pkg
│ └── linux_amd64
│
└── src
├── project1
│
└── project2
│
└── project3
│
└── …
優點: 方便發布到github.com, 讓第三方通過go get等工具獲取。
內部項目,建議采用第一種工程結構。公開項目、提供給第三方集成的項目采用第二種項目結構。
1.3 import 規范
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"
)
有順序的引入包,不同的類型采用空格分離,第一種實標准庫,第二是項目包,第三是第三方包。
【規則1.3.1】在非測試文件(*_test.go)中,禁止使用 . 來簡化導入包的對象調用。
錯誤示例:
// 這是不好的導入
import . " pubcode/api/broker"
這種寫法不利於閱讀,因而不提倡。
【規則1.3.2】禁止使用相對路徑導入(./subpackage),所有導入路徑必須符合 go get 標准。
錯誤示例:
// 這是不好的導入
import "../net"
正確做法:
// 這是正確的做法
import "github.com/repo/proj/src/net"
【建議1.3.3】建議使用goimports工具或者IDE工具來管理多行import
go默認已經有了gofmt工具,但是我們強烈建議使用goimport工具,這個在gofmt的基礎上增加了自動刪除和引入包.
go get golang.org/x/tools/cmd/goimports
不同的編輯器有不同的配置, sublime的配置教程:http://michaelwhatcott.com/gosublime-goimports/
LiteIDE和GoLand默認已經支持了goimports,如果你的不支持請點擊屬性配置->golangfmt->勾選goimports
保存之前自動fmt你的代碼。
好處:import在多行的情況下,goimports工具會自動幫你格式化,自動刪除和引入包。很多IDE工具也可以自動檢查並糾正import路徑
1.4 代碼風格
Go語言對代碼風格作了很多強制的要求,並提供了工具gofmt, golint, go tool vet等工具檢查。
【規則1.4.1】提交代碼時,必須使用gofmt對代碼進行格式化。
大部分的格式問題可以通過 gofmt 來解決,gofmt 自動格式化代碼,保證所有的 go 代碼與官方推薦的格式保持一致,所有格式有關問題,都以gofmt的結果為准。所以,建議在提交代碼庫之前先運行一下這個命令。
gofmt(也可以用go fmt,其操作於程序包的級別,而不是源文件級別),讀入Go的源代碼,然后輸出按照標准風格縮進和垂直對齊的源碼,並且保留了根據需要進行重新格式化的注釋。如果你想知道如何處理某種新的布局情況,可以運行gofmt;如果結果看起來不正確,則需要重新組織你的程序,不要把問題繞過去。標准程序包中的所有Go代碼,都已經使用gofmt進行了格式化。
不需要花費時間對結構體中每個域的注釋進行排列,如下面的代碼,
type T struct {
name string // name of the object
value int // its value
}
gofmt將會按列進行排列:
type T struct {
name string // name of the object
value int // its value
}
【規則1.4.2】提交代碼時,必須使用golint對代碼進行檢查。
golint 會檢測的方面:
- 變量名規范
- 變量的聲明,像var str string = "test",會有警告,應該var str = "test"
- 大小寫問題,大寫導出包的要有注釋
- x += 1 應該 x++
等等
詳細可以看官方庫示例,https://github.com/golang/lint/tree/master/testdata
想速成的可以看Golang lint簡易使用方法自行學習使用
【建議1.4.3】提交代碼前,必須使用go vet對代碼進行檢查。
如果說golint是檢查我們的代碼規范的話,那么vet工具則是可以幫我們靜態分析我們的源碼存在的各種問題,例如多余的代碼,提前return的邏輯,struct的tag是否符合標准等。
go get golang.org/x/tools/cmd/vet
使用如下:
go vet .
1.5 大小約定
【建議1.5.1】單個文件長度不超過500行。
對開源引入代碼可以降低約束,新增代碼必須遵循。
【建議1.5.2】單個函數長度不超過50行。
函數兩個要求:單一職責、要短小
【規則1.5.3】單個函數圈復雜度最好不要超過10,禁止超過15。
說明:圈復雜度越高,代碼越復雜,就越難以測試和維護,同時也說明函數職責不單一。
【規則1.5.4】單行語句不能過長,如不能拆分需要分行寫。一行最多120個字符。
換行時有如下建議:
換行時要增加一級縮進,使代碼可讀性更好;
低優先級操作符處划分新行;換行時操作符應保留在行尾;
換行時建議一個完整的語句放在一行,不要根據字符數斷行
示例:
if ((tempFlag == TestFlag) &&
(((counterVar - constTestBegin) % constTestModules) >= constTestThreshold)) {
// process code
}
【建議1.5.5】函數中縮進嵌套必須小於等於3層。
舉例,禁止出現以下這種鋸齒形的函數:
func testUpdateOpts PushUpdateOptions) (err error) {
isNewRef := opts.OldCommitID == git.EMPTY_SHA
isDelRef := opts.NewCommitID == git.EMPTY_SHA
if isNewRef && isDelRef {
if isDelRef {
repo, err := GetRepositoryByName(owner.ID, opts.RepoName)
if err != nil {
if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) {
if err := CommitRepoAction(CommitRepoActionOptions{
PusherName: opts.PusherName,
RepoOwnerID: owner.ID,
RepoName: repo.Name,
RefFullName: opts.RefFullName,
OldCommitID: opts.OldCommitID,
NewCommitID: opts.NewCommitID,
Commits: &PushCommits{},
}); err != nil {
return fmt.Errorf("CommitRepoAction (tag): %v", err)
}
return nil
}
}
else {
owner, err := GetUserByName(opts.RepoUserName)
if err != nil {
return fmt.Errorf("GetUserByName: %v", err)
}
return nil
}
}
}
// other code
}
提示:如果發現鋸齒狀函數,應通過盡早通過return等方法重構。
【原則1.5.6】保持函數內部實現的組織粒度是相近的。
舉例,不應該出現如下函數:
func main() {
initLog()
//這一段代碼的組織粒度,明顯與其他的不均衡
orm.DefaultTimeLoc = time.UTC
sqlDriver := beego.AppConfig.String("sqldriver")
dataSource := beego.AppConfig.String("datasource")
modelregister.InitDataBase(sqlDriver, dataSource)
Run()
}
應該改為:
func main() {
initLog()
initORM() //修改后,函數的組織粒度保持一致
Run()
}
參考鏈接
https://studygolang.com/articles/2059
https://studygolang.com/articles/12033
https://blog.csdn.net/shuanger_/article/details/48241767
https://blog.csdn.net/tanzhe2017/article/list
