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ì)玛歌、整潔測(cè)試等。

對(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】測(cè)試文件放在實(shí)現(xiàn)文件的同級(jí)目錄下,便于Go語言工具的使用述呐。

說明: 雖然測(cè)試文件和實(shí)現(xiàn)文件的代碼在同一個(gè)包內(nèi)惩淳,但是測(cè)試用例的設(shè)計(jì)盡量站在包用戶的角度去考慮,一般只測(cè)試包外可見的函數(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列。
說明: 長(zhǎng)行要在低優(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)行注釋误澳,提供有用耻矮、額外的信息,而不是代碼表面意義的簡(jiǎ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è)界常用的縮略語保持一致。

說明: 較短的單詞可通過去掉“元音”形成縮寫塞栅,較長(zhǎng)的單詞可取單詞的頭幾個(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ì)于代碼的走查、測(cè)試奄喂、變更維護(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ù)多長(zhǎng)才算合適蝇更?長(zhǎng)度不是問題沪编,關(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】 為簡(jiǎn)單功能編寫函數(shù)净嘀。

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

參數(shù)

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

說明: 函數(shù)參數(shù)設(shè)置最理想的參數(shù)個(gè)數(shù)是零岩臣,其次是一,再次是二宵膨,最后是三架谎。參數(shù)不易對(duì)付,它們有太多的概念性辟躏。另外從測(cè)試的角度看谷扣,參數(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(zhǎng)度是固定不變的,結(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 existedT筇āJ踩佟!"

這使得相同的錯(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ò)簡(jiǎn)單來講就是“讓它掛”,只有掛了你才會(huì)第一時(shí)間知道錯(cuò)誤降传。在早期開發(fā)以及任何發(fā)布階段之前篷朵,最簡(jiǎn)單的同時(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ā)生的場(chǎng)景發(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
}

整潔測(cè)試

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

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

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

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

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

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

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市猩系,隨后出現(xiàn)的幾起案子媚送,更是在濱河造成了極大的恐慌,老刑警劉巖寇甸,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件季希,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡幽纷,警方通過查閱死者的電腦和手機(jī)式塌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來友浸,“玉大人峰尝,你說我怎么就攤上這事∈栈郑” “怎么了武学?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)伦意。 經(jīng)常有香客問我火窒,道長(zhǎng),這世上最難降的妖魔是什么驮肉? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任熏矿,我火速辦了婚禮,結(jié)果婚禮上离钝,老公的妹妹穿的比我還像新娘票编。我一直安慰自己,他們只是感情好卵渴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布慧域。 她就那樣靜靜地躺著,像睡著了一般浪读。 火紅的嫁衣襯著肌膚如雪昔榴。 梳的紋絲不亂的頭發(fā)上辛藻,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音互订,去河邊找鬼吱肌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛屁奏,可吹牛的內(nèi)容都是我干的岩榆。 我是一名探鬼主播错负,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼坟瓢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了犹撒?” 一聲冷哼從身側(cè)響起折联,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎识颊,沒想到半個(gè)月后诚镰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祥款,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年清笨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刃跛。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抠艾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桨昙,到底是詐尸還是另有隱情检号,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布蛙酪,位于F島的核電站齐苛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏桂塞。R本人自食惡果不足惜凹蜂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阁危。 院中可真熱鬧炊甲,春花似錦、人聲如沸欲芹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菱父。三九已至颈娜,卻和暖如春剑逃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背官辽。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工蛹磺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人同仆。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓萤捆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親俗批。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俗或,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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