Go 代碼審查建議

注:該文的原文來自於 go-wiki 爲 Go Code Review Commentshtml

Go 代碼審查建議

該頁收集了 Go 代碼審查時候的常見意見,以致於一個詳細說明能被快速參考。這是一個常見的錯誤清單,而不是一個風格指南。node

你能夠看 effective go 做爲補充。git

請在編輯這個頁面前先討論這個變動,就算是一個很小的變動,許多人都有本身的想法,這裏不是戰場。github

gofmt

運行 gofmt 來自動化的解決你代碼的主要的機械的風格問題,幾乎全部的不正規的 go 代碼都使用 gofmt。該文檔的其他部分涉及非機械式的風格點。golang

另一個替代方案是使用 goimports,它是 gofmt 的父集,額外的新增(和移除)了 import 行。app

註釋句子

查看 http://golang.org/doc/effective_go.html#commentary。註釋文檔聲明瞭應該是整句,即便它看起來是有點多餘的。這個方法使得它被提取進 godoc 文檔的時候,是很是格式化的。註釋應該開始於這個事物被描述的名字,並在一段時期結束。框架

// A Request represents a request to run a command.
type Request struct { ...

// Encode writes the JSON encoding of req to w.
func Encode(w io.Writer, req *Request) { ...

等等。編輯器

Doc 建議

全部頂級的,導出的名字都應該有文檔註釋,做爲 non-trivial unexported type 和功能聲明。看 http://golang.org/doc/effective_go.html#commentary 來獲取更多的註釋約定的更多信息。ide

不要使用 Panic

http://golang.org/doc/effective_go.html#errors,不要使用 panic 用於正常的錯誤處理。使用 error 和多個返回值。函數

Error Strings

Error strings 不該該大寫(除非有專有名詞或是首字母縮小)或是以標點符號結束。由於它們一般在其餘上下文中打印。即便用 fmt.Errorf("something bad") 而不是 fmt.Errorf("Something bad"),以致於 log.Print("Reading %s: %v", filename, err) 格式沒有一個虛假的大寫字母中間消息。這個不適用於日誌記錄,這個是絕對面向行的,不被包含在其餘信息中。

注:上面 Errorf 區別是裏面的內容 s 這個 字母一個大寫一個小寫】

處理錯誤

http://golang.org/doc/effective_go.html#errors,不要使用 _ 變量丟棄錯誤,若是一個函數返回一個錯誤,檢查它確保函數式成功的。處理這個錯誤,返回它,或是在真正的異常狀況下使用 panic。

Imports

Imports 是以組的形式組織的,在它們之間使用空行,標準包在第一組。

package main

import (
    "fmt"
    "hash/adler32"
    "os"

    "appengine/user"
    "appengine/foo"

    "code.google.com/p/x/y"
    "github.com/foo/bar"
)

goimports 將幫助你作到這個。

Import 的點號

import 的點號形式對測試是很是有用的,因爲循環依賴,不能成爲被測試的包的一部分。

package foo_test

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

在這個例子中,測試文件不能在 foo 包中,由於它使用 bar/testutil,它也引入 foo,所以咱們使用 'import .' 形式來假裝成 foo 包的一部分,即便它不是。除了這種狀況,不要在你的程序中使用 'import .' 。它會使得你的程序難以閱讀,由於它是難以理解的,一個名字好比 Quux 在當前包種是不是一個頂級的標識符或是在一個引入包中。

Indent Error Flow

在一個最小的代碼縮進中嘗試保存正常的代碼路徑,和縮進錯誤處理,首先處理錯誤。這樣作提高了程序的可讀性,這個符合視覺的快速掃描習慣。例如,不要這些寫:

if err != nil {
    // error handling
} else {
    // normal code
}

而應該這樣寫:

if err != nil {
    // error handling
    return // or continue, etc.
}
// normal code

若是 if 語句有一個初始化的語句,像這樣:

if x, err := f(); err != nil {
  // error handling
  return
} else {
  // use x
}

這就要求把短的變量聲明移動到它本身的行去。

x, err := f()
if err != nil {
  // error handling
  return
}
// use x

首字母縮寫

名字的單詞應該是首字母縮寫的(好比,"URL" 或 "NATO")是一致的狀況。例如,"URL" 應該以 "URL" 或是 」url「 展示(就像 "urlPony", 或 "URLPony"),毫不是 Url 。這裏有一個示例:ServeHTTP 而不是 ServeHttp。

這個規則也適用於 "ID",當它是 "identifier" 短名稱的時候。所以使用 "appID" 代替 "appId"。

被 protocol buffer 編譯器生成的代碼是脫離了這個規則的。人類寫的代碼應該比機器寫的代碼更高標準。

行長度

這在 Go 的代碼中沒有嚴格的限制,可是爲了不太長的行,一樣的,當他們在更可讀的長度的時候,不該該添加換行符使其更短 --- 好比,若是他們是重複的。

建議是一般在包裝前不超過 80 個字符,不是由於它是一個規則,而是由於它在一個可顯示幾百列的編輯器中查看的時候可讀性更高。人類更適合窄文本相對於一個寬文本。無論怎樣,godoc 應該以更好的方式渲染它。

Mixed Caps

http://golang.org/doc/effective_go.html#mixed-caps,這個甚至當它打破了其餘語言的習慣的時候也適用。好比一個未導出的常量是 maxLength 而不是 MaxLength 或是 MAX_LENGTH。

結果參數命名

考慮下這個在 godoc 應該是看起來像什麼,結果參數命名像:

func (n *Node) Parent1() (node *Node)
func (n *Node) Parent2() (node *Node, err error)

更好的用法是:

func (n *Node) Parent1() *Node
func (n *Node) Parent2() (*Node, error)

換句話說,若是一個函數返回了兩個或是三個相同類型的參數,或者若是一個結果的含義經過上下文不清楚,添加命名會很是有用,好比:

func (f *Foo) Location() (float64, float64, error)

沒有這個清晰:

// Location returns f's latitude and longitude.
 // Negative values mean south and west, respectively.
 func (f *Foo) Location() (lat, long float64, err error)

Naked 返回值是好的,若是函數很小。一旦它是一箇中型函數,必須明確你的返回值,必然的結果:僅僅使得你使用 naked 返回值是不知道命名結果參數的。清晰的文檔一直是比在你的函數中保存一行或兩行更重要。

最後,在一些狀況下,爲了在一個 deferred closure 中改變它,你須要命名一個結果參數。這一般是沒有問題的。

Naked Returns

查看 CodeReviewComments#Named_Result_Parameters

包建議

Package 建議,就像全部的註釋都應該由 godoc 呈現,必須與包相鄰,而沒有空行:

// Package math provides basic constants and mathematical functions.
package math
/*
Package template implements data-driven templates for generating textual
output such as HTML.
....
*/
package template

查看 http://golang.org/doc/effective_go.html#commentary 獲取更多關於註釋約定的信息。

包名

在你的包中的全部的引用的名稱應該使用包名,所以你能夠忽略這個名字的標識符。好比,若是你在包 chubby 中,你不須要鍵入 ChubbyFile,客戶端將寫成 chubby.ChubbyFile。相反,命名該類型文件,客戶端將寫成 chubby.File,看 http://golang.org/doc/effective_go.html#package-names 獲取更多信息

傳遞值

不要將指針做爲函數參數傳遞只是爲了省幾字節,若是函數引用的參數 x 僅僅至始至終都做爲 x,那麼參數就不該該使用指針。常見的實例包括傳遞一個指針給一個 string (string),或是一個指針給一個 interface 值(*io.Reader)。在兩個狀況中,它本身的值都是不變的,能夠被直接傳遞。這個建議不適用於大的 structs,或是會增加的小的 structs。

Receiver Names

一個方法的 receiver 的名字應該是它身份的一個反射;一般一兩個字母是足夠知足它類型的縮寫(好比,"c" or "cl" 做爲 "Client" 的縮寫)。不要使用通用名稱,好比 "me", "this" or "self",面嚮對象語言的典型標識是更注重方法而不是函數。該名字沒必要做爲方法參數的描述,它的角色是很是明顯的,而且不是做爲文檔目的。它能夠是很是短的,由於它出如今每個方法的幾乎每一行上;熟悉認可整潔。須要保持一致,若是你在一個方法中調用 "c" receiver,不能在另一個方法中調用 "cl"。

Receiver Type

在一個方法上選擇是使用一個值 receiver 仍是指針 receiver 是很是困難的,特別是對於 Go 新手。若是不能確定,那就使用指針,可是有時候一個值 receiver 更有意義。一般是由於效率的緣由,好比小的不變的 structs 或是基礎類型的值。
根據經驗,通常來講:

  • 若是 receiver 是一個 map,func 或 chan,不要使用指針
  • 若是 receiver 是一個 slice 和 方法再也不 reslice 或是 重分配的 slice,不要使用指針
  • 若是方法須要可變的 receiver,receiver 必須使用指針
  • 若是 receiver 是一個包含 sync.Mutex 或是相似的 synchronizing 屬性的 struct,receiver 必須是指針以免複製
  • 若是 receiver 是一個大的 struct 或是 array,一個指針 receiver 會更有效率。多大是大?假設它是要傳遞它全部的元素做爲方法的參數。若是感到太大,它一樣對於 receiver 也是大的。
  • Can function or methods, either concurrently or when called from this method, be mutating the receiver? 當方法被調用是,一個值類型的副本被建立,所以外部更新,不會影響 receiver。若是在原始的 receiver 中改變必須是可見的,那 receiver 必須是指針。
  • 若是 receiver 是一個 struct, array 或 slice 和 它任何一個元素是指針,那它便是可變的。更喜歡指針 receiver,由於對於讀者來講,它的意圖更加清晰
  • 若是 receiver 是小的 array 或 struct,那天然是值類型(好比, time.Time 類型),沒有可變的屬性和沒有指針,或者僅僅是一個簡單的基礎類型,好比 int 或 string,一個值 receiver 會更有意義。一個值 receiver 能夠減小生成的內存垃圾數量;若是一個值被傳遞給一個值方法,一個棧拷貝將被使用,而不是在 heap 上分配內存(編譯器嘗試聰明的避免分配內存,但它可能不會一直成功)。在沒有優化前,沒有由於這個緣由選擇一個值 receiver。
  • 最後,當不肯定的時候,請使用指針 receiver

Useful Test Failures

測試應該在失敗的時候伴隨着輸出有幫助的信息告訴你失敗緣由是什麼,輸入是什麼,實際發生了什麼,指望值是什麼。它極可能成爲寫一堆 assertFoo 的幫手。可是確保你的幫手生成了有用的錯誤信息。假設一個不是你的人,或者不是你團隊的人在 debugging 你的錯誤測試。一個典型的 Go 失敗測試應該像這樣:

if got != tt.want {
                t.Errorf("Foo(%q) = %d; want %d", tt.in, got, tt.want)    // or Fatalf, if test can't test anything more past this point
        }

注意這裏的實際命令應該是指望 != ,而且消息也使用這個命令。一些測試框架鼓勵寫這些後置的寫法:0 != x, 「指望 x 得到 0」等等,Go 不這樣作。

若是看起來有不少類型,你可能想寫一個 table-driven 測試:http://code.google.com/p/go-wiki/wiki/TableDrivenTests

另一個經常使用的技術是消除錯誤測試的歧義,當使用一個有不一樣輸入的來使用一個不一樣的 TestFoo 函數來包裝每一個調用測試幫手的時候,所以這個失敗測試使用的名字爲:

func TestSingleValue(t *testing.T) { testHelper(t, []int{80}) }
     func TestNoValues(t *testing.T) { testHelper(t, []int{}) }

在任何狀況下,你的責任是無論之後誰調試你的代碼,當失敗的時候,都應該輸出有用的信息。

變量命名

在 Go 中的變量名應該短而不是長。對於空間有限的局部變量尤爲如此。更喜歡 c 表示行數,喜歡 i 表示 slice 的索引。

基本的規則是:進一步聲明,一個名字被使用,這個名字必須描述更多的信息。對於一個方法 receiver,一個或兩個字母是合適的。普通的變量好比 loop indices 和 readers 能夠是單個字母(i, r)。更不尋常的事情和全局變量須要更具描述性的名稱。

相關文章
相關標籤/搜索