開始評分量表

本文件列出在 Fuchsia 原始碼中編寫 Go 時必須遵循的慣例 樹。這些慣例是結合最佳做法 以及一些是為了維持一致性的選擇

本文所描述的慣例可視為新增或不常用的慣例 詳細做法請參閱 Effective GoGo 程式碼審查留言

一般

檔案中的宣告順序

Go 檔案中的宣告組織方式有何顯著差異,尤其是 通常會有一個包含完整內容的大型檔案,或是較小的檔案 這裡有一些經驗法則。

用於針對頂層宣告 (常數、介面、類型,以及 相關方法),通常會按功能群組列出,而且 以下順序:

  • 常數 (包含列舉);
  • 匯出的介面、類型和函式;
  • 未匯出的介面、類型和函式,可支援匯出的類型。

例如,fidlgen lib 中的一組功能可能是 而另一個群組可能讀取並去標準化 JSON IR。這些 建議在規模擴大時成為個別檔案。

對於含方法的型別,依序列出:

  • 類型宣告 (例如 type myImpl struct { ...);
  • type assertion(s) (類型宣告) (如有);
  • 方法 (先有匯出方法,第二是未匯出的方法);
  • 在適用情況下,建議將實作介面的方法分組在一起,即所有 Foo 介面的方法,接著是 Bar 介面的所有方法;
  • 通常,雖然已匯出,但 String() 方法仍會是最後一種。

如需列舉項目,請參閱如何定義列舉

命名慣例

Go 相當著眼於如何命名。部分慣例:

  • var err error
  • var buf bytes.Buffer
  • var buf strings.Builder
  • var mu sync.Mutex
  • var wg sync.WaitGroup
  • var ctx context.Context
  • _, ok := someMap[someKey]
  • _, ok := someValue.(someType)

定義方法時,請確認接收器名稱一致 (當 。另請參閱指標與數值的比較

避免匯出

在 Go 中,ID 可取消匯出或匯出 (公開/私密術語) 未使用)。匯出的聲明開頭為大寫字母 MyImportantThing,未匯出的宣告開頭為小寫 字母 myLocalThing

請只匯出您確實需要且想重複使用的內容。注意事項 後來變更匯出是相對簡單的方法 直到必要為止讓說明文件更加簡潔。

如果您的類型採用反射方法,例如:範本中或自動建立 您可以將差異匯出到 go-cmp 欄位,在未匯出的 ID 中 只要使用來自這些領域的 小型資料集訓練即可再次提醒您,除了要匯出的欄位外, 其他規則。

名稱與類型相同的本機變數沒有問題。如果 您對匯出類型編寫 server := Server{...},然後 server := server{...} 適用於未匯出的類型。

一律不得使用已命名的傳回值

語意會造成混淆,而且容易出錯。即使在 演進過程中通常會導致 進而產生比實際情形更大的差異 變更。

系統會顯示建議, 特殊情況 或「已命名的回傳值適合這種特殊情況」我們的 規則,則不使用已命名的傳回值。

(定義介面時可以隨意命名。與 name 會在實作中傳回,但對語意沒有影響)。

定義列舉

定義列舉的標準模式如下:

type myEnumType int

const (
    _ myEnumType = iota
    myFirstEnumValue
    mySecondEnumValue
)

只有在使用 iota 時才能省略類型。使用明確值時,您必須 重複類型:

const (
    _ myEnumType = iota
    myFirstEnumValue
    mySecondEnumValue
    // ...
    myCombinedValue myEnumType = 100
)

此外,如果您希望這個列舉使用文字表示法:

var myEnumTypeStrings = map[myEnumType]string{
    myFirstEnumValue:  "myFirstEnumValue",
    mySecondEnumValue: "mySecondEnumValue",
}

func (val myEnumType) String() string {
    if fmt, ok := myEnumTypeStrings[val]; ok {
        return fmt
    }
    return fmt.Sprintf("myEnumType(%d)", val)
}

例如 mdlint: TokenKind. 偏好使用 map,而非使用 switch,因為這是很常見的做法 不斷更新程式碼,以便計算地圖以外的事物,例如:1 個FromString 建構函式運作效率低落,例如搜尋,或是預先計算 stringsToMyEnum 反向對應)。

您只能在為自然預設值的情況下使用值為 0 的列舉成員。 詳情請參閱列舉 FIDL API Rubric 項目

另一個選項是使用 cmd/stringer 產生列舉。這會產生 程式碼的執行效率更高,但維護需要花費更多心力。別擔心!您可以使用 這個方法。

集合

如要表示集合,請使用對應空白的結構:

allMembers := map[uint32]struct{}{
    5: {},
    8: {},
}

將 Slice 執行個體化

如果您要使用常值定義切片,則 []T{ ... } 是 非常可靠

否則,請使用 var slice []T 建立空的配量,也就是不要使用。 slice:= []T{}

請注意,預先分配配量可帶來顯著的成效提升。 且可能比更簡單的語法來得好請參閱執行個體配量用途和 內部

使地圖例項化

如要使用常值定義地圖,則 map[K]T{ ... } 是 非常可靠

否則,請使用 make(map[string]struct{}) 建立空白地圖,這樣才不會。 使用 myEmptyMap := map[K]T{}

不正常退貨

當函式需要傳遞非正常的傳回時 (即「失敗」),請按照以下模式輸入幾個模式:

  1. func doSomething(...) (data, bool),也就是傳回資料,否。
  2. func doSomething(...) *data,亦即傳回資料,或 nil。
  3. func doSomething(...) (data, error),亦即傳回資料,或 錯誤。
  4. func doSomething(...) dataWrapper,亦即傳回內含的包裝函式 作業結果的相關結構化資訊。

對於在各種口味下行走的時機,制定艱難且快速的規則。 但也有一些一般原則

在 Go 中用於多態性時,仰賴 nil 與現在很容易出錯 內容:nil 不是單位,其中有許多nil,而各自不同 其他!因此,最好能夠傳回 使用的方法可能為多型時,額外的 ok 布林值 定義。建議只使用 nil,而只顯示該方法的時機 只在單體式情境中使用或者換個方式說,使用圖案 (2) 比 (1) 更簡便,而且當所有呼叫端都使用 函式,不會透過介面鏡頭查看傳回的值。

傳回 error 表示發生問題,呼叫端 並附上相關解釋呼叫端可正常付款 以便讓錯誤逐漸增加,可能是在錯誤途中換行,或是進行某些 以及錯誤處理與復原與傳回 ok 方法的金鑰區別 布林值 (或 nil 資料),則是指在傳回 error 時,方法為 確保模型有足夠的瞭解背景資訊,進而分類為 錯誤。

舉例來說,LookupUser(user_id uint64) 傾向傳回 ok 布林值 表示找不到使用者,但 LookupCountryCode(code IsoCountryCode) 則較好 在特定國家/地區傳回 error,指出設定錯誤。 無法查詢 (或要求的國家/地區無效)。

如果結果顯示 方法很複雜,需要使用結構化資料來描述。舉例來說, 適合使用資料包裝函式的驗證 API,這種 API 會 XML 文件並傳回錯誤路徑清單,每個路徑都含有警告或錯誤 。

靜態類型斷言

由於 Go 採用結構體型,例如型別是否實作介面 取決於其結構 (而非宣告)。我們可以輕鬆將 型別會實作一些介面,但實際上卻並非如此。這會產生 只到一陣子沒用到遠距離,導致編譯器難以辨識 發生錯誤。

為解決這個問題,請務必為所有介面編寫類型宣告 會實作 (是,標準程式庫中的類型) too):

var _ MyInterface = (*myImplementation)(nil)

這會建立型別 nil,其指派會強制編譯器檢查型別 確保一致性。

我們通常有許多導入都必須實作相同的介面, 例如:代表單節點實作的抽象語法樹狀結構 運算式介面在這些情況下,您應在 因此也記錄了所有預期的子類型

var _ = []MyInterface{
    (*myImplementation)(nil),
    (*myOtherImplementation)(nil),
    ...
}

關於放置這類斷言的位置,經驗法則如下: 如下:

  • 最好在實作下方都採用單一型別斷言 獨立實作 (例如 這裡
  • 執行所有實作時,優先在介面下方使用分組類型的斷言 主要用途為搭配 (例如 Expression 的運算式節點) 代表 AST 的介面),例如 這裡

嵌入

嵌入是 Go 中非常強大的概念。充分運用。已讀 嵌入相關說明。

嵌入介面或結構類型時,應先列出在 這些元素相應的介面或結構體類型,也就是說,嵌入的類型 會顯示為第一個欄位

指標與值的差異

如需方法接收器,請參閱指標與值一文 和接收器類型。 tl;dr 是希望保持一致,但如果有任何疑慮,請使用指標接收器。 對於特定型別,請務必保持一致的傳遞方式,例如 一律按照值傳遞、一律以參照方式傳遞 方法是由此型別定義 (含值或指標接收器)。

另請注意,根據值傳遞結構體,假設呼叫端 不正確或不正確。您可以輕鬆按住地圖、切片或 並因此進行修改所以在 Go 中,這個音不正確 也就是「以值傳遞是 const」。

如需具體建議,請參閱「實作介面」一節 有關方法接收器的資訊

實作介面

一般使用指標接收器實作介面;實作介面 使用值接收器會導致介面同時透過值和 指標,可使型別斷言變複雜,試圖列舉出可能性 介面的實作。查看執行個體 fxrev.dev/269371

在某些情況下,使用值實作介面。 適用情況:

  • 當類型從未用做指標時。舉例來說,自訂排序 方法是定義 type mySlice []myElement 並實作 sort.Interface (mySlice)。 類型 *mySlice 絕不會使用,因為 []myElement 已經是 參照。範例 請按這裡
  • 不應使用類型斷言或類型切換值 介面類型例如: Stringer 通常會在以下位置實作: 值型別。接受 val Stringer 的函式並不常見 開啟「val.(type)」。

如有疑慮,請一律使用指標接收器實作介面。

留言

閱讀評論,特別是:

文件註解功能最適合完整的句子, 自動產生簡報第一句應為一句話的摘要 開頭為要宣告的名稱

// Compile parses a regular expression and returns, if successful,
// a Regexp that can be used to match against text.
func Compile(str string) (*Regexp, error) {

針對類型的文件註解 文章 「A」、「An」或「The」。Go 標準程式庫中的所有說明文件都會遵循這個 例如 // A Buffer is a variable-sized ...

針對長度超過一句的註解,樣式通常會優先使用 一個摘要句子,然後加上空白行,然後再輸入一段內容 詳細資料。如需範例,請參閱 FileHeader 結構體。

換行時發生錯誤

使用 fmt.Errorf 傳播錯誤時:

  • 使用 %s 即可只包含其字串值;
  • 使用 %w 允許呼叫端解除包裝並觀察包裝錯誤。附註:%w 這些包裝錯誤就會成為 API 的一部分

請參閱「在 Go 中處理錯誤 1.13,更具體來說是否 重點回顧

在某些情況下,必須採用錯誤傳播的方式 符合 API 合約規定,例如而不是 RDBMS 驅動程式庫 系統傳回的錯誤代碼,表示來電者可恢復通話。因此 而必須明確包裝潛在錯誤,而不是依賴 在 fmt.Errorf

fmt 個動詞

盡量避免使用 %v,優先使用系統支援的特定 fmt 動詞 運算元這麼做的好處是可讓 go vet 檢查動詞 確實受到運算元支援。

如果運算元是未實作 fmt.Stringer 的結構, %v 不太可能產生理想的結果;%+v%#v可能會 更好的選擇

這項規則的常見例外狀況,是執行階段可能是 nil 的運算元 - nil 值已由 %v 妥善處理,但並非所有其他動詞。

引用字串時,請使用 %q,而不要明確呼叫 strconv.Quote

如要傳播錯誤,請參閱錯誤包裝一文。

GN 目標

Go 工具的一般 BUILD.gn 檔案如下所示:

go_library("gopkg") {
  sources = [
    "main.go",
    "main_test.go",
  ]
}

go_binary("foo") {
  library = ":gopkg"
}

go_test("foo_test") {
  library = ":gopkg"
}

如果您擁有巢狀套件 (僅適用於 case),請使用 name = "go.fuchsia.dev/fuchsia/<path>/..." 表單以啟用 遞迴套件來源:

go_library("gopkg") {
  name = "go.fuchsia.dev/fuchsia/tools/foo/..."
  sources = [
    "main.go",
    "subdir/bar.go",
    "extra/baz.go",
  ]
}

測試

結尾為 _test 的套件

通常 _test.go 檔案會與所測試程式碼位於相同的套件中 (例如 package foo),而且可以存取未匯出的宣告。Go 也允許 您在套件後方加上 帶有 _test 的名稱 (例如 package foo_test),此時會編譯為 獨立套件,但以主要二進位檔連結及執行。這種做法 稱為外部測試不要為套件命名 加上 _test 後置字串,除非您編寫外部測試,請改為參閱 測試套件

在進行整合等級測試或與其互動時,優先使用外部測試 僅限測試中套件的匯出部分

使用結尾為 _test 的套件也對提供編譯過的範例也很有幫助 程式碼,即可透過套件選取器直接貼上。例如 範例 程式碼 來源

測試公用程式

測試公用程式是套件中使用的輔助程式,可協助測試。是 「測試程式碼」該檔案位於具有 _test.go 後置字串的檔案中地點 測試公用程式;testutils_test.go這個慣例是 ,導致此程式碼不包含在非測試中 二進位檔,確保它不會在測試以外的地方使用。

測試套件

測試套件是一種程式庫,旨在簡化編寫測試的流程。這項服務 是「Production code」— 結尾不是 _test.go 字尾,但僅供 可使用這組 ID

測試套件的命名慣例是使用「test」/稱謂 指定要用於測試的套件名稱標準程式庫中的範例如下 httptest 和 Fuchsia 樹中的 iotest fidlgentestemulatortest

其他資源