Go 語言中常見 100 問題 - 創建通用工具包

創建 util 包不是一種好的做法

本文將討論 Go 語言中一種常見的不好的實踐:創建 utils、common 和 base 等共享包。首先分析這種做法存在的問題,然後討論如何改進。

下面是一個受 Go 官方博客啓發構造的例子,實現一個集合數據結構。在 Go 語言中完成該功能的慣用方法是通過 map[K]struct{} 類型來處理,K 是 map 中允許的任何類型作爲鍵,而值是 struct{} 類型, 表示我們對值不關心。實現代碼如下,在 util 包中提供了兩個對外的函數。

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

客戶端的調用方法如下:

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

上述代碼的問題是包名 util 沒有任何意義,我們可以稱它爲 common、shared 或 base, 但它仍是一個毫無意義的名稱, 無法提供任何關於包的有意義信息。我們應該創建一個富有表現力的包名稱,而不是通用工具包(util),例如可以命名爲 stringset.

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

上面的程序去掉了 NewStringSet 和 SortStringSet 的後綴,分別變成了 New 和 Sort. 客戶端調用代碼變成下面的樣子。

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

「NOTE: 在 Go 語言中常見 100 問題 -#12 Project misorganization 中,討論了包的粒度問題,提到了應該避免有幾十個包含一兩個文件的小包。然而這種小包的思想沒有問題,如果一個小的代碼組具有很高的內聚性並且不真正屬於其他地方,將它組織到一個特定的包中是可以接受的。也就是說包的粒度沒有嚴格規定,找到一個平衡點即可。」

我們可以對上面的程序做進一步封裝,創建一個特定的類型並將 Sort 作爲對外提供的方法,而不是一個對外公開的函數。

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

經過上面的重構,使得客戶端調用起來更加簡單,只有一個對 stringset 包的引用。

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

通過上面小的重構,去掉了無意義的包名,對外提供了一個富有表現力的接口。正如 Dave Cheney(Go 項目組成員) 所說,我們應該合理地找到處理常用程序邏輯的實用程序包。例如,如果有一個客戶端和一個服務端包,應該把公共類型放在哪裏呢?在這種情況下,也許一種解決方法是將客戶端、服務端和公共代碼組合放到一個包中。

程序包的命名是應用程序設計的一個關鍵點,我們應該對此保持謹慎。創建沒有意義名稱的共享包不是一種好的設計,像 utils、common 或 base 包名稱。此外,注意一點,以包提供的內容而不是包含的內容命名包是增加其表現力的有效方法。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/W6JFmMx3qAVShwgt4F2iEA