Go 部落格

封裝名稱

Sameer Ajmani
2015 年 2 月 4 日

簡介

Go 程式碼會整理成封裝。封裝內部的程式碼可以參考任何在封裝內定義的識別符號 (名稱),而封裝的用戶端只能參考封裝內外匯的類型、函式、常數和變數。此類參考通常會包含封裝名稱為開頭:foo.Bar 會參考引進名為 foo 的封裝中的 Bar 外部名稱。

好的封裝名稱可以讓程式碼更好。封裝名稱會提供其內容的背景,讓用戶端更容易了解封裝的目的及使用方式。隨著封裝的演變,名稱也能協助封裝維護人員決定何者屬於封裝,何者不屬於封裝。命名良好的封裝可以讓人更容易找到所需的程式碼。

實用 Go 提供有關封裝、類型、函式和變數的命名 指南。本文針對該討論進行延伸,並調查標準程式庫中的名稱。它也討論不好的封裝名稱,以及如何修正這些問題。

封裝名稱

好的封裝名稱應該是簡潔且清楚。它們是小寫,沒有 底線大小寫混合。它們通常是簡單的名詞,例如:

  • time (提供測量和顯示時間的功能)
  • list (實作雙向連結清單)
  • http(提供 HTTP client 和服務器實作)

其他語言中常見的命名方式在 Go 程式中可能不符慣例。以下列出兩個可能是其他語言中良好的命名方式,但不太適合用於 Go 的範例:

  • computeServiceClient
  • priority_queue

Go 套件可能匯出多個型別與函式。例如,一個compute 套件可能會匯出一个Client 型別和使用服務的方法,以及在數個 client 上分割運算作業的方法。

適當縮寫。當縮寫對程式設計人員來說很熟悉時,套件名稱可以縮寫。廣泛使用的套件通常會有簡短的名稱。

  • strconv(字串轉換)
  • syscall(系統呼叫)
  • fmt(格式化 I/O)

另一方面,如果縮寫套件名稱會使它含糊不清,那就不要這麼做。

不要使用使用者常用的好名稱。避免將套件命名為 client 程式碼中常用的名稱。例如,暫存 I/O 套件會命名為 bufio,而不是 buf,因為 buf 是緩衝區的良好變數名稱。

命名套件內容

套件名稱與其內容名稱是相關聯的,因為 client 程式碼會同時使用它們。設計套件時,請採用 client 的觀點。

避免重複。由於 client 程式碼在參照套件內容時會使用套件名稱作為字首,這些內容的名稱不需要重複套件名稱。http 套件提供的 HTTP 伺服器會命名為 Server,而不是 HTTPServer。client 程式碼會將此型別參照為 http.Server,因此不會產生歧義。

簡化函式名稱。當套件 pkg 中的某個函式回傳型別為 pkg.Pkg(或 *pkg.Pkg)時,函式名稱通常可以省略型別名稱而不會造成混淆。

start := time.Now()                                  // start is a time.Time
t, err := time.Parse(time.Kitchen, "6:06PM")         // t is a time.Time
ctx = context.WithTimeout(ctx, 10*time.Millisecond)  // ctx is a context.Context
ip, ok := userip.FromContext(ctx)                    // ip is a net.IP

套件 pkg 中一個名為 New 的函式會回傳型別為 pkg.Pkg 的值。這是使用該型別的 client 程式碼的標準切入點。

 q := list.New()  // q is a *list.List

當函式回傳型別為 pkg.T 時(其中 T 不為 Pkg),函式名稱可以包含 T,以使 client 程式碼易於理解。一個常見的情況是套件包含多個 New 類型的函式。

d, err := time.ParseDuration("10s")  // d is a time.Duration
elapsed := time.Since(start)         // elapsed is a time.Duration
ticker := time.NewTicker(d)          // ticker is a *time.Ticker
timer := time.NewTimer(d)            // timer is a *time.Timer

不同套件中的型別可以有相同的名稱,因為從 client 的觀點來看,這些名稱是由套件名稱區分的。例如,標準程式庫包括多個名為 Reader 的型別,包含 jpeg.Readerbufio.Readercsv.Reader。各個套件名稱與 Reader 相符,產生良好的型別名稱。

如果您想不出一個有意義的套件內容前綴作為套件名稱,套件抽象界線可能出了問題。撰寫程式碼,以 client 的方式使用您的套件,如果結果不佳,請重新建構您的套件。這種方法產生的套件將更容易讓 client 理解,也更容易讓套件開發人員維護。

套件路徑

Go 程式碼套件擁有名稱和路徑。套件名稱會在原始檔的 package 陳述式中指定; 呼叫程式碼使用它作為套件外連名稱的前綴,呼叫程式碼在套件過程中使用套件路徑。照慣例,套件路徑的最後一個元素會是套件名稱

import (
    "context"                // package context
    "fmt"                    // package fmt
    "golang.org/x/time/rate" // package rate
    "os/exec"                // package exec
)

建置工具將套件路徑對應至目錄。go 工具使用 GOPATH 環境變數,在目錄 $GOPATH/src/github.com/user/hello 中,找出路徑 "github.com/user/hello" 的原始檔。(當然,這個情況應該不陌生,但對於套件的術語和結構清楚了解很重要。)

目錄。 標準函式庫使用 cryptocontainerencodingimage 等目錄,依據相關協定和演算法整理套件。目錄中各個套件之間並沒有一定關係; 目錄只提供一種排列檔案的方法。任何套件都可以匯入任何其他套件,前提是匯入時不會產生循環。

就像不同套件的類型可在不造成模糊情況下擁有相同名稱一樣,不同目錄的套件也可以擁有相同名稱。舉例來說,runtime/pprof 會提供 pprof 設定工具預期的格式設定檔資料,而 net/http/pprof 則提供 HTTP 端點,以這種格式呈現設定檔資料。呼叫程式碼使用套件路徑來匯入套件,因此不會產生混淆。如果原始檔需要匯入兩個 pprof 套件,它可以重新命名一個或兩個本機。重新命名已匯入套件時,本機名稱應該遵循與套件名稱相同的準則(小寫、不使用 下底線混合大小寫)。

糟糕的套件名稱

糟糕的套件名稱會讓程式碼更難以瀏覽及維護。以下是辨識及修正糟糕名稱的一些準則。

避免使用沒有意義的套件名稱。 名稱為 utilcommonmisc 的套件無法讓呼叫方理解套件中含有哪些內容。這使得呼叫方難以使用套件,維護人員也更難維持套件的重點。時日一久,這些套件會累積依賴項,可能會顯著且不必要地減緩編譯速度,特別是在大型程式中,而且由於這種套件名稱具有通用性,因此與呼叫方程式碼匯入的其他套件發生衝突的可能性較高,迫使呼叫方想出名稱予以區別。

拆解通用的套件。要修復此類套件,請尋找具有共用名稱元素的類型和函數,並將它們抽取到自己的套件中。例如,如果您有

package util
func NewStringSet(...string) map[string]bool {...}
func SortStringSet(map[string]bool) []string {...}

則用戶端程式碼看似

set := util.NewStringSet("c", "a", "b")
fmt.Println(util.SortStringSet(set))

將這些函數從 util 抽取到新套件,選擇與內容相符的名稱

package stringset
func New(...string) map[string]bool {...}
func Sort(map[string]bool) []string {...}

則用戶端程式碼變為

set := stringset.New("c", "a", "b")
fmt.Println(stringset.Sort(set))

一旦您進行此變更,更易見如何改善新套件

package stringset
type Set map[string]bool
func New(...string) Set {...}
func (s Set) Sort() []string {...}

還能產生更簡單的用戶端程式碼

set := stringset.New("c", "a", "b")
fmt.Println(set.Sort())

套件的名稱是其設計的關鍵部分。致力於從您的專案中消除毫無意義的套件名稱。

不要為所有 API 使用單一套件。很多有良善意圖的程式設計師會將其程式所公開的所有介面放入名為 apitypesinterfaces 的單一套件中,認為這樣可以更輕易地找到其程式庫中的進入點。這是個錯誤的做法。此類套件會遭遇和 utilcommon 名稱的套件相同的問題,會不斷擴張,無法提供使用者指引、會累積相依性,並會與其他匯入衝突。將它們拆開,或許可以使用目錄將公開套件與實做分開。

避免不必要的套件名稱衝突。雖然目錄中的不同套件可能具有相同名稱,但頻繁一起使用的套件應具有明確不同的名稱。這能減少混淆,以及用戶端程式碼中進行區域性重新命名的需求。基於相同的原因,避免使用與 iohttp 等熱門標準套件相同的名稱。

結論

套件名稱是 Go 程式中完善命名的核心。花些時間選擇良好的套件名稱,並妥善組織您的程式碼。這能協助用戶端了解及使用您的套件,並能協助維護人員優雅地擴充套件。

進一步閱讀

下一篇:Go 中可測試的範例
上一篇:錯誤為值
部落格索引