Go 語(yǔ)言編程 — 編碼規(guī)范指南

      網(wǎng)友投稿 910 2025-04-03

      目錄

      文章目錄

      目錄

      參考

      工程化要求

      編碼規(guī)范

      大小約定

      縮進(jìn)、括號(hào)和空格約定

      命名規(guī)范

      包、目錄命名規(guī)范

      文件命名規(guī)范

      標(biāo)識(shí)符命名規(guī)范

      變量、常量名

      函數(shù)、方法名

      結(jié)構(gòu)體、接口名

      空行、注釋、文檔規(guī)范

      空行

      注釋與文檔

      注釋風(fēng)格

      包注釋

      函數(shù)、方法注釋

      結(jié)構(gòu)體、接口注釋

      其它說(shuō)明

      導(dǎo)入規(guī)范

      代碼邏輯實(shí)現(xiàn)規(guī)范

      變量、常量定義規(guī)范

      String 類型定義規(guī)范

      Slice、Map 類型定義規(guī)范

      結(jié)構(gòu)體定義規(guī)范

      接口定義規(guī)范

      函數(shù)、方法定義規(guī)范

      Named Result Parameters

      Receiver Names

      Receiver Type

      錯(cuò)誤處理規(guī)范

      單元測(cè)試規(guī)范

      參考

      Effective Go

      The Go common mistakes guide

      工程化要求

      建議你在 IDE 中集成下述工具插件:

      提交代碼時(shí),必須使用 gofmt 工具格式化代碼。注意,gofmt 不識(shí)別空行,因?yàn)?gofmt 不能理解空行的意義。

      提交代碼前,必須使用 goimports 工具檢查導(dǎo)入。

      提交代碼時(shí),必須使用 golint 工具檢查代碼規(guī)范。

      提交代碼前,必須使用 go vet 工具靜態(tài)分析代碼實(shí)現(xiàn)。

      編碼規(guī)范

      大小約定

      單個(gè)文件長(zhǎng)度盡量不超過(guò) 500 行。

      單個(gè)函數(shù)長(zhǎng)度盡量不超過(guò) 50 行。

      單個(gè)函數(shù)圈復(fù)雜度盡量不超過(guò) 10,禁止超過(guò) 15。

      單個(gè)函數(shù)中嵌套不超過(guò) 3 層。

      單行注釋盡量不超過(guò) 80 個(gè)字符。

      單行語(yǔ)句盡量不超過(guò) 80 個(gè)字符。

      當(dāng)單行代碼超過(guò) 80 個(gè)字符時(shí),就要考慮分行。分行的規(guī)則是以參數(shù)為單位將從較長(zhǎng)的參數(shù)開(kāi)始換行,以此類推直到每行長(zhǎng)度合適:

      So(z.ExtractTo( path.Join(os.TempDir(), "testdata/test2"), "dir/", "dir/bar", "readonly"), ShouldBeNil)

      1

      2

      3

      當(dāng)單行聲明語(yǔ)句超過(guò) 80 個(gè)字符時(shí),就要考慮分行。分行的規(guī)則是將參數(shù)按類型分組,緊接著的聲明語(yǔ)句的是一個(gè)空行,以便和函數(shù)體區(qū)別:

      // NewNode initializes and returns a new Node representation. func NewNode( importPath, downloadUrl string, tp RevisionType, val string, isGetDeps bool) *Node { n := &Node{ Pkg: Pkg{ ImportPath: importPath, RootPath: GetRootPath(importPath), Type: tp, Value: val, }, DownloadURL: downloadUrl, IsGetDeps: isGetDeps, } n.InstallPath = path.Join(setting.InstallRepoPath, n.RootPath) + n.ValSuffix() return n }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      縮進(jìn)、括號(hào)和空格約定

      縮進(jìn)、括號(hào)和空格都使用 gofmt 工具處理。

      強(qiáng)制使用 tab 縮進(jìn)。

      強(qiáng)制左大括號(hào)不換行。

      強(qiáng)制所有的運(yùn)算符和操作數(shù)之間要留空格。

      命名規(guī)范

      所有命名遵循 “意圖” 原則。

      包、目錄命名規(guī)范

      包名和目錄名保持一致。一個(gè)目錄盡量維護(hù)一個(gè)包下的所有文件。

      包名為全小寫(xiě)單詞, 不使用復(fù)數(shù),不使用下劃線。

      包名應(yīng)該盡可能簡(jiǎn)短。

      文件命名規(guī)范

      文件名為全小寫(xiě)單詞,使用 “_” 分詞。Golang 通常具有以下幾種代碼文件類型:

      業(yè)務(wù)代碼文件

      模型代碼文件

      測(cè)試代碼文件

      工具代碼文件

      標(biāo)識(shí)符命名規(guī)范

      短名優(yōu)先,作用域越大命名越?且越有意義。

      變量命名遵循駝峰法。

      常量使用全大寫(xiě)單詞,使用 “_” 分詞。

      首字母根據(jù)訪問(wèn)控制原則使用大寫(xiě)或者小寫(xiě)。

      對(duì)于常規(guī)縮略語(yǔ),一旦選擇了大寫(xiě)或小寫(xiě)的風(fēng)格,就應(yīng)當(dāng)在整份代碼中保持這種風(fēng)格,不要首字母大寫(xiě)和縮寫(xiě)兩種風(fēng)格混用。以 URL 為例,如果選擇了縮寫(xiě) URL 這種風(fēng)格,則應(yīng)在整份代碼中保持。錯(cuò)誤:UrlArray,正確:urlArray 或 URLArray。再以 ID 為例,如果選擇了縮寫(xiě) ID 這種風(fēng)格,錯(cuò)誤:appleId,正確:appleID。

      對(duì)于只在本文件中有效的頂級(jí)變量、常量,應(yīng)該使用 “_” 前綴,避免在同一個(gè)包中的其他文件中意外使用錯(cuò)誤的值。例如:

      var ( _defaultPort = 8080 _defaultUser = "user" )

      1

      2

      3

      4

      若變量、常量為 bool 類型,則名稱應(yīng)以 Has、Is、Can 或 Allow 開(kāi)頭:

      var isExist bool var hasConflict bool var canManage bool var allowGitHook bool

      1

      2

      3

      4

      如果模塊的功能較為復(fù)雜、常量名稱容易混淆的情況下,為了更好地區(qū)分枚舉類型,可以使用完整的前綴:

      type PullRequestStatus int const ( PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota PULL_REQUEST_STATUS_CHECKING PULL_REQUEST_STATUS_MERGEABLE )

      1

      2

      3

      4

      5

      6

      函數(shù)、方法(結(jié)構(gòu)體或者接口下屬的函數(shù)稱為方法)命名規(guī)則: 動(dòng)詞 + 名詞。

      若函數(shù)、方法為判斷類型(返回值主要為 bool 類型),則名稱應(yīng)以 Has、Is、Can 或 Allow 等判斷性動(dòng)詞開(kāi)頭:

      func HasPrefix(name string, prefixes []string) bool { ... } func IsEntry(name string, entries []string) bool { ... } func CanManage(name string) bool { ... } func AllowGitHook() bool { ... }

      1

      2

      3

      4

      結(jié)構(gòu)體命名規(guī)則:名詞或名詞短語(yǔ)。

      接口命名規(guī)則:以 ”er” 作為后綴,例如:Reader、Writer。接口實(shí)現(xiàn)的方法則去掉 “er”,例如:Read、Write。

      Go 語(yǔ)言編程 — 編碼規(guī)范指南

      type Reader interface { Read(p []byte) (n int, err error) } // 多個(gè)函數(shù)接口 type WriteFlusher interface { Write([]byte) (int, error) Flush() error }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      空行、注釋、文檔規(guī)范

      空行

      空行需要體現(xiàn)代碼邏輯的關(guān)聯(lián),所以空行不能隨意,非常嚴(yán)重地影響可讀性。

      保持函數(shù)內(nèi)部實(shí)現(xiàn)的組織粒度是相近的,用空行分隔。

      注釋與文檔

      Golang 的 go doc 工具可以根據(jù)注釋生成代碼文檔,所以注釋的質(zhì)量決定了代碼文檔的質(zhì)量。

      統(tǒng)一使用中文注釋,中西文之間嚴(yán)格使用空格分隔,嚴(yán)格使用中文標(biāo)點(diǎn)符號(hào)

      注釋?xiě)?yīng)當(dāng)是一個(gè)完整的句子,以句號(hào)結(jié)尾。

      句子類型的注釋首字母均需大寫(xiě),短語(yǔ)類型的注釋首字母需小寫(xiě)。

      注釋的單行長(zhǎng)度不能超過(guò) 80 個(gè)字符。

      每個(gè)包都應(yīng)該有一個(gè)包注釋。包注釋會(huì)首先出現(xiàn)在 go doc 網(wǎng)頁(yè)上。包注釋?xiě)?yīng)該包含:

      包名,簡(jiǎn)介。

      創(chuàng)建者。

      創(chuàng)建時(shí)間。

      對(duì)于 main 包,通常只有一行簡(jiǎn)短的注釋用以說(shuō)明包的用途,且以項(xiàng)目名稱開(kāi)頭:

      // Gogs (Go Git Service) is a painless self-hosted Git Service. package main

      1

      2

      對(duì)于簡(jiǎn)單的非 main 包,也可用一行注釋概括。

      對(duì)于一個(gè)復(fù)雜項(xiàng)目的子包,一般情況下不需要包級(jí)別注釋,除非是代表某個(gè)特定功能的模塊。

      對(duì)于相對(duì)功能復(fù)雜的非 main 包,一般都會(huì)增加一些使用示例或基本說(shuō)明,且以 Package 開(kāi)頭:

      /* Package regexp implements a simple library for regular expressions. The syntax of the regular expressions accepted is: regexp: concatenation { '|' concatenation } concatenation: { closure } closure: term [ '*' | '+' | '?' ] term: '^' '$' '.' character '[' [ '^' ] character-ranges ']' '(' regexp ')' */ package regexp

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      對(duì)于特別復(fù)雜的包說(shuō)明,一般使用 doc.go 文件用于編寫(xiě)包的描述,并提供與整個(gè)包相關(guān)的信息。

      每個(gè)函數(shù)、方法(結(jié)構(gòu)體或者接口下屬的函數(shù)稱為方法)都應(yīng)該有注釋說(shuō)明,包括三個(gè)方面(順序嚴(yán)格):

      函數(shù)、方法名,簡(jiǎn)要說(shuō)明。

      參數(shù)列表,每行一個(gè)參數(shù)。

      返回值,每行一個(gè)返回值。

      // NewtAttrModel,屬性數(shù)據(jù)層操作類的工廠方法。 // 參數(shù): // ctx:上下文信息。 // 返回值: // 屬性操作類指針。 func NewAttrModel(ctx *common.Context) *AttrModel {}

      1

      2

      3

      4

      5

      6

      如果一句話不足以說(shuō)明全部問(wèn)題,則可換行繼續(xù)進(jìn)行更加細(xì)致的描述:

      // Copy copies file from source to target path. // It returns false and error when error occurs in underlying function calls.

      1

      2

      若函數(shù)或方法為判斷類型(返回值主要為 bool 類型),則注釋以 returns true if 開(kāi)頭:

      // HasPrefix returns true if name has any string in given slice as prefix. func HasPrefix(name string, prefixes []string) bool { ...

      1

      2

      每個(gè)自定義的結(jié)構(gòu)體、接口都應(yīng)該有注釋說(shuō)明,放在實(shí)體定義的前一行,格式為:名稱、說(shuō)明。同時(shí),結(jié)構(gòu)體內(nèi)的每個(gè)成員都要有說(shuō)明,該說(shuō)明放在成員變量的后面(注意對(duì)齊),例如:

      // User,用戶實(shí)例,定義了用戶的基礎(chǔ)信息。 type User struct{ Username string // 用戶名 Email string // 郵箱 }

      1

      2

      3

      4

      5

      當(dāng)某個(gè)部分等待完成時(shí),用 TODO(Your name): 開(kāi)頭的注釋來(lái)提醒維護(hù)人員。

      當(dāng)某個(gè)部分存在已知問(wèn)題進(jìn)行需要修復(fù)或改進(jìn)時(shí),用 FIXME(Your name): 開(kāi)頭的注釋來(lái)提醒維護(hù)人員。

      當(dāng)需要特別說(shuō)明某個(gè)問(wèn)題時(shí),可用 NOTE(You name): 開(kāi)頭的注釋。

      導(dǎo)入規(guī)范

      使用 goimports 工具,在保存文件時(shí)自動(dòng)檢查 import 規(guī)范。

      如果使用的包沒(méi)有導(dǎo)入,則自動(dòng)導(dǎo)入;如果導(dǎo)入的包沒(méi)有被使用,則自動(dòng)刪除。

      強(qiáng)制使用分行導(dǎo)入,即便僅導(dǎo)入一個(gè)包。

      導(dǎo)入多個(gè)包時(shí)注意按照類別順序并使用空行區(qū)分:標(biāo)準(zhǔn)庫(kù)包、程序內(nèi)部包、第三方包。

      禁止使用相對(duì)路徑導(dǎo)入。

      禁止使用 Import Dot(“.”) 簡(jiǎn)化導(dǎo)入。

      在所有其他情況下,除非導(dǎo)入之間有直接沖突,否則應(yīng)避免導(dǎo)入別名。

      import ( "fmt" "os" "runtime/trace" nettrace "golang.net/x/trace" )

      1

      2

      3

      4

      5

      6

      7

      如果包名與導(dǎo)入路徑的最后一個(gè)元素不匹配,則必須使用導(dǎo)入別名。

      import ( client "example.com/client-go" trace "example.com/trace/v2" )

      1

      2

      3

      4

      代碼邏輯實(shí)現(xiàn)規(guī)范

      變量、常量定義規(guī)范

      函數(shù)內(nèi)使用短變量聲明(海象運(yùn)算符 :=)。

      函數(shù)外使用長(zhǎng)變量聲明(var 關(guān)鍵字),var 關(guān)鍵字一般用于包級(jí)別變量聲明,或者函數(shù)內(nèi)的零值情況。

      變量、常量的分組聲明一般需要按照功能來(lái)區(qū)分,而不是將所有類型都分在一組:

      const ( // Default section name. DEFAULT_SECTION = "DEFAULT" // Maximum allowed depth when recursively substituing variable names. _DEPTH_VALUES = 200 ) type ParseError int const ( ERR_SECTION_NOT_FOUND ParseError = iota + 1 ERR_KEY_NOT_FOUND ERR_BLANK_SECTION_NAME ERR_COULD_NOT_PARSE )

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      如果有可能,盡量縮小變量的作用范圍。

      // Bad err := ioutil.WriteFile(name, data, 0644) if err != nil { return err } // Good if err := ioutil.WriteFile(name, data, 0644); err != nil { return err }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      如果需要在 if 之外使用函數(shù)調(diào)用的結(jié)果,則不應(yīng)嘗試縮小變量的作用范圍。

      data, err := ioutil.ReadFile(name) if err != nil { return err } if err := cfg.Decode(data); err != nil { return err } fmt.Println(cfg) return nil

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      如果是枚舉常量,需要先創(chuàng)建相應(yīng)類型:

      type Scheme string const ( HTTP Scheme = "http" HTTPS Scheme = "https" )

      1

      2

      3

      4

      5

      6

      自構(gòu)建的枚舉類型應(yīng)該從 1 開(kāi)始,除非從 0 開(kāi)始是有意義的:

      // Bad type Operation int const ( Add Operation = iota Subtract Multiply ) // Good type Operation int const ( Add Operation = iota + 1 Subtract Multiply )

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      String 類型定義規(guī)范

      聲明 Printf-style String 時(shí),將其設(shè)置為 const 常量,這有助于 go vet 對(duì) String 類型實(shí)例執(zhí)行靜態(tài)分析。

      // Bad msg := "unexpected values %v, %v\n" fmt.Printf(msg, 1, 2) // Good const msg = "unexpected values %v, %v\n" fmt.Printf(msg, 1, 2)

      1

      2

      3

      4

      5

      6

      7

      優(yōu)先使用 strconv 而不是 fmt,將原語(yǔ)轉(zhuǎn)換為字符串或從字符串轉(zhuǎn)換時(shí),strconv 速度比 fmt 快。

      // Bad for i := 0; i < b.N; i++ { s := fmt.Sprint(rand.Int()) } // Good for i := 0; i < b.N; i++ { s := strconv.Itoa(rand.Int()) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      避免字符串到字節(jié)的轉(zhuǎn)換,不要反復(fù)從固定字符串創(chuàng)建字節(jié) Slice,執(zhí)行一次性完成轉(zhuǎn)換。

      // Bad for i := 0; i < b.N; i++ { w.Write([]byte("Hello world")) } // Good data := []byte("Hello world") for i := 0; i < b.N; i++ { w.Write(data) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      Slice、Map 類型定義規(guī)范

      盡可能指定容器的容量,以便為容器預(yù)先分配內(nèi)存,向 make() 傳入容量參數(shù)會(huì)在初始化時(shí)嘗試調(diào)整 Slice、Map 類型實(shí)例的大小,這將減少在將元素添加到 Slice、Map 類型實(shí)例時(shí)的重新分配內(nèi)存造成的損耗。

      使用 make() 初始化 Map 類型變量,使得開(kāi)發(fā)者可以很好的區(qū)分開(kāi) Map 類型實(shí)例的聲明,或初始化。使用 make() 還可以方便地添加大小提示。

      var ( // m1 讀寫(xiě)安全。 // m2 在寫(xiě)入時(shí)會(huì) panic。 m1 = make(map[T1]T2) m2 map[T1]T2 )

      1

      2

      3

      4

      5

      6

      如果 Map 類型實(shí)例包含固定的元素列表,則使用 map literals(map 初始化列表)的方式進(jìn)行初始化:

      // Bad m := make(map[T1]T2, 3) m[k1] = v1 m[k2] = v2 m[k3] = v3 // Good m := map[T1]T2{ k1: v1, k2: v2, k3: v3, }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      在追加 Slice 類型變量時(shí)優(yōu)先指定切片容量,在初始化要追加的切片時(shí)為 make() 提供一個(gè)容量值。

      for n := 0; n < b.N; n++ { data := make([]int, 0, size) for k := 0; k < size; k++{ data = append(data, k) } }

      1

      2

      3

      4

      5

      6

      Map 或 Slice 類型實(shí)例是引用類型,所以在函數(shù)調(diào)用傳遞時(shí),要注意在函數(shù)內(nèi)外保證實(shí)例數(shù)據(jù)的安全性,除非你知道自己在做什么。這是一個(gè)深拷貝和淺拷貝的問(wèn)題。

      // Bad func (d *Driver) SetTrips(trips []Trip) { d.trips = trips } trips := ... d1.SetTrips(trips) // 你是要修改 d1.trips 嗎? trips[0] = ... // Good func (d *Driver) SetTrips(trips []Trip) { d.trips = make([]Trip, len(trips)) copy(d.trips, trips) } trips := ... d1.SetTrips(trips) // 這里我們修改 trips[0],但不會(huì)影響到 d1.trips。 trips[0] = ...

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      返回 Map 或 Slice 類型實(shí)例時(shí),同樣要注意用戶對(duì)暴露了內(nèi)部狀態(tài)的實(shí)例的數(shù)值進(jìn)行修改:

      // Bad type Stats struct { mu sync.Mutex counters map[string]int } // Snapshot 返回當(dāng)前狀態(tài)。 func (s *Stats) Snapshot() map[string]int { s.mu.Lock() defer s.mu.Unlock() return s.counters } // snapshot 不再受互斥鎖保護(hù)。 // 因此對(duì) snapshot 的任何訪問(wèn)都將受到數(shù)據(jù)競(jìng)爭(zhēng)的影響。 // 影響 stats.counters。· snapshot := stats.Snapshot() // Good type Stats struct { mu sync.Mutex counters map[string]int } func (s *Stats) Snapshot() map[string]int { s.mu.Lock() defer s.mu.Unlock() result := make(map[string]int, len(s.counters)) for k, v := range s.counters { result[k] = v } return result } // snapshot 現(xiàn)在是一個(gè)拷貝 snapshot := stats.Snapshot()

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      結(jié)構(gòu)體定義規(guī)范

      嵌入結(jié)構(gòu)體中作為成員的結(jié)構(gòu)體,應(yīng)位于結(jié)構(gòu)體內(nèi)的成員列表的頂部,并且必須有一個(gè)空行將嵌入式成員與常規(guī)成員分隔開(kāi)。

      在初始化 Struct 類型的指針實(shí)例時(shí),使用 &T{} 代替 new(T),使其與初始化 Struct 類型實(shí)例一致。

      sval := T{Name: "foo"} sptr := &T{Name: "bar"}

      1

      2

      接口定義規(guī)范

      特別的,如果希望通過(guò)接口的方法修改接口實(shí)例的實(shí)際數(shù)據(jù),則必須傳遞接口實(shí)例的指針(將實(shí)例指針賦值給接口變量),因?yàn)橹羔樦赶蛘嬲膬?nèi)存數(shù)據(jù):

      type F interface { f() } type S1 struct{} func (s S1) f() {} type S2 struct{} func (s *S2) f() {} // f1.f() 無(wú)法修改底層數(shù)據(jù)。 // f2.f() 可以修改底層數(shù)據(jù),給接口變量 f2 賦值時(shí)使用的是實(shí)例指針。 var f1 F := S1{} var f2 F := &S2{}

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      函數(shù)、方法定義規(guī)范

      函數(shù)、方法的參數(shù)排列順序遵循以下幾點(diǎn)原則(從左到右):

      參數(shù)的重要程度與邏輯順序。

      簡(jiǎn)單類型優(yōu)先于復(fù)雜類型。

      盡可能將同種類型的參數(shù)放在相鄰位置,則只需寫(xiě)一次類型。

      函數(shù)、方法的順序一般需要按照依賴關(guān)系由淺入深由上至下排序,即最底層的函數(shù)出現(xiàn)在最前面。例如,函數(shù) ExecCmdDirBytes 屬于最底層的函數(shù),它被 ExecCmdDir 函數(shù)調(diào)用,而 ExecCmdDir 又被 ExecCmd 調(diào)用。

      避免實(shí)參傳遞時(shí)的語(yǔ)義不明確(Avoid Naked Parameters),當(dāng)參數(shù)名稱的含義不明顯時(shí),使用塊注釋語(yǔ)法:

      func printInfo(name string, isLocal, done bool) // Bad printInfo("foo", true, true) // Good printInfo("foo", true /* isLocal */, true /* done */)

      1

      2

      3

      4

      5

      6

      7

      上述例子中,更好的做法是將 bool 類型換成自定義類型。將來(lái),該參數(shù)可以支持不僅僅是兩個(gè)狀態(tài)(true/false):

      func printInfo(name string, isLocal, done bool) type Region int const ( UnknownRegion Region = iota Local ) type Status int const ( StatusReady Status= iota + 1 StatusDone // Maybe we will have a StatusInProgress in the future. ) func printInfo(name string, region Region, status Status)

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      避免使用 init() 函數(shù),否則 init() 中的代碼應(yīng)該保證:

      函數(shù)定義的內(nèi)容不對(duì)環(huán)境或調(diào)用方式有任何依賴,具有完全確定性。

      避免依賴于其他init()函數(shù)的順序或副作用。雖然順序是明確的,但代碼可以更改, 因此 init() 函數(shù)之間的關(guān)系可能會(huì)使代碼變得脆弱和容易出錯(cuò)。

      避免訪問(wèn)或操作全局或環(huán)境狀態(tài),如:機(jī)器信息、環(huán)境變量、工作目錄、程序參數(shù)/輸入等。

      避免 I/O 操作,包括:文件系統(tǒng)、網(wǎng)絡(luò)和系統(tǒng)調(diào)用。

      不能滿足上述要求的代碼應(yīng)該被定義在 main 中(或程序生命周期中的其他地方)。

      Named Result Parameters

      給函數(shù)返回值命名。尤其對(duì)于當(dāng)你需要在函數(shù)結(jié)束的 defer 中對(duì)返回值做一些事情,返回值名字是必要的。

      // 錯(cuò)誤 func (n *Node) Parent1() *Node func (n *Node) Parent2() (*Node, error) // 正確 func (n *Node) Parent1() (node *Node) func (n *Node) Parent2() (node *Node, err error)

      1

      2

      3

      4

      5

      6

      7

      Receiver Names

      結(jié)構(gòu)體方法中,接受者的命名(Receiver Names)不應(yīng)該采用 me,this,self 等通用的名字,而應(yīng)該采用簡(jiǎn)短的(1 或 2 個(gè)字符)并且能反映出結(jié)構(gòu)體名的命名風(fēng)格,它不必像參數(shù)命名那么具體,因?yàn)槲覀儙缀醪魂P(guān)心接受者的名字。

      例如:Struct Client,接受者可以命名為 c 或者 cl。這樣做的好處是,當(dāng)生成了 go doc 后,過(guò)長(zhǎng)或者過(guò)于具體的命名,會(huì)影響搜索體驗(yàn)。

      Receiver Type

      編寫(xiě)結(jié)構(gòu)體方法時(shí),接受者的類型(Receiver Type)到底是選擇值還是指針通常難以決定。一條萬(wàn)能的建議:如果你不知道要使用哪種傳遞時(shí),請(qǐng)選擇指針傳遞吧!

      建議:

      當(dāng)接受者是 map、chan、func,不要使用指針傳遞,因?yàn)樗鼈儽旧砭褪且妙愋汀?/p>

      當(dāng)接受者是 slice,而函數(shù)內(nèi)部不會(huì)對(duì) slice 進(jìn)行切片或者重新分配空間,不要使用指針傳遞。

      當(dāng)函數(shù)內(nèi)部需要修改接受者,必須使用指針傳遞。

      當(dāng)接受者是一個(gè) struct,并且包含了 sync.Mutex 或者類似的用于同步的成員。必須使用指針傳遞,避免成員拷貝。

      當(dāng)接受者類型是一個(gè) struct 并且很龐大,或者是一個(gè)大的 array,建議使用指針傳遞來(lái)提高性能。

      當(dāng)接受者是 struct、array、slice,并且其中的元素是指針,并且函數(shù)內(nèi)部可能修改這些元素,那么使用指針傳遞是個(gè)。

      不錯(cuò)的選擇,這能使得函數(shù)的語(yǔ)義更加明確。

      當(dāng)接受者是小型 struct,小 array,并且不需要修改里面的元素,里面的元素又是一些基礎(chǔ)類型,使用值傳遞是個(gè)不錯(cuò)的選擇。

      錯(cuò)誤處理規(guī)范

      err 總是作為函數(shù)返回值列表的最后一個(gè)。

      如果一個(gè)函數(shù) return error,一定要檢查它是否為空,判斷函數(shù)調(diào)用是否成功。如果不為空,說(shuō)明發(fā)生了錯(cuò)誤,一定要處理它。

      不能使用 _ 丟棄任何 return 的 err。若不進(jìn)行錯(cuò)誤處理,要么再次向上游 return err,或者使用 log 記錄下來(lái)。

      盡早 return err,函數(shù)中優(yōu)先進(jìn)行 return 檢測(cè),遇見(jiàn)錯(cuò)誤則馬上 return err。

      錯(cuò)誤提示(Error Strings)不需要大寫(xiě)字母開(kāi)頭的單詞,即使是句子的首字母也不需要。除非那是個(gè)專有名詞或者縮寫(xiě)。同時(shí),錯(cuò)誤提示也不需要以句號(hào)結(jié)尾,因?yàn)橥ǔT诖蛴⊥赍e(cuò)誤提示后還需要跟隨別的提示信息。

      采用獨(dú)立的錯(cuò)誤流進(jìn)行處理。盡可能減少正常邏輯代碼的縮進(jìn),這有利于提高代碼的可讀性,便于快速分辨出哪些還是正常邏輯代碼,例如:

      // 錯(cuò)誤寫(xiě)法 if err != nil { // error handling } else { // normal code } // 正確寫(xiě)法 if err != nil { // error handling return // or continue, etc. } // normal code

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      另一種常見(jiàn)的情況,如果我們需要用函數(shù)的返回值來(lái)初始化某個(gè)變量,應(yīng)該把這個(gè)函數(shù)調(diào)用單獨(dú)寫(xiě)在一行,例如:

      // 錯(cuò)誤寫(xiě)法 if x, err := f(); err != nil { // error handling return } else { // use x } // 正確寫(xiě)法 x, err := f() if err != nil { // error handling return } // use x

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      盡量不要使用 panic,除非你知道你在做什么。只有當(dāng)實(shí)在不可運(yùn)行的情況下采用 panic,例如:文件無(wú)法打開(kāi),數(shù)據(jù)庫(kù)無(wú)法連接導(dǎo)致程序無(wú)法正常運(yùn)行。但是對(duì)于可導(dǎo)出的接口不能有 panic,不要拋出 panic 只能在包內(nèi)采用。建議使用 log.Fatal 來(lái)記錄錯(cuò)誤,這樣就可以由 log 來(lái)結(jié)束程序。

      單元測(cè)試規(guī)范

      單元測(cè)試都必須使用 GoConvey 編寫(xiě),且覆蓋率必須在 80% 以上。

      業(yè)務(wù)代碼文件和單元測(cè)試文件放在同一目錄下。

      單元測(cè)試文件名以 *_test.go 為后綴,例如:example_test.go。

      測(cè)試用例的函數(shù)名稱必須以 Test 開(kāi)頭,例如:Test_Logger。

      如果為結(jié)構(gòu)體的方法編寫(xiě)測(cè)試用例,則需要以 Text__ 的形式命名,例如:Test_Macaron_Run。

      每個(gè)重要的函數(shù)都要同步編寫(xiě)測(cè)試用例。

      測(cè)試用例和業(yè)務(wù)代碼同步提交,方便進(jìn)行回歸測(cè)試。

      在測(cè)試中,我們很可能會(huì)使用 Import Dot(“.”)這個(gè)特性,可以我們避免循環(huán)引用問(wèn)題,除此之外都不要使用 . 進(jìn)行簡(jiǎn)易導(dǎo)入。例如:

      package foo_test import ( "bar/testutil" // also imports "foo" . "foo" )

      1

      2

      3

      4

      5

      6

      以上例子,該測(cè)試文件不能定義在于 foo 包里面,因?yàn)樗?import bar/testutil,而 bar/testutil 有 import 了 foo,這將構(gòu)成循環(huán)引用。所以我們需要將該測(cè)試文件定義在 foo_test 包中。并且使用 import . “foo” 后,該測(cè)試文件內(nèi)代碼能直接調(diào)用 foo 里面的函數(shù)而不需要顯式地寫(xiě)上包名。

      Go

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:金山WPS Office實(shí)用自動(dòng)化技巧四則
      下一篇:Excel vba刪除特定的最后幾列
      相關(guān)文章
      亚洲一卡二卡三卡| 亚洲精品无码专区在线在线播放| 国产亚洲欧美在线观看| 亚洲成人福利网站| 亚洲AV无码成人精品区蜜桃| 日韩亚洲人成在线综合日本| 久久久久亚洲AV成人网人人网站| 亚洲综合色视频在线观看| 国产精品亚洲精品日韩电影| 亚洲?V无码乱码国产精品| 亚洲v国产v天堂a无码久久| 亚洲 小说区 图片区 都市| 国产亚洲高清在线精品不卡| 亚洲第一成人影院| 相泽亚洲一区中文字幕| 亚洲中文字幕在线第六区| 亚洲一区二区三区在线观看精品中文| 亚洲宅男天堂在线观看无病毒| 亚洲精品少妇30p| 亚洲欧洲免费视频| 亚洲婷婷综合色高清在线| 亚洲制服丝袜第一页| 在线观看日本亚洲一区| 亚洲国产精品ⅴa在线观看| 亚洲AV无码国产剧情| 亚洲国产天堂久久久久久| 国产美女亚洲精品久久久综合| 久久精品国产亚洲一区二区| 亚洲AV成人精品网站在线播放| 久久精品国产亚洲av麻豆色欲 | 亚洲精品福利在线观看| 亚洲免费电影网站| 2020年亚洲天天爽天天噜| 亚洲av无码专区在线观看下载| 国产成人 亚洲欧洲| 337p日本欧洲亚洲大胆裸体艺术| 国产亚洲成AV人片在线观黄桃| 亚洲网站在线观看| 亚洲一级片在线观看| 亚洲av第一网站久章草| 亚洲第一页日韩专区|