最近在項(xiàng)目中也 codereview 了不少 Go 語言的代碼,有必要總結(jié)下代碼規(guī)范,算是一個(gè)筆記記錄了。
說在前面,這只是我們團(tuán)隊(duì)的一套規(guī)范而已。
今天我們聊一下 Go 的編碼規(guī)范,大概分為幾大模塊,如注包/變量/常量命名、基本語法、函數(shù)、錯(cuò)誤處理、心得等?!就扑]:golang教程】
1. 代碼風(fēng)格
1.1 代碼格式
- 代碼必須用 gofmt 進(jìn)行格式化,goland 可以配置,可以自行搜索一下配置
- 我們編寫的代碼每行應(yīng)該不超過 120 個(gè)字符,超出部分用換行解決。
- 單個(gè)文件最大行數(shù)最大不超過 800 行.
- 單個(gè)函數(shù)最大行數(shù)不超過 80 行。
- import 規(guī)范
- 不要使用相對(duì)路徑引入包,例如
import ../util/net
- 在導(dǎo)入包時(shí),多個(gè)相同包名沖突時(shí),必須使用導(dǎo)入別名
- 不要使用相對(duì)路徑引入包,例如
// bad "github.com/google/uuid" // good uuid "github.com/google/uuid"
- 導(dǎo)入的包建議分組,引用匿名包建議用一個(gè)新的分組,并加上注釋方便后面小伙伴閱讀
import ( // Go 標(biāo)準(zhǔn)庫 "fmt" //第三方包 "github.com/jinzhu/gorm" "github.com/google/uuid" "github.com/go-redis/redis/v8" // 匿名包 /import mysql driver _"github.com/jinzhu/gorm/dialects/mysql" // 內(nèi)部包 slice "xxx.local/pkg/v1/goslice" meta "xxx.local/pkg/v1/meta" gomap "xxx.local/pkg/v2/gomap")
1.2 聲明、初始化和定義
- 一個(gè)函數(shù)需要使用多個(gè)變量時(shí),可以在函數(shù)最開頭處使用 var 聲明。在函數(shù)外部聲明的變量不能使用 :=,會(huì)踩坑,不知道的可以評(píng)論區(qū)留言(要評(píng)論不易呀)!
var ( port = 8081 metricServerPort = 2001)
- 在初始化結(jié)構(gòu)體用 &struct 代替 new(struct),確保與結(jié)構(gòu)體初始化一致,初始化結(jié)構(gòu)體時(shí)換行。
// bad stu := new(S) stu.Name = "張三" // good stu := &S{ Name:"李四" }
- 使用 make 在聲明 map、array 等應(yīng)該指定容器的容量,從而達(dá)到預(yù)先分配內(nèi)容。
users := make(map[int]string, 10)tags := make([]int, 0, 10)
- 使用標(biāo)準(zhǔn) var 關(guān)鍵字事,不要指定類型,除非它與表達(dá)式的類型不同。
// bad var _f string F() func F() string { return "hello world!" } // good var _f F() func F() string { return "hello world!" }
1.3 error 處理
- 若函數(shù)返回 error, 必須對(duì) error 進(jìn)行處理,如果業(yè)務(wù)允許可以用 _ 接受忽略。對(duì)應(yīng) defer 可以不用顯式進(jìn)行處理。
// bad func InitConfig() error { ... } InitConfig() // good func InitConfig() error { ... } err := InitConfig() if err != nil { ... } // or _ := InitConfig()
- error 作為返回值時(shí)必須作為最后一個(gè)參數(shù)返回
// bad func InitConfig() (error,int) { ... } // good func InitConfig() (int, error) { ... }
- 錯(cuò)誤需要單獨(dú)處理,盡量不要與其他的邏輯耦合在一起。
// bad res, err := InitConfig() if err != nil || res != nil { return err } // good res, err := InitConfig() if err != nil { return err } if res != nil { return fmt.Errorf("invalid result") }
1.4 panic處理
- 業(yè)務(wù)代碼中禁止拋出 panic 錯(cuò)誤。
- panic 只允許出現(xiàn)在在服務(wù)啟動(dòng)之前,如讀取配置、鏈接存儲(chǔ)(redis、mysql 等)。
- 業(yè)務(wù)代碼中建議用 error 而不是 panic 來傳遞。
1.5 單元測(cè)試
- 每個(gè)重要的函數(shù)都要編寫測(cè)試用例,合并代碼要自動(dòng)化運(yùn)行一下所有的 test。
- 文件命名 xxx_test.go。
- 函數(shù)命名建議使用 Test函數(shù)名。
2. 命名規(guī)范
在每個(gè)語言中,命名規(guī)范在代碼規(guī)范中非常重要,一個(gè)統(tǒng)一的、精確的命名不僅僅可以提高代碼的可讀性,也可以讓人覺的這個(gè)同志真的會(huì)呀。牛!
2.1 包命名規(guī)范
- 包名必須與目錄名一致(這和其他 php、Java 還是有一點(diǎn)不太一樣的),盡量采取有意義、簡(jiǎn)短的包名,不要與 go 的標(biāo)準(zhǔn)庫名稱一樣。
- 包名小寫,沒有下劃線,可以使用中劃線隔開,使用多級(jí)目錄來劃分目錄。
- 包名不要出現(xiàn)復(fù)數(shù)命名。
- 包名命名盡量簡(jiǎn)單一目了然,ge:user、log。
2.2 文件命名規(guī)范
- 文件名要見名思義,盡量簡(jiǎn)而短
- 文件名小寫,組合詞用下劃線分割
2.3 函數(shù)命名規(guī)范
- 與 php、Java 一樣,必須遵循駝峰規(guī)范,Go 語言中需要根據(jù)訪問的控制決定大駝峰還是小駝峰。
- 單元測(cè)試的函數(shù)用大駝峰,TestFunc。
2.4 結(jié)構(gòu)體命名規(guī)范
- 與 php、Java 一樣,必須遵循駝峰規(guī)范,Go 語言中需要根據(jù)訪問的控制決定大駝峰還是小駝峰。
- 避免使用 info 、data 這種無意義的名稱。
- 命名使用名詞而非動(dòng)詞。
- 結(jié)構(gòu)體在聲明和初始化的時(shí)候需要換行,eg:
type Student struct{ Name string Age uint8}student := Student{ Name: "張三", Age: 18,}
2.5 變量命名規(guī)范
- 和 php、Java 一樣,必須遵循駝峰規(guī)范,Go 語言中需要根據(jù)訪問的控制決定大駝峰還是小駝峰。
- 若變量為私有時(shí),可以使用小寫命名。
- 局部變量可以簡(jiǎn)寫,eg:i 表示 index。
- 若變量代表 bool 值,則可以使用 Is 、Can、Has 前綴命名,eg:
var isExit boolvar canReturn bool
2.6 常量命名規(guī)范
- 必須遵循駝峰規(guī)范,Go 語言中需要根據(jù)訪問的控制決定大駝峰還是小駝峰。
- 若代表枚舉值,需要先創(chuàng)建。
type Code intconst ( ErrNotFound Code = iota ErrFatal)
3. 類型
3.1 字符串
好像學(xué)過的語言中,都是從字符串開始說起的。就像寫代碼第一行都是從 Hello World!一樣!同意的點(diǎn)贊哈。
- 字符串判空值
// bad if s == "" { ...} // good if len(s) == 0 { ...}
- 字符串去除前后子串。
// bad var s1 "hello world"var s2 "hello"var s3 strings.TrimPrefix(s1, s2) // good var s1 "hello world"var s2 "hello"var s3 stringif strings.HasPrefix(s1, s2){ s3 = s1[len(s2):]}
3.2 切片 slice
- 聲明 slice。
// bad s := []string{}s := make([]string, 10) // good var s []string s := make([]string, 0, 10)
- 非空判斷。
//bad if len(slice) >0 { ...} // good if slice != nil && len(slice) > 0 { ...}
- slice copy。
// badvar b1,b2 []bytefor i, v := range b1 { b2[i] = v}for i := range b1 { b2[i] = b1[i]}// goodcopy(b2,b1)
- slice 新增。
// bad var a,b []intfor _, v := range a { b = append(b,v)} // good var a, b []int b := append(b, a...)
3.4 結(jié)構(gòu)體 struct
- 初始化需要多行。
type Student struct{ Name string Age uint8}student := Student{ Name: "張三", Age: 18,}
4. 控制語句
4.1 if
- if 可以用局部變量的方式初始化。
if err := InitConfig; err != nil { return err}
4.2 for
- 不允許在 for 中使用 defer, defer 只在函數(shù)結(jié)束時(shí)才會(huì)執(zhí)行。
// bad for file := range files { fd, err := os.Open(file) if err != nil { return err } defer fd.close()} // good for file := range files{ func() { fd,err := os.open(file) if err!=nil { return err } defer fd.close() }()}
4.3 range
- 如果不需要 key 直接用 _ 忽略,value 也一樣。
for _, v := range students { ...}for i, _ := range students { ...}for i, v := range students { ...}
注: 若操作指針時(shí)請(qǐng)注意不能直接用 s := v。想知道可以評(píng)論區(qū)告訴我哦!
4.4 switch
- 和其他語言不一樣,必須要有 defalt
switch type { case 1: fmt.Println("type = 1") break case 2: fmt.Println("type = 2") break default : fmt.Println("unKnown type")}
4.5 goto
- 業(yè)務(wù)中不允許使用 goto。
- 框架和公共工具也不允許使用 goto。
5. 函數(shù)
- 傳參和返回的變量小寫字母。
- 傳入?yún)?shù)時(shí)slice、map、interface、chan 禁止傳遞指針類型。
- 采用值傳遞,不用指針傳值。
- 入?yún)€(gè)數(shù)不能超出 5 個(gè),超過的可以用 struct 傳值。
5.1 函數(shù)參數(shù)
- 返回值超出 1 個(gè)時(shí),需要用變量名返回。
- 多個(gè)返回值可以用 struct 傳。
5.2 defer
- 當(dāng)操作資源、或者事物需要提交回滾時(shí),可以在創(chuàng)建開始下方就使用 defer 釋放資源。
- 創(chuàng)建資源后判斷 error,非 error 情況后在用 defer 釋放。
5.3 代碼嵌套
- 為了代碼可讀性,為了世界和平,盡量別用太多的嵌套,因?yàn)檎娴暮茈y有人類能看懂。
6. 日常使用感悟
- 能不用全局變量就不用,可以用參數(shù)傳值的方式,這樣可以大大降低耦合,更有利于單元測(cè)試。
- 衣服開發(fā)中,在函數(shù)間多用 context 傳遞上下文,在請(qǐng)求開始時(shí)可以生成一個(gè) request_id,便于鏈路、日志追蹤。
6.1 提高性能
- 在業(yè)務(wù)開發(fā)中,盡量使用 strconv 來替代 fmt。
- 我們?cè)谑褂?string 字符串類型時(shí),當(dāng)修改的場(chǎng)景較多,盡量在使用時(shí)用 []byte 來替代。因?yàn)槊看螌?duì) string 的修改都需要重新在申請(qǐng)內(nèi)存。
6.2 避免踩坑
- append 要小心自動(dòng)擴(kuò)容的情況,最好在申明時(shí)分配好容量,避免擴(kuò)容所帶來的性能上的損耗以及分配新的內(nèi)存地址。若不能確定容量,應(yīng)選擇一個(gè)比較大一點(diǎn)的值。
- 并發(fā)場(chǎng)景下,map 非線程安全,需要加鎖。還有一種評(píng)論區(qū)告訴我吧。
- interface 在編譯期間無法被檢查,使用上會(huì)出現(xiàn) panic,需要注意
7. 總結(jié)
本篇很講了 Go 語言的編碼規(guī)范,當(dāng)時(shí)想說的,規(guī)范是大家預(yù)定的東西,每個(gè)公司、團(tuán)隊(duì)都會(huì)有不一樣的規(guī)范,只要大家一起遵循就好啦。你可以根據(jù)自己團(tuán)隊(duì)的需求,定一套屬于自己團(tuán)隊(duì)的項(xiàng)目規(guī)范。如果想小伙伴一起遵循,可以借助一些工具來保障執(zhí)行度。
講了很多,雖然很基礎(chǔ),希望對(duì)于剛剛轉(zhuǎn) Go 語言,或者剛學(xué)習(xí) Go 語言的同學(xué)有幫助吧。今天就到這里了。希望得到大家的一鍵三連。感謝!
本文系轉(zhuǎn)載,原文鏈接:mp.weixin.qq.com/s/lfjP9DEia2WL4Ua…
php入門到就業(yè)線上直播課:立即學(xué)習(xí)
全程直播 + 實(shí)戰(zhàn)授課 + 邊學(xué) + 邊練 + 邊輔導(dǎo)