Go語言:包的風格指南

Go 語言也有自己的命名與代碼組織規則。漂亮的代碼,佈局清晰、易讀易懂,就像是設計嚴謹的 API 一樣。拿到代碼,用戶首先看到和接觸的就是佈局、命名還有包的結構。

這篇文章不是爲了給大家設立硬性的規定,而是用實踐引導大家形成自己的規則。因爲具體問題要具體分析,通過自己的判斷才能挑選出最恰當的規則。

所有的 Go 代碼都是以包的形式組織起來的。Go 中的包就是目錄或文件夾裏面包括一個或者多個以 .go 結尾的文件。用目錄或文件夾這樣的方式來管理代碼,與電腦管理目錄或文件夾是一樣一樣的。

所有的 Go 代碼都是放在包裏的,且只能是通過包來進行訪問。理解並且建立良好的包的習慣,可以幫助寫出高效的代碼。


包的組織

我們先聊聊如何組織 Go 代碼,解釋一下定位 Go 包的規範。

使用多個文件

一個包就是一個或者多個文件的目錄。先把代碼切分成符合邏輯且易讀的多個文件。

例如,根據文件處理 HTTP 的內容不一樣,一個 HTTP 包可以被切分成多個文件。在下面這個例子中,一個 HTTP 包被切成下列文件:頭部類型定義和代碼,cookie 類型定義加代碼,還有實際 HTTP 功能的實現和包說明文檔。

- doc.go       // 包說明文檔
- headers.go   // HTTP 頭部類型定義和代碼
- cookies.go   // HTTP cookie 類型定義和代碼
- http.go      // HTTP 客戶端實現,請求和返回類型,等等

聚合類型定義

首要規則是,把類型定義儘量都聚合到他們被引用的地方。這讓代碼的維護者(不僅僅侷限於代碼的原作者)更易於找到類型的定義。比如,頭結構體類型最好就是放在 headers.go 文件當中。

$ cat headers.go
package http

// Header 表示一個 Http 頭部結構體定義
type Header struct {...}

雖然 Go 語言本身並沒有嚴格地要求你必須在文件哪個部分定義類型,但是把核心類型的定義都放在文件的最上面是沒錯的。

根據功能進行安排

在其他語言中,通常都是把類型定義聚合到一個包裏,叫作模型或者類別。在 Go 語言中,則是通過代碼的功能職責來進行安排的。

package models // 千萬別叫這個名字

// User 代表系統中的一個用戶
type User struct {...}

不要創建一個命名爲 models 的包,然後在裏面定義所有的實體類型。在這個例子中,User 類型應該定義在服務層的包中。

package mngtservice

// User 代表系統中的一個客戶
type User struct {...}

func UsersByQuery(ctx context.Context, q *Query) ([]*User, *Iterator, error)

func UserIDByEmail(ctx context.Context, email string) (int64, error)

優化 godoc

越早使用 godoc 越好,尤其是在初期設計包的 API 的時候。使用 godoc,你可以清楚知道自己的構思用文檔表達出來是個什麼。有時候,可視化也對設計有影響。因爲 godoc 需要放在一個獨立的包裏,所以可以慢慢地來進行優化,讓文檔越來越容易理解。執行命令 godoc -http= 來啓動本地的 godoc 文檔服務。

舉幾個例子說明一下

有些情況下,你可能沒辦法把所有相關的類型定義都寫在一個包裏。因爲這樣做可能會很繁瑣。或者你只是想發佈一個實現了單個包中通用接口的具體功能,或者這些類型是由第三方包提供的。下面給出幾個例子來說明和理解這些情況。

$ godoc cloud.google.com/go/datastore
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error)
...

NewClient 這個方法裏有一個結構參數 option.ClientOptions,但是這個結構的定義既不在 datastore 包裏,也不在提供所有 option 類型的 option 包裏。

$ godoc google.golang.org/extraoption
func WithCustomValue(v string) option.ClientOption
...

如果你設計的 API 需要引入很多非標準庫的包,那麼,添加 Go 示例 通常會很有用,以便爲用戶提供一些工作代碼。

樣例可以提高藏地較深的包的暴光率。例如,datastore.NewCLient 這樣的結構需要引用額外的 option 包。提供樣例,就可以讓用戶知道,還有一個 option 包。

不要在 main 文件中導出

標識符可以被 導出,以允許從外部包來使用它。

main 包是不能被導入的,所以從 main 包中導出標記符是沒有必要的。如果你要把包編譯成二進制文件,就不要從 main 包中導出標記符。

這條規則也有例外,那就是 main 包被編譯成了 .so 文件、.a 文件或者 Go 插件。在這種情況下,Go 代碼被其他語言通過 cgo 的導出功能 使用,那標識符的導出就是必要的了。


包的命名

包的名字與導入路徑,都是很重要的標識,它們會告訴你這個包裏有哪些內容。按規則給包命名不僅可以提高你的代碼的質量,也間接地提高這個包的使用者的代碼水平。

只用小寫

包的名字應只用小寫。不要用下劃線式,也不要用駝峯式。Go 官方博文 關於包命名的綜合指南 中有多個不同情形的樣例。

簡短而有意義

包的名字需要簡短,但應該唯一且有意義。用戶從包的名字中就能直接理解這個包的作用。

避免泛泛的包名,例如 “common”, “util”。

import "pkgs.org/common" // 可千萬別這樣寫

避免重名,萬一用戶要同時引入並使用這兩個同名包。

如果命名上確實有困難,可能是因爲設計的代碼結構與整體構架本身就有問題。

精簡引入路徑

避免暴露自定義的倉庫結構(repository structure)給包的用戶。謹遵 GOPATH 的規定。避免在引入路徑中出現包含 src/, pkg/ 命名的路徑。

github.com/user/repo/src/httputil   // 可千萬別這麼做,不要使用 SRC !!

github.com/user/repo/gosrc/httputil // 可千萬別這麼做,不要使用 GOSRC !!

使用單數

在 Go 語言中,包的名字不要使用複數。從其他語言轉過來的程序員會覺得很彆扭,因爲在先前使用的語言中,已經形成了使用複數的習慣。給包命名的時候,用 httputil,不用 httputils!

package httputils  // 用單數,不用複數

別名也應該遵守規則

如果你在引入多個相同名字的包,你可以在本地修改這些包的名字。別名也需要遵守本文提到的規則。並沒有規則指明需要修改哪一個包的名字。如果你在修改標準庫包的名字,最好加一個前綴來做區別,畢竟是 “Go 標準庫” 中的包,比如,可以修改爲 gourlgoioutil

import (
    gourl "net/url"

    "myother.com/url"
)

強制使用虛擬 URL

go get 支持通過另外一種 URL 來獲取包,這個 URL 與包倉庫的 URL 的名字不一樣。這個不一樣的 URL 叫做虛擬 URL,需要準備一個頁面,裏面包含可被 Go 工具識別的詳細的元標籤。你可以使用虛擬 URL 通過自定義域名和路徑來提供包的服務。

例如,

$ go get cloud.google.com/go/datastore

在後臺去查看來自 https://code.googlesource.com/gocloud 的源碼,把它加到你的工作區當中去,這個工作區是定義在 $GOPATH/src/cloud.google.com/go/datastore 下面的。

假定 code.googlesource.com/gocloud 已經在包裏了,那能不能通過這個 URL 來使用這個包呢?答案是 NO,因爲開啓了強制使用虛擬 URL。

實際使用中,在包裏添加了一個引入聲明。Go 工具就不允許從任何其他路徑來引入這個包,並且會給用戶一個友好地錯誤提示。如果你沒有開啓強制使用虛擬 URL,那麼就會有出現兩個一樣的包,並且因爲不同的命名空間,它們不能放在一起使用。

package datastore // import "cloud.google.com/go/datastore"

包說明文檔

記得要給包寫說明文檔。包說明文檔最可以闡明包的功能。對於非 main 包來講,godoc 都是以 “Package {包名}” 開頭,並且附上一個描述說明。對於 main 包來講,文檔就是用來說明程序的功能的。

// Package ioutil 實現了一些輸入或輸出效用功能
package ioutil

// gops 命令會列出所有在系統中跑的進程
package main

// helloworld 樣例來展示如何使用 x 功能
package main

使用 doc.go 文件

有時候,包裏的文檔說明內容會很多,尤其是包括詳細的用法說明與指導。將包的 godoc 移到 doc.go 這個文件中去。 (參考樣例 doc.go)


via: https://rakyll.org/style-packages

作者:rakyll 譯者:chenbrooks 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

更多Go語言知識,歡迎關注微信公衆號:Go語言中文網在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章