Golang Programming Style

前言

本規(guī)范是針對(duì) Go 語言的編碼規(guī)范,目的是為了統(tǒng)一項(xiàng)目的編碼風(fēng)格,提高源程序的可讀性、可靠性和可重用性,從而提高軟件的質(zhì)量。

本規(guī)范適用于所有產(chǎn)品的軟件源程序,同時(shí)考慮到不同產(chǎn)品和項(xiàng)目的實(shí)際開發(fā)特性,本規(guī)范分成規(guī)則性和建議性兩種:對(duì)于規(guī)則性規(guī)范,要求所有軟件開發(fā)人員嚴(yán)格執(zhí)行;對(duì)于建議性規(guī)范,各項(xiàng)目編程人員可以根據(jù)實(shí)際情況選擇執(zhí)行。本規(guī)范的示例都以Go 語言描述。

本規(guī)范的內(nèi)容包括:開發(fā)環(huán)境、包設(shè)計(jì)、布局、注釋、命名、基本元素設(shè)計(jì)、函數(shù)設(shè)計(jì)、錯(cuò)誤和異常設(shè)計(jì)、整潔測試等。

對(duì)本規(guī)范中所用的術(shù)語解釋如下:
原則:編程時(shí)應(yīng)該堅(jiān)持的指導(dǎo)思想。
規(guī)則:編程時(shí)必須遵守的約定。
建議:編程時(shí)必須加以考慮的約定。
說明:對(duì)此規(guī)則或建議的必要的解釋。
正例:對(duì)此規(guī)則或建議給出的正確例子。
反例:對(duì)此規(guī)則或建議給出的反面例子。

golang.jpg

開發(fā)環(huán)境

【規(guī)則1-1】為了防止代碼出現(xiàn)可移植性問題和兼容性問題,團(tuán)隊(duì)使用的操作系統(tǒng)、編譯器類型、版本保持一致性。

【規(guī)則1-2】團(tuán)隊(duì)統(tǒng)一使用相同的IDE,并使用統(tǒng)一的代碼模板,保持代碼風(fēng)格的一致性。

說明:系統(tǒng)中所有的代碼看起來就好像是由單獨(dú)一個(gè)值得勝任的人編寫的。

【規(guī)則1-3】團(tuán)隊(duì)統(tǒng)一配置 IDE 的 TAB 為4個(gè)空格。

包設(shè)計(jì)

【原則2-1】包設(shè)計(jì)要滿足單一職責(zé)原則。

說明: 這是SRP(Single Reponsibility Priciple) 在包(package)設(shè)計(jì)時(shí)的一個(gè)具體運(yùn)用,我們要將包設(shè)計(jì)的非常內(nèi)聚。

【原則2-2】包內(nèi)標(biāo)識(shí)符遵守最小可見性原則。
說明: 如果一個(gè)標(biāo)識(shí)符(interface名、類型名、變量名或函數(shù)名)在語義上僅在包內(nèi)可見,則它的命名不要用大寫開頭。

【規(guī)則2-1】測試文件放在實(shí)現(xiàn)文件的同級(jí)目錄下,便于Go語言工具的使用。

說明: 雖然測試文件和實(shí)現(xiàn)文件的代碼在同一個(gè)包內(nèi),但是測試用例的設(shè)計(jì)盡量站在包用戶的角度去考慮,一般只測試包外可見的函數(shù)。

【規(guī)則2-2】包間禁止共享全局變量。

說明:常量除外

【規(guī)則2-3】 不允許一個(gè)目錄下有多個(gè)包。

布局

【規(guī)則3-1】import 導(dǎo)入包時(shí)統(tǒng)一使用小括號(hào),包名要另起一行

說明:import "C" 除外

正例:

import (
    "fmt"
    "reflect"
)

反例:

import "fmt"
import "reflect"

【規(guī)則3-2】import 包時(shí),路徑分隔符一律使用Unix 風(fēng)格,拒絕使用Windows 風(fēng)格;即采用/ 而不是使用\ 分割路徑。

正例:

import (
    "cs/domain/model/object"
)

反例:

import (
    "cs\domain\model\object"
)

【規(guī)則3-3】包含空格在內(nèi),代碼的行寬不應(yīng)超過120列。
說明: 長行要在低優(yōu)先級(jí)操作符處拆分成新行,拆分出的新行要進(jìn)行適當(dāng)?shù)目s進(jìn),使排版整齊。

【規(guī)則3-4】程序?qū)嶓w之間有且僅有一行空行區(qū)分。
說明: 函數(shù)之間的空行,能夠幫組我們快速定位函數(shù)的始末的準(zhǔn)確位置;甚至在函數(shù)內(nèi)部,將邏輯相關(guān)的代碼放在一起也同樣具有意義,它能夠幫組我們更好地理解代碼塊的語義。超過一行的空行完全沒有必要,部分粗心的程序員在處理這些細(xì)節(jié)時(shí)總存在著或多或少的問題,團(tuán)隊(duì)?wèi)?yīng)該杜絕這樣的情況發(fā)生。

【規(guī)則3-5】每個(gè)文件末尾都應(yīng)該有且僅有一行空行。

【規(guī)則3-6】一元操作符如“!”、“~”、“++”、“--”、 “*”、 “&”(地址運(yùn)算符)等前后不加空格; “[]”、“.”、“->”這類操作符前后不加空格。

【規(guī)則3-7】函數(shù)名之后不要留空格
說明: 函數(shù)名后緊跟左括號(hào)‘(’,以與關(guān)鍵字區(qū)別。
正例:

func getStatus() string {
    return status
}

反例:

func getStatus () string {
    return status
}

【規(guī)則3-8】在進(jìn)行“==”或“!="比較時(shí),將常量或常數(shù)放在“==”或“!="號(hào)的右邊。
正例:

if type == "book" {
    return true
}

反例:

if "book" == type {
    return link
}

【規(guī)則3-9】 數(shù)組的初始化按照矩陣結(jié)構(gòu)分行書寫。

正例:

numbers := [4][3]int {
    1, 1, 1,
    2, 4, 8,
    3, 9, 27,
    4, 16, 64,
}

【建議3-1】 每次提交代碼前,使用gofmt工具格式化一下。

注釋

注釋有助于理解代碼,有效的注釋是指在代碼的功能、意圖層次上進(jìn)行注釋,提供有用、額外的信息,而不是代碼表面意義的簡單重復(fù)。
注釋的恰當(dāng)用法是彌補(bǔ)我們?cè)谟么a表達(dá)意圖時(shí)遭遇的失敗。每次寫注釋,你都應(yīng)該做個(gè)鬼臉,感受自己在表達(dá)能力上的失敗。
寫注釋時(shí),首先想到的應(yīng)該是通過重構(gòu)來提高表達(dá)力,不要太早放棄。

【規(guī)則4-1】注釋與所描述內(nèi)容進(jìn)行同樣的縮進(jìn)。
說明: 可使程序排版整齊,并方便注釋的閱讀與理解。

【規(guī)則4-2】避免垃圾注釋。
說明: 對(duì)于代碼本身能夠表達(dá)的意思,不必增加額外的注釋。

【規(guī)則4-3】注釋符 “//” 或 "/*” (“*/”) 與注釋內(nèi)容之間用一個(gè)空格分隔。

【建議4-1】并非所有的函數(shù)都要配有函數(shù)頭,短函數(shù)需要一個(gè)好名字而非太多描述。

【建議4-2】提倡代碼自注釋。
說明: 能用函數(shù)或變量時(shí)就不要用注釋,如果可以的話,應(yīng)該創(chuàng)建一個(gè)描述與注釋所言同一事物的函數(shù)或變量用于消除注釋。

【建議4-3】行注釋和塊注釋都可行時(shí),優(yōu)先使用行注釋。

【建議4-4】保證代碼和注釋的一致性。

說明: 修改代碼同時(shí)修改相應(yīng)的注釋,不再有用的注釋要?jiǎng)h除。

【建議4-5】注釋應(yīng)與其描述的代碼相近,對(duì)代碼的注釋應(yīng)放在其上方或右方(對(duì)單條語句的注釋)相鄰位置,不可放在下面,如放于上方則需與其上面的代碼用空行隔開,而且注釋內(nèi)容與與被注釋的代碼相同縮進(jìn)。

命名

【規(guī)則5-1】命名要名副其實(shí)——要像給自己的baby 起名字一樣謹(jǐn)慎來對(duì)待程序命名。

說明: 變量、函數(shù)的命名告訴我們,它為什么會(huì)存在,它做什么事,應(yīng)該怎么用。如果名稱需要注釋來補(bǔ)充,那就不算是名副其實(shí)。要像給自己的baby 起名字一樣謹(jǐn)慎來對(duì)待程序命名。

【規(guī)則5-2】目錄名一律使用小寫和中劃線風(fēng)格的名稱。

正例:
task-agent

反例:
taskagent
task_agent
TaskAgent
taskAgent

【規(guī)則5-3】 包名一律使用小寫風(fēng)格,通常為過濾掉中劃線的目錄名。

正例1:
目錄名:service
對(duì)應(yīng)的包名:service

正例2:
目錄名:project-obj
對(duì)應(yīng)的包名:projectobj

【規(guī)則5-4】 開發(fā)文件命名一律使用小寫和下劃線風(fēng)格的名稱。

正例:
openstack_virtual_machine.go

反例:
openstack-virtual-machine.go
OpenstackVirtualMachine.go
openstackvirtualmachine.go

【規(guī)則5-5】標(biāo)識(shí)符要采用英文單詞或其組合,便于記憶和閱讀,切忌使用漢語拼音來命名。

說明: 標(biāo)識(shí)符應(yīng)當(dāng)直觀且可以拼讀,可望文知義,避免使人產(chǎn)生誤解。程序中的英文單詞一般不要太復(fù)雜,用詞應(yīng)當(dāng)準(zhǔn)確。

【規(guī)則5-6】如果函數(shù)返回值的類型為bool,則名字前面加上is, has, may, can, should, need等詞修飾會(huì)增強(qiáng)語意。

正例:

func isDigit() bool

反例:

func digit() bool

【規(guī)則5-7】接口名、類型名、變量名和函數(shù)名統(tǒng)一使用駝峰命名法,首字母是否大寫由包外可見性決定。
說明: 應(yīng)遵循最小可見性原則

【規(guī)則5-8】避免在名稱中攜帶類型信息。

正例:

var num int
var ports []Port

反例:

var iNum int
var portSlice []Port

【規(guī)則5-9】 變量名的主體應(yīng)當(dāng)使用“名詞”或者“形容詞+名詞”。

【規(guī)則5-10】 函數(shù)名應(yīng)當(dāng)使用“動(dòng)詞”或者“動(dòng)詞+名詞”(動(dòng)賓詞組)。

【規(guī)則5-11】 系統(tǒng)中每個(gè)實(shí)體概念對(duì)應(yīng)一個(gè)詞。

說明: 給每個(gè)抽象概念選一個(gè)詞,并且在同一個(gè)系統(tǒng)中統(tǒng)一,以便符合SRP 原則。如在同一系統(tǒng)的代碼中既有controller,還有manager 和driver,會(huì)令使用者困惑,應(yīng)統(tǒng)一。

【規(guī)則5-12】 不使用雙關(guān)語命名變量。

說明: 變量命名時(shí)應(yīng)避免將同一單詞用于不同目的,同一術(shù)語用于不同概念,應(yīng)遵從“一詞一義”規(guī)則。比如add在表達(dá)計(jì)算兩個(gè)值的和的語義時(shí),就不能再表達(dá)往一個(gè)數(shù)組切片插入一個(gè)元素的語義。

【規(guī)則5-13】 常量名使用駝峰命名法,首字母是否大寫由包外可見性決定。
說明: 應(yīng)遵循最小可見性原則

【規(guī)則5-14】 團(tuán)隊(duì)使用統(tǒng)一的縮略語,并和業(yè)界常用的縮略語保持一致。

說明: 較短的單詞可通過去掉“元音”形成縮寫,較長的單詞可取單詞的頭幾個(gè)字母形成縮寫,一些單詞有大家公認(rèn)的縮寫,常用單詞的縮寫必須統(tǒng)一。協(xié)議中的單詞的縮寫與協(xié)議保持一致。對(duì)于某個(gè)系統(tǒng)使用的專用縮寫應(yīng)該在某處注釋中做統(tǒng)一說明。

正例: 如下單詞的縮寫能夠被大家認(rèn)可
temp 可縮寫為:tmp
flag 可縮寫為:flg
statistic 可縮寫為:stat
increment 可縮寫為:inc
message可縮寫為:msg

規(guī)范的常用縮寫如下:

常用詞 縮寫 常用詞 縮寫
Argument Arg Buffer Buf
Clear Clr Clock Clk
Compare Cmp Configuration Cfg
Context Ctx Delay Dly
Device Dev Disable Dis
Display Disp Enable En
Error Err Function Fnct
Hexadecimal Hex High Priority Task HPT
I/O System IOS Initialize Init
Mailbox Mbox Manager Mgr
Maximum Max Message Msg
Minimum Min Multiplex Mux
Operating System OS Overflow Ovf
Parameter Param Pointer Ptr
Previous Prev Priority Prio
Read Rd Ready Rdy
Register Reg Request Req
Response Rsp Schedule Sched
Semaphore Sem Stack Stk
Synchronize Sync Timer Tmr
Trigger Trig Write Wr

【規(guī)則5-16】 用正確的反義詞組命名具有互斥意義的變量或相反動(dòng)作的函數(shù)等。

正例:

詞組 詞組
add / remove insert / delete
create / destroy begin / end
first / last lock / unlock
increment / decrement push / pull
open / close min / max
old / new start / stop
next / previous source / destination
show / hide send / receive
attach / detach left / right
up / down north / south

基本元素設(shè)計(jì)

變量與常量

【規(guī)則6-1-1】 一個(gè)變量有且只有一個(gè)功能,并與其名稱相一致,不能把一個(gè)變量用作多種用途。

說明: 一個(gè)變量只用來表示一個(gè)特定功能,不能把一個(gè)變量用作多種用途,即同一變量取值不同時(shí),其代表的意義也不同。除循環(huán)變量和收集計(jì)算結(jié)果的變量,在一個(gè)函數(shù)中,一個(gè)變量被賦值不應(yīng)該超過一次。

【規(guī)則6-1-2】 代碼中不允許出現(xiàn)魔法數(shù)。

說明: 魔法數(shù),即擁有特殊意義,卻又不能明確表現(xiàn)出這種意義的數(shù)字。用const來定義常數(shù),并根據(jù)其意義為它命名,既提高了代碼的可讀性,又便于使用IDE 等工具進(jìn)行查找修改。

【規(guī)則6-1-3】 如果 struct 中的數(shù)據(jù)變量需要進(jìn)行 json 序列化,則需要以大寫字母開頭,同時(shí)需要 json 重命名。

說明: 結(jié)構(gòu)體中的變量以大寫字母開頭,可以保證 json.Marshal 的時(shí)候數(shù)據(jù)持久化正確。如果結(jié)構(gòu)體中的變量以小寫字母開頭,則使得 json.Marshal 的時(shí)候忽略該字段,使得該字段的值丟失,從而 json.Unmarshal 的時(shí)候?qū)⒃撟兞康闹抵脼槟J(rèn)值。由于結(jié)構(gòu)體中的變量以大寫字母開頭, json 串中的字段 key 的字符串形式變成了以大寫字母開始,這對(duì)于追求以 json 串全小寫為美的我們來說,需要進(jìn)行 json 重命名。

正例:

type Position struct {
    X int `json:"x"`
    Y int `json:"y"`
    Z int `json:"z"`
}

type Student struct {
    Name string `json:"name"`
    Sex string `json:"sex"`
    Age int `json:"age"`
    Posi Position `json:"position"`
}

反例:

type Position struct {
    X int
    Y int
    Z int
}

type Student struct {
    Name string
    Sex string
    Age int
    Posi Position
}

【建議6-1-1】 變量應(yīng)盡可能的滿足短跨度和短存活時(shí)間。

說明: 那些介于同一個(gè)變量多個(gè)引用點(diǎn)之間的代碼可稱為攻擊窗口,我們用跨度來衡量一個(gè)變量的不同引用點(diǎn)之間的靠近程度,而變量的存活時(shí)間是一個(gè)變量存在期間所跨越的語句總數(shù)。跨度越短,則表明一個(gè)變量的不同引用點(diǎn)越靠近;存活時(shí)間越短,則表明一個(gè)變量經(jīng)歷的語句數(shù)越少。

我們追求的目標(biāo)是短跨度和短存活時(shí)間,因?yàn)?br> (1)可以提高程序的可讀性;
(2)可以減小變量的攻擊窗口;
(3)可以減少變量的初始化錯(cuò)誤;
(4)可以減少全局變量的使用;
(5)可以方便修改Bug;
(6)可以方便重構(gòu)代碼。

表達(dá)式和語句

【規(guī)則6-2-1】 對(duì)于布爾類型的變量,應(yīng)直接進(jìn)行真假判斷

正例:

/* 設(shè)flag 是布爾類型的變量 */
if flag  /* 表示flag為真 */
if !flag  /* 表示flag為假 */

反例:

/* 設(shè)flag 是布爾類型的變量 */
if flag == true
if flag == 1
if flag == false
if flag == 0

【規(guī)則6-2-2】 在條件判斷語句中,當(dāng)整型變量與0 比較時(shí),不可模仿布爾變量的風(fēng)格,應(yīng)當(dāng)將整型變量用“==”或“!=”直接與0比較。

正例:

/* 設(shè)value是整型的變量 */
if value == 0
if value != 0

反例:

/* 設(shè)value是整型的變量 */
if value /* 會(huì)讓人誤解 value是布爾類型的變量 */
if !value

【規(guī)則6-2-3】 邏輯表達(dá)式已經(jīng)具有 true 或 false 語義,無需畫蛇添足。

正例:

return i == 3

反例:

if i == 3 {
    return true
} else {
    return false
}

【建議6-2-1】 循環(huán)嵌套次數(shù)不大于3。

【建議6-2-2】 if 語句的嵌套層數(shù)不要大于3。

說明: 適當(dāng)調(diào)整和優(yōu)化判斷邏輯,能夠有效地控制if語句的嵌套層次,這對(duì)于代碼的走查、測試、變更維護(hù)都有很大的幫助。如果能減少大語句塊的嵌套深度,對(duì)于減輕代碼閱讀時(shí)的理解負(fù)擔(dān)很有好處。
條件式通常有兩種呈現(xiàn)形式:第一種形式是所有分支都屬于正常行為;第二種形式則是條件式提供的答案只有一種是正常行為,其他都是不常見的情況。
這兩類條件式有不同的用途,這一點(diǎn)應(yīng)該通過代碼表現(xiàn)出來。如果兩條分支都是正常行為,就應(yīng)該使用形如if-else的條件式;如果某個(gè)條件極其罕見,就應(yīng)該單獨(dú)檢查該條件,并在該條件為真時(shí)立刻從函數(shù)中返回,這樣的單獨(dú)檢查常常被稱為衛(wèi)語句。
使用衛(wèi)語句,能夠有效的減少if語句嵌套層數(shù)。

【建議6-2-3】 使用for循環(huán)時(shí),優(yōu)先使用range 關(guān)鍵字而不是顯式下標(biāo)遞增控制。

正例:

for i, v := range array {
    fmt.Printf("element %v of array is %v\n", i, v)
}

反例:

for i := 0; i < len(array); i++ {
    fmt.Printf("element %v of array is %v\n", i, array[i])
}

【建議6-2-4】 對(duì)于 range 的返回值,如果只需要第二項(xiàng),則把第一項(xiàng)置為下劃線。
正例:

sum := 0
for _, value := range array {
    sum += value
}

函數(shù)設(shè)計(jì)

函數(shù)實(shí)現(xiàn)

【規(guī)則7-1-1】 函數(shù)命名要短小精悍和名副其實(shí),避免誤導(dǎo)。一般以它" 做什么" 來命名,而不是以它" 怎么做" 來命名。

說明: 函數(shù)命名名副其實(shí)就是指通過只讀函數(shù)的名稱就可以知道函數(shù)的功能,而不需要注釋來補(bǔ)充。
給函數(shù)命名的方法:通過對(duì)要完成的功能進(jìn)行分解和抽象,將功能分解成一個(gè)個(gè)單一的短小的功能實(shí)現(xiàn)體,對(duì)實(shí)現(xiàn)體的功能采用一個(gè)恰當(dāng)?shù)拿枋鲂悦Q命名,形成函數(shù)名稱。

【規(guī)則7-1-2】 函數(shù)要短小,還要更短小。盡量控制在20行代碼之內(nèi),包括空行和{}。

說明: 有幾個(gè)原因造成我喜歡短而命名良好的函數(shù)。首先,如果每個(gè)函數(shù)的粒度都很小,那么函數(shù)被復(fù)用的機(jī)會(huì)就更大;其次,如果函數(shù)都是細(xì)粒度,那么函數(shù)在修改時(shí)也會(huì)更容易些;再次,高層函數(shù)調(diào)用命名良好的短小函數(shù),使高層函數(shù)讀起來就像一系列解釋。
一個(gè)函數(shù)多長才算合適?長度不是問題,關(guān)鍵在于函數(shù)名稱和函數(shù)本體之間的語義距離。建議函數(shù)體的規(guī)模不能太大,20 行封頂最佳。

【規(guī)則7-1-3】 函數(shù)應(yīng)該做一件事,做好這件事,只做這一件事。

說明: 判斷一個(gè)函數(shù)是否只做了一件事,可以通過兩種方法:
(1)函數(shù)只是做了該函數(shù)名下同一抽象層上的步驟,則函數(shù)只做了一件事;
(2)如果一個(gè)函數(shù)內(nèi)部的實(shí)現(xiàn)還可以拆分出一個(gè)函數(shù),則該函數(shù)違反只做一件事原則。

【規(guī)則7-1-4】 函數(shù)的縮進(jìn)層次不應(yīng)該超過3層。

【規(guī)則7-1-5】 分隔指令與詢問,不要設(shè)置多功能函數(shù)。

說明: 函數(shù)要么做什么事,要么回答什么事,兩者不可兼得。如某個(gè)函數(shù)既返回對(duì)象狀態(tài)值,又修改對(duì)象狀態(tài)值,則需要建立兩個(gè)不同的函數(shù),其中一個(gè)負(fù)責(zé)查詢對(duì)象狀態(tài),另一個(gè)負(fù)責(zé)修改對(duì)象狀態(tài)。

【建議7-1-1】 為簡單功能編寫函數(shù)。

說明: 雖然為僅用一兩行就可完成的功能去編函數(shù)好象沒有必要,但使用函數(shù)可使功能明確化,增加程序可讀性,亦可方便維護(hù)、測試。

參數(shù)

【規(guī)則7-2-1】 禁止定義多于3個(gè)參數(shù)的函數(shù)。

說明: 函數(shù)參數(shù)設(shè)置最理想的參數(shù)個(gè)數(shù)是零,其次是一,再次是二,最后是三。參數(shù)不易對(duì)付,它們有太多的概念性。另外從測試的角度看,參數(shù)更叫人為難。

【規(guī)則7-2-2】 函數(shù)參數(shù)不能含有標(biāo)識(shí)參數(shù)。

說明: 標(biāo)識(shí)參數(shù)丑陋不堪,函數(shù)往往根據(jù)它的多個(gè)取值而做多件事情,這與函數(shù)只做一件事原則違背。如果參數(shù)只是用于賦值,那么就不是標(biāo)識(shí)參數(shù),所以是否標(biāo)識(shí)參數(shù)不是今通過形參來界定,而是看函數(shù)的實(shí)現(xiàn)是否因?yàn)楹瘮?shù)的入?yún)⒍隽硕嗉虑椤?/p>

【規(guī)則7-2-3】當(dāng)struct變量作為參數(shù)時(shí),應(yīng)傳送struct的指針而不傳送struct,并且不得修改struct中的元素,用作輸出時(shí)除外。

說明: 一個(gè)函數(shù)被調(diào)用的時(shí)候,形參會(huì)被一個(gè)個(gè)壓入被調(diào)函數(shù)的堆棧中,在函數(shù)調(diào)用結(jié)束以后再彈出。一個(gè)結(jié)構(gòu)所包含的變量往往比較多,直接以一個(gè)結(jié)構(gòu)為參數(shù),壓棧出棧的內(nèi)容就會(huì)太多,不但占用堆棧空間,而且影響代碼執(zhí)行效率。
如果使用結(jié)構(gòu)的指針作為參數(shù),因?yàn)橹羔樀拈L度是固定不變的,結(jié)構(gòu)的大小就不會(huì)影響代碼執(zhí)行的效率,也不會(huì)過多地占用堆棧空間。
如果傳遞的參數(shù)類型是 map、slice 和 channel 等引用類型,則不用傳遞指針,修改引用類型變量的初始地址除外(比如 json.Unmarshal)。

【規(guī)則7-2-4】在API函數(shù)中對(duì)輸入?yún)?shù)的正確性和有效性進(jìn)行檢查,在內(nèi)部能保證的條件下其他函數(shù)不用再進(jìn)行重復(fù)檢查。

說明: 很多程序錯(cuò)誤是由非法參數(shù)引起的,我們應(yīng)該充分理解并正確處理來防止此類錯(cuò)誤,特別是指針參數(shù)地址非法判斷和數(shù)組下標(biāo)參數(shù)的邊界判斷,但是我們沒有必要在多個(gè)函數(shù)中重復(fù)檢查。

【規(guī)則7-2-5】防止將函數(shù)的參數(shù)作為工作變量。

說明: 將函數(shù)的參數(shù)作為工作變量,有可能錯(cuò)誤地改變?nèi)雲(yún)⒌膬?nèi)容,所以很危險(xiǎn)。對(duì)于必須要改變的出參,最好也先使用局部變量,最后再將該局部變量賦值給該出參。

【規(guī)則7-2-6】如果參數(shù)列表中若干個(gè)相鄰的參數(shù)類型相同,則可以在參數(shù)列表中省略前面變量的類型聲明。

正例:

func Add(a, b int)(int, error) {
    // ...
}

【規(guī)則7-2-7】當(dāng) channel 作為函數(shù)參數(shù)時(shí),根據(jù)最小權(quán)限原則,使用單向 channel。

說明: 從設(shè)計(jì)的角度考慮,所有的代碼應(yīng)該都遵循“最小權(quán)限原則”。

正例:在函數(shù)Parse中ch不會(huì)被改寫

func Parse(ch <-chan int) {
    for value := range ch {
        fmt.Println("Parsing value", value)
    }
}

返回值

【規(guī)則7-3-1】 返回值的個(gè)數(shù)不要大于3。

錯(cuò)誤和異常設(shè)計(jì)

錯(cuò)誤設(shè)計(jì)

【規(guī)則8-1-1】 錯(cuò)誤值統(tǒng)一分組定義,而不是跟著感覺走。

說明: 很多人寫代碼時(shí),到處return errors.New(value),而錯(cuò)誤value在表達(dá)同一個(gè)含義時(shí)也可能形式不同,比如“記錄不存在”的錯(cuò)誤value可能為:

  1. "record is not existed."
  2. "record is not exist!"
  3. "###record is not existed!!!"

這使得相同的錯(cuò)誤value撒在一大片代碼里,當(dāng)上層函數(shù)要對(duì)特定錯(cuò)誤value進(jìn)行統(tǒng)一處理時(shí),需要漫游所有下層代碼,以保證錯(cuò)誤value統(tǒng)一,不幸的是有時(shí)會(huì)有漏網(wǎng)之魚,而且這種方式嚴(yán)重阻礙了錯(cuò)誤value的重構(gòu)。
于是,我們可以參考C/C++的錯(cuò)誤碼定義文件,在Go語言的每個(gè)包中增加一個(gè)錯(cuò)誤對(duì)象定義文件,對(duì)于共性的錯(cuò)誤對(duì)象定義,則放在公共的目錄中。

正例:

// file error object
var (
    ErrEof = errors.New("EOF")
    ErrClosedPipe = errors.New("io: read/write on closed pipe")
    ErrShortBuffer = errors.New("short buffer")
    ErrShortWrite = errors.New("short write")
)

【規(guī)則8-1-2】 失敗的原因只有一個(gè)時(shí),不使用error。

正例:

func (self *AgentContext) IsValidHostType(hostType string) bool {
    return hostType == "virtual_machine" || hostType == "bare_metal"
}

反例:

func (self *AgentContext) CheckHostType(hostType string) error {
    switch hostType {
    case "virtual_machine":
        return nil
    case "bare_metal":
        return nil
    }
    return ErrInvalidHostType
}

【規(guī)則8-1-3】 沒有失敗原因時(shí),不使用error。

說明: error在Go語言中是如此的流行,以至于很多人設(shè)計(jì)函數(shù)時(shí)不管三七二十一都使用error,即使沒有一個(gè)失敗原因,而該函數(shù)的調(diào)用者無疑是無奈的。

正例:
函數(shù)設(shè)計(jì):

func SetProjectId(projectId string) 

反例:
函數(shù)設(shè)計(jì):

func SetProjectId(projectId string)  error

【規(guī)則8-1-4】 error/bool應(yīng)放在返回值類型列表的最后。

【規(guī)則8-1-5】 錯(cuò)誤處理巧用defer。

正例:

func deferDemo() error {
    err := createResource1()
    if err != nil {
        return ErrCreateResource1Failed
    }
    defer func() {
        if err != nil {
            destroyResource1()
        }
    }()
    err = createResource2()
    if err != nil {
        return ErrCreateResource2Failed
    }
    defer func() {
        if err != nil {
            destroyResource2()
        }
    }()

    err = createResource3()
    if err != nil {
        return ErrCreateResource3Failed
    }
    defer func() {
        if err != nil {
            destroyResource3()
        }
    }()

    err = createResource4()
    if err != nil {
        return ErrCreateResourc4Failed
    }
    return nil
}

反例:

func deferDemo() error {
    err := createResource1()
    if err != nil {
        return ErrCreateResource1Failed
    }
    err = createResource2()
    if err != nil {
        destroyResource1()
        return ErrCreateResource2Failed
    }

    err = createResource3()
    if err != nil {
        destroyResource1()
        destroyResource2()
        return ErrCreateResource3Failed
    }

    err = createResource4()
    if err != nil {
        destroyResource1()
        destroyResource2()
        destroyResource3()
        return ErrCreateResource4Failed
    }
    return nil
}

【規(guī)則8-1-6】 當(dāng)嘗試幾次可以避免失敗時(shí),不要立即返回錯(cuò)誤。

說明: 如果錯(cuò)誤的發(fā)生是偶然性的,或由不可預(yù)知的問題導(dǎo)致。一個(gè)明智的選擇是重新嘗試失敗的操作,有時(shí)第二次或第三次嘗試時(shí)會(huì)成功。在重試時(shí),我們需要限制重試的時(shí)間間隔或重試的次數(shù),防止無限制的重試。比如我們平時(shí)上網(wǎng)時(shí),嘗試請(qǐng)求某個(gè)URL,有時(shí)第一次沒有響應(yīng),當(dāng)我們?cè)俅嗡⑿聲r(shí),就有了驚喜。

【規(guī)則8-1-7】 當(dāng)上層函數(shù)不關(guān)心錯(cuò)誤時(shí),不返回error。

說明: 對(duì)于一些資源清理相關(guān)的函數(shù)(destroy/delete/clear),如果子函數(shù)出錯(cuò),打印日志即可,而無需將錯(cuò)誤進(jìn)一步反饋到上層函數(shù),因?yàn)橐话闱闆r下,上層函數(shù)是不關(guān)心執(zhí)行結(jié)果的,或者即使關(guān)心也無能為力,于是我們建議將相關(guān)函數(shù)設(shè)計(jì)為不返回error。

異常設(shè)計(jì)

【規(guī)則8-2-1】 在程序開發(fā)階段,堅(jiān)持速錯(cuò),讓程序異常崩潰。

說明: 所謂速錯(cuò)簡單來講就是“讓它掛”,只有掛了你才會(huì)第一時(shí)間知道錯(cuò)誤。在早期開發(fā)以及任何發(fā)布階段之前,最簡單的同時(shí)也可能是最好的方法是調(diào)用panic函數(shù)來中斷程序的執(zhí)行以強(qiáng)制發(fā)生錯(cuò)誤,使得該錯(cuò)誤不會(huì)被忽略,因而能夠被盡快修復(fù)。

【規(guī)則8-2-2】 在程序部署后,應(yīng)恢復(fù)異常避免程序終止。

說明: 在Golang中,雖然有類似Erlang進(jìn)程的Goroutine,但需要強(qiáng)調(diào)的是Erlang的掛,只是Erlang進(jìn)程的異常退出,不會(huì)導(dǎo)致整個(gè)Erlang節(jié)點(diǎn)退出,所以它掛的影響層面比較低,而Goroutine如果panic了,并且沒有recover,那么整個(gè)Golang進(jìn)程(類似Erlang節(jié)點(diǎn))就會(huì)異常退出。所以,一旦Golang程序部署后,在任何情況下發(fā)生的異常都不應(yīng)該導(dǎo)致程序異常退出,我們?cè)谏蠈雍瘮?shù)FuncA中加一個(gè)延遲執(zhí)行的recover調(diào)用來達(dá)到這個(gè)目的,并且是否進(jìn)行recover需要根據(jù)環(huán)境變量或配置文件來定(便于在開發(fā)階段進(jìn)行速錯(cuò)),默認(rèn)需要recover。

正例:

func FuncA() (err error) {
    defer func() {
        if permittedRecover {
            if p := recover(); p != nil {
                fmt.Println("panic recover! p:", p)
                str, ok := p.(string)
                if ok {
                    err = errors.New(str)
                } else {
                    err = errors.New("panic")
                }
                debug.PrintStack()
            }
        }
    }()
    ...
}

【規(guī)則8-2-3】 對(duì)于不應(yīng)該出現(xiàn)的分支,使用異常處理。

說明: 當(dāng)某些不應(yīng)該發(fā)生的場景發(fā)生時(shí),我們就應(yīng)該調(diào)用panic函數(shù)來觸發(fā)異常。

正例:

switch s := suit(drawCard()); s {
    case "Spades":
    // ...
    case "Hearts":
    // ...
    case "Diamonds":
    // ...
    case "Clubs":
    // ...
    default:
        panic(fmt.Sprintf("invalid suit %v", s))
}

【規(guī)則8-2-4】 針對(duì)入?yún)⒉粦?yīng)該有問題的函數(shù),使用異常設(shè)計(jì)。

說明: 入?yún)⒉粦?yīng)該有問題一般指的是入?yún)橛簿幋a,而不是API的外部輸入。當(dāng)調(diào)用者明確知道輸入不會(huì)引起函數(shù)錯(cuò)誤時(shí),要求調(diào)用者檢查這個(gè)錯(cuò)誤是不必要和累贅的。我們應(yīng)該假設(shè)函數(shù)的輸入一直合法,當(dāng)調(diào)用者輸入了不應(yīng)該出現(xiàn)的輸入時(shí),就觸發(fā)panic異常。

正例: 庫函數(shù)MustCompile的實(shí)現(xiàn)

func MustCompile(str string) *Regexp {
    regexp, error := Compile(str)
    if error != nil {
        panic(`regexp: Compile(` + quote(str) + `): ` + error.Error())
    }
    return regexp
}

整潔測試

【規(guī)則9-1】 不要為了測試而對(duì)產(chǎn)品代碼進(jìn)行侵入性修改。

說明: 禁止僅為了測試而在產(chǎn)品代碼中增加條件分支或函數(shù)變量。

【建議9-1】 測試用例中不應(yīng)該存在復(fù)雜的循環(huán)和條件控制語句。

說明: 測試用例對(duì)可讀性的要求非常高,如果出現(xiàn)大量的循環(huán)、條件控制語句,將大大地?fù)p害了用例的可讀性。一般地,測試用例應(yīng)該是由若干條陳述句所組成,越簡單越好。

【建議9-2】 測試代碼和產(chǎn)品代碼一樣重要。

說明: 測試代碼不是二等公民,它需要被思考、被設(shè)計(jì)和被照料,它該像產(chǎn)品代碼一般保持整潔。

【建議9-3】 整潔的測試有三個(gè)要素:可讀性,可讀性和可讀性。

說明: 對(duì)于測試代碼,可讀性比產(chǎn)品代碼還重要。產(chǎn)品代碼的正確性由測試代碼來保證,而測試代碼的正確性只能由自己來保證。如果測試代碼一直保持簡單清晰,那么錯(cuò)誤便無處藏身。

【建議9-4】 測試應(yīng)該是黑盒的。

說明: 避免根據(jù)代碼編寫測試。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,635評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,733評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,935評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,172評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評(píng)論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,595評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,908評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容