Golang 代碼規範
目錄
1. 前言
2. 代碼風格
2.1 【必須】格式化
2.2 【推薦】換行
2.3 【必須】括号和空格
2.4 【必須】import 規範
2.5 【必須】錯誤處理
2.5.1 【必須】error 處理
2.5.2 【必須】panic 處理
2.5.3 【必須】recover 處理
2.6 【必須】單元測試
2.7 【必須】類型斷言失敗處理
3. 注釋
3.1 【必須】包注釋
3.2 【必須】結構體注釋
3.3 【必須】方法注釋
3.4 【必須】變量和常量注釋
3.5 【必須】類型注釋
4. 命名規範
4.1 【推薦】包命名
4.2 【必須】檔案命名
4.3 【必須】結構體命名
4.4 【推薦】接口命名
4.5 【必須】變量命名
4.6 【必須】常量命名
4.7 【必須】函數命名
5. 控制結構
5.1 【推薦】if
5.2 【推薦】for
5.3 【必須】range
5.4 【必須】switch
5.5 【推薦】return
5.6 【必須】goto
6. 函數
6.1 【推薦】函數參數
6.2 【必須】defer
6.3 【推薦】方法的接收器
6.4 【推薦】代碼行數
6.5 【必須】嵌套
6.6 【推薦】變量聲明
6.7 【必須】魔法數字
7. 依賴管理
7.1 【必須】go1.11 以上必須使用 go modules 模式:
7.2 【推薦】代碼送出
8. 應用服務
8.1 【推薦】應用服務接口建議有 README.md
8.2 【必須】應用服務必須要有接口測試。
附:常用工具
為形成公司統一的 Go 編碼風格,以保障公司項目代碼的易維護性和編碼安全性,特制定本規範。
本規範在 Google Golang 代碼規範 的基礎上,根據騰訊實際情況進行了調整和補充。
每項規範内容,給出了要求等級,其定義為:
必須(Mandatory):使用者必須采用;
推薦(Preferable):使用者理應采用,但如有特殊情況,可以不采用;
可選(Optional):使用者可參考,自行決定是否采用;
目前本規範以 Gometalinter 工具落地,點選檢視工具源碼。
代碼都必須用 <code>gofmt</code> 格式化。
建議一行代碼不要超過<code>120列</code>,超過的情況,使用合理的換行方法換行。
例外場景:
import 子產品語句
工具生成代碼
struct tag
遵循 <code>gofmt</code> 的邏輯。
運算符和操作數之間要留白格。
作為輸入參數或者數組下标時,運算符和運算數之間不需要空格,緊湊展示。
使用 <code>goimports</code> 自動格式化引入的包名,import 規範原則上以 <code>goimports</code> 規則為準。
<code>goimports</code> 會自動把依賴包按首字母排序,并對包進行分組管理,通過空行隔開,預設分為本地包(标準庫、内部包)、第三方包。
标準包永遠位于最上面的第一組。
内部包是指不能被外部 import 的包,如 GoPath 模式下的包名或者非域名開頭的目前項目的 GoModules 包名。
帶域名的包名都屬于第三方包,如 company.code.oa.com/xxx/xxx,github.com/xxx/xxx,不用區分是否是目前項目内部的包。
<code>goimports</code> 預設最少分成本地包和第三方包兩大類,這兩類包必須分開不能放在一起。本地包或者第三方包内部可以繼續按實際情況細分不同子類。
不要使用相對路徑引入包:
應該使用完整的路徑引入包:
包名和 git 路徑名不一緻時,或者多個相同包名沖突時,使用别名代替:
【可選】匿名包的引用建議使用一個新的分組引入,并在匿名包上寫上注釋說明。
完整示例如下:
<code>error</code> 作為函數的值傳回,必須對 <code>error</code> 進行處理, 或将傳回值指派給明确忽略。對于 <code>defer xx.Close()</code>可以不用顯式處理。
<code>error</code> 作為函數的值傳回且有多個傳回值的時候,<code>error</code> 必須是最後一個參數。
錯誤描述不需要标點結尾。
采用獨立的錯誤流進行處理。
如果傳回值需要初始化,則采用下面的方式:
錯誤傳回的判斷獨立處理,不與其他變量組合邏輯判斷。
【推薦】建議 go1.13 以上,error 生成方式為:<code>fmt.Errorf("module xxx: %w", err)</code>。
在業務邏輯進行中禁止使用 <code>panic</code>。
在 <code>main</code> 包中隻有當完全不可運作的情況可使用 <code>panic</code>,例如:檔案無法打開,資料庫無法連接配接導緻程式無法正常運作。
對于其它的包,可導出的接口一定不能有 <code>panic</code>;在包内傳遞錯誤時,不推薦使用 <code>panic</code> 來傳遞 <code>error</code>。
建議在 <code>main</code> 包中使用 <code>log.Fatal</code> 來記錄錯誤,這樣就可以由 <code>log</code> 來結束程式,或者将 <code>panic</code> 抛出的異常記錄到日志檔案中,友善排查問題。
<code>panic</code> 捕獲隻能到 <code>goroutine</code> 最頂層,每個自行啟動的 <code>goroutine</code>,必須在入口處捕獲 <code>panic</code>,并列印詳細堆棧資訊或進行其它處理。
<code>recover</code> 用于捕獲 <code>runtime</code> 的異常,禁止濫用 <code>recover</code>。
必須在 <code>defer</code> 中使用,一般用來捕獲程式運作期間發生異常抛出的 <code>panic</code> 或程式主動抛出的 <code>panic</code>。
單元測試檔案名命名規範為 <code>example_test.go</code>。
測試用例的函數名稱必須以 <code>Test</code> 開頭,例如 <code>TestExample</code>。
如果存在 <code>func Foo</code>,單測函數可以帶下劃線,為 <code>func Test_Foo</code>。如果存在 <code>func (b *Bar) Foo</code>,單測函數可以為 <code>func TestBar_Foo</code>。下劃線不能出現在前面描述情況以外的位置。
單測檔案行數限制是普通檔案的2倍,即<code>1600行</code>。單測函數行數限制也是普通函數的2倍,即為<code>160行</code>。圈複雜度、列數限制、 import 分組等其他規範細節和普通檔案保持一緻。
由于單測檔案内的函數都是不對外的,所有可導出函數可以沒有注釋,但是結構體定義時盡量不要導出。
每個重要的可導出函數都要首先編寫測試用例,測試用例和正規代碼一起送出友善進行回歸測試。
<code>type assertion</code> 的單個傳回值形式針對不正确的類型将産生 <code>panic</code>。是以,請始終使用 <code>“comma ok”</code> 的慣用法。
在編碼階段同步寫好變量、函數、包注釋,注釋可以通過 <code>godoc</code> 導出生成文檔。
程式中每一個被導出的(大寫的)名字,都應該有一個文檔注釋。
所有注釋掉的代碼在送出 code review 前都應該被删除,除非添加注釋講解為什麼不删除, 并且标明後續處理建議(比如删除計劃)。
每個包都應該有一個包注釋。
包如果有多個 go 檔案,隻需要出現在一個 go 檔案中(一般是和包同名的檔案)即可,格式為:“// Package 包名 包資訊描述”。
每個需要導出的自定義結構體或者接口都必須有注釋說明。
注釋對結構進行簡要介紹,放在結構體定義的前一行。
格式為:"// 結構體名 結構體資訊描述"。
結構體内的可導出成員變量名,如果是個生僻詞,或者意義不明确的詞,就必須要給出注釋,放在成員變量的前一行或同一行的末尾。
每個需要導出的函數或者方法(結構體或者接口下的函數稱為方法)都必須有注釋。注意,如果方法的接收器為不可導出類型,可以不注釋,但需要質疑該方法可導出的必要性。
注釋描述函數或方法功能、調用方等資訊。
格式為:"// 函數名 函數資訊描述"。
每個需要導出的常量和變量都必須有注釋說明。
該注釋對常量或變量進行簡要介紹,放在常量或者變量定義的前一行。
大塊常量或變量定義時,可在前面注釋一個總的說明,然後每一行常量的末尾詳細注釋該常量的定義。
格式為:"// 變量名 變量資訊描述",斜線後面緊跟一個空格。
每個需要導出的類型定義(type definition)和類型别名(type aliases)都必須有注釋說明。
該注釋對類型進行簡要介紹,放在定義的前一行。
格式為:"// 類型名 類型資訊描述"。
命名是代碼規範中很重要的一部分,統一的命名規範有利于提高代碼的可讀性,好的命名僅僅通過命名就可以擷取到足夠多的資訊。
保持 <code>package</code> 的名字和目錄一緻。
盡量采取有意義、簡短的包名,盡量不要和标準庫沖突。
包名應該為小寫單詞,不要使用下劃線或者混合大小寫,使用多級目錄來劃分層級。
項目名可以通過中劃線來連接配接多個單詞。
簡單明了的包命名,如:<code>time</code>、<code>list</code>、<code>http</code>。
不要使用無意義的包名,如:<code>util</code>、<code>common</code>、<code>misc</code>、<code>global</code>。<code>package</code>名字應該追求清晰且越來越收斂,符合‘單一職責’原則。而不是像<code>common</code>一樣,什麼都能往裡面放,越來越膨脹,讓依賴關系變得複雜,不利于閱讀、複用、重構。注意,<code>xx/util/encryption</code>這樣的包名是允許的。
采用有意義,簡短的檔案名。
檔案名應該采用小寫,并且使用下劃線分割各個單詞。
采用駝峰命名方式,首字母根據通路控制采用大寫或者小寫。
結構體名應該是名詞或名詞短語,如 <code>Customer</code>、<code>WikiPage</code>、<code>Account</code>、<code>AddressParser</code>,它不應是動詞。
避免使用 <code>Data</code>、<code>Info</code> 這類意義太寬泛的結構體名。
結構體的聲明和初始化格式采用多行,例如:
命名規則基本保持和結構體命名規則一緻。
單個函數的接口名以 <code>er</code> 作為字尾,例如 <code>Reader</code>,<code>Writer</code>。
兩個函數的接口名綜合兩個函數名。
三個以上函數的接口名,類似于結構體名。
變量名必須遵循駝峰式,首字母根據通路控制決定使用大寫或小寫。
特有名詞時,需要遵循以下規則:
如果變量為私有,且特有名詞為首個單詞,則使用小寫,如 <code>apiClient</code>;
其他情況都應該使用該名詞原有的寫法,如 <code>APIClient</code>、<code>repoID</code>、<code>UserID</code>;
錯誤示例:<code>UrlArray</code>,應該寫成 <code>urlArray</code> 或者 <code>URLArray</code>;
詳細的專有名詞清單可參考這裡。
若變量類型為 <code>bool</code> 類型,則名稱應以 <code>Has</code>,<code>Is</code>,<code>Can</code> 或者 <code>Allow</code> 開頭。
私有全局變量和局部變量規範一緻,均以小寫字母開頭。
代碼生成工具自動生成的代碼可排除此規則(如 xxx.pb.go 裡面的 Id)。
變量名更傾向于選擇短命名。特别是對于局部變量。 <code>c</code>比<code>lineCount</code>要好,<code>i</code>比<code>sliceIndex</code>要好。基本原則是:變量的使用和聲明的位置越遠,變量名就需要具備越強的描述性。
常量均需遵循駝峰式。
如果是枚舉類型的常量,需要先建立相應類型:
私有全局常量和局部變量規範一緻,均以小寫字母開頭。
函數名必須遵循駝峰式,首字母根據通路控制決定使用大寫或小寫。
代碼生成工具自動生成的代碼可排除此規則(如協定生成檔案 xxx.pb.go , gotests 自動生成檔案 xxx_test.go 裡面的下劃線)。
<code>if</code> 接受初始化語句,約定如下方式建立局部變量:
<code>if</code> 對兩個值進行判斷時,約定如下順序:變量在左,常量在右:
<code>if</code> 對于bool類型的變量,應直接進行真假判斷:
采用短聲明建立局部變量:
如果隻需要第一項(key),就丢棄第二個:
如果隻需要第二項,則把第一項置為下劃線:
要求必須有 <code>default</code>:
盡早 <code>return</code>,一旦有錯誤發生,馬上傳回:
業務代碼禁止使用 <code>goto</code>,其他架構或底層源碼推薦盡量不用。
函數傳回相同類型的兩個或三個參數,或者如果從上下文中不清楚結果的含義,使用命名傳回,其它情況不建議使用命名傳回。
傳入變量和傳回變量以小寫字母開頭。
參數數量均不能超過<code>5個</code>。
盡量用值傳遞,非指針傳遞。
傳入參數是 <code>map</code>,<code>slice</code>,<code>chan</code>,<code>interface</code> 不要傳遞指針。
當存在資源管理時,應緊跟 <code>defer</code> 函數進行資源的釋放。
判斷是否有錯誤發生之後,再 <code>defer</code> 釋放資源。
禁止在循環中使用 <code>defer</code>,舉例如下:
【推薦】推薦以類名第一個英文首字母的小寫作為接收器的命名。
【推薦】接收器的命名在函數超過<code>20行</code>的時候不要用單字元。
【必須】命名不能采用 <code>me</code>,<code>this</code>,<code>self</code> 這類易混淆名稱。
【必須】檔案長度不能超過<code>800行</code>。
【推薦】函數長度不能超過<code>80行</code>。
嵌套深度不能超過<code>4層</code>:
變量聲明盡量放在變量第一次使用前面,就近原則。
如果魔法數字出現超過<code>2次</code>,則禁止使用。
用一個常量代替:
建議所有不對外開源的工程的 <code>module name</code> 使用 <code>test.oa.com/group/repo</code> ,友善他人直接引用。
建議使用 <code>go modules</code> 作為依賴管理的項目不送出 <code>vendor</code> 目錄。
建議使用 <code>go modules</code> 管理依賴的項目, <code>go.sum</code> 檔案必須送出,不要添加到 .gitignore 規則中。
其中建議包括服務基本描述、使用方法、部署時的限制與要求、基礎環境依賴(例如最低 go 版本、最低外部通用包版本)等。
go 語言本身在代碼規範性這方面也做了很多努力,很多限制都是強制文法要求,例如左大括号不換行,引用的包或者定義的變量不使用會報錯,此外 go 還是提供了很多好用的工具幫助我們進行代碼的規範。
<code>gofmt</code> ,大部分的格式問題可以通過 <code>gofmt</code> 解決, <code>gofmt</code> 自動格式化代碼,保證所有的 go 代碼與官方推薦的格式保持一緻,于是所有格式有關問題,都以 <code>gofmt</code> 的結果為準。
<code>goimports</code> ,此工具在 <code>gofmt</code> 的基礎上增加了自動删除和引入包。
<code>go vet</code> ,<code>vet</code> 工具可以幫我們靜态分析我們的源碼存在的各種問題,例如多餘的代碼,提前 <code>return</code> 的邏輯, <code>struct</code> 的 <code>tag</code> 是否符合标準等。編譯前先執行代碼靜态分析。
<code>golint</code> ,類似 <code>javascript</code> 中的 <code>jslint</code> 的工具,主要功能就是檢測代碼中不規範的地方。
I can see a bigger world.