Go 項目文件命名規範是什麼?
計算機科學中只有兩件難事:緩存失效和命名。—— 菲爾 · 卡爾頓(Phil Karlton)。
在編程世界中,選擇正確的命名約定是打開可讀和可維護代碼大門的重要途徑。在使用 Go 語言開發大型項目時,文件命名是構建清晰項目結構的關鍵一環,一個合理的文件命名規範不僅能提高開發效率,還能降低團隊協作中的溝通成本。
在這篇博文中,我們將深入探討 Go 中命名的最佳實踐。
在探討 Go 文件命名規範之前,我們有必要先來了解下 Go 項目中的目錄和包的命名規範是什麼。
目錄名
關於 Go 目錄命名規範,在網上搜索相關資料,基本能找到如下兩條共識:
-
全小寫單詞。例如
cmd
、internal
。 -
必要時可以使用中劃線分隔。例如
kube-scheduler
、kube-controller-manager
。
項目本身也是一個目錄,所以項目名也遵循這兩條規範。
其實我們可以借鑑流行的 Go 項目,參考這些優秀的項目是如何對目錄進行命名的。比如 Kubernetes 項目就非常具有參考價值。
如下是 Kubernetes 項目 /cmd
目錄部分截圖:
K8s /cmd
可以發現,Kubernetes 項目目錄名都採用全小寫單詞,並且必要時可以使用中劃線分隔。
不過,值得注意的是,有些目錄名並沒有使用中劃線進行分隔,而是直接將多個單詞連在了一起。所以何時使用中劃線是一個選擇問題,而不是固定的強制規範。
我們再看下 Kubernetes 項目 /pkg
目錄部分截圖:
K8s /pkg
可以發現,/pkg
目錄下所有目錄名都沒有使用中劃線分隔。有意思的是,甚至 kubeapiserver
也連在了一起,回看上面 /cmd
目錄中的寫法是 kube-apiserver
。看來 Kubernetes 項目不同目錄有着各自的命名規範。
針對這點,我的理解是:/cmd/kube-apiserver
,作爲項目中組件的入口文件目錄,使用中劃線分隔可以起到見名知意的效果;而 /pkg/kubeapiserver
作爲包路徑不使用中劃線分隔,是爲了方便包的導入。
如下列舉幾個目錄名正反示例。
正確示例:
cmd
internal
pkg
task
kube-scheduler
kube-controller-manager
反向示例:
CMD
kubeScheduler
KubeScheduler
kube_scheduler
不過凡事總有特例,我之前寫過一篇文章《如何設計一個優秀的 Go Web 項目目錄結構》,裏面介紹了一個 Go 項目的標準佈局。其中包含一個外部輔助工具目錄 third_party
,用來存放 fork 的代碼和其他第三方工具。這裏 third_party
就採用了下劃線命名,這種算是約定俗成的命名,就無需刻意遵循目錄命名規範了。
另外,關於項目本身的的命名,其實還有一些額外的規範:
-
可以是項目功能的描述。例如
userapi
、redis-go
、go-swagger
。 -
也可以是一個代號(如神話人物的名字、希臘語、遊戲角色等)(公司的基礎組件、開源項目,都是比較適合採用代號命名的項目)。例如
kratos
、kubernetes
。 -
項目名要儘量避免重複,如果可能重複要添加必要的前綴或者後綴做區分。例如
ai-user
、ai-infra
。
如下列舉幾個項目名正反示例。
正確示例:
user
userapi
redis-go
kubernetes
反向示例:
user_api
Product
AIInfra
AiInfra
以下是我個人的一些建議:
-
儘量使用單數命名(不過對於約定俗稱的名稱比如
configs
可以存在特例)。 -
儘量不要使用中劃線,而採用簡短的單詞。
-
單詞長度儘量控制在 3 個單詞以內。
包名
接下來,我們再一起探究下 Go 項目中包名的命名規範是什麼。
幸運的是,對於包名,Go 官方博客給出了參考建議,也是最爲權威的規範。
在 Package names 這篇 Go 官方博文中,給出了幾條好的包命名原則:
-
好的包名應該簡短而清晰的,具有描述性。例如
bufio
(帶緩衝的I/O
)比buf
或buffer
更好,因爲它既簡短又具有描述性。 -
小寫單一單詞,簡短名詞,避免冗餘。避免蛇形命名(
under_scores
)或駝峯命名(mixedCaps
)。例如time
、list
、http
。 -
明智而謹慎的使用縮寫,如果縮寫包名稱會使其產生歧義或不清楚,請不要這樣做。例如
strconv
(string conversion)、syscall
(system call)、fmt
(formatted I/O)。 -
不要從用戶那裏竊取好名字,避免給包起一個在客戶端代碼中常用的名字。例如,帶緩衝的
I/O
包名叫bufio
,而不是buf
,因爲buf
是表示緩衝區的一個好的變量名,常會出現在客戶端程序代碼中。 -
包名以及包所在的目錄名,不要使用複數。例如可以是
net/url
,而不應是net/urls
。 -
不要用
common
、util
、shared
或者lib
這類過於寬泛且無意義的包名。記住,一個包應該只有一個目的,只有一個責任。 -
避免不必要的包名衝突,不使用常用名或標準庫作爲包名。
-
不同目錄中的包也可以具有相同的名稱。例如
runtime/pprof
和net/http/pprof
不會產生歧義,客戶端代碼在導入包時可以重命名(當重命名導入的包時,本地名稱同樣應遵循與包名稱相同的指導原則 (lower case, no under_scores or mixedCaps))。 -
按照慣例,包路徑的最後一個元素是包名,如果不一致,會對代碼閱讀者產生困惑。例如
import golang.org/x/time/rate
,包名爲rate
。
Go Team 成員 David Crawshaw 在 2014 Google I/O talk 中也對包命名規範給出了建議:
-
保持包名簡短而有意義。
-
不要使用下劃線,它們會使包名變長。例如採用
suffixarray
而不是suffix_array
。 -
包的名稱是它的類型名和函數名的一部分。例如
bytes
包下存在Buffer
結構體,本身而言Buffer
存在二義性,但客戶端用戶看到的是buf := new(bytes.Buffer)
,即包名 + 類型名,解決了二義性的問題。
同目錄名規範一樣,包名也存在例外的情況:
-
例如使用代碼生成工具生成的代碼包名稱可能會存在下劃線,個人建議是在導入時將其重命名爲適合在 Go 代碼中使用的名稱。
-
以
_test
結尾的包名是測試代碼包。
如下列舉幾個包名正反示例。
正確示例:
controller
stringset
tabwriter
反例:
MyUtil
util
time // 與標準庫重名
tabWriter
TabWriter
tab_writer
文件名
前文分別介紹了 Go 項目中目錄名和包名的命名規範,現在終於可以探討 Go 文件的命名規範了,這也是本文的重頭戲。
不過,對於 Go 文件的命名規範,即沒有 Go 團隊的官方博客作爲參考,也沒有著名的 Gopher 站出來分享 Go 文件到底改如何命名。
這是一個少有人特意提及的規範項,不過我在 Go 項目的 GitHub 倉庫中找到了一條討論這個問題的 issue
doc: filename conventions。
雖然不少人蔘與了討論,但遺憾的是,這個問題現在仍然沒有定論。不過,這也正是此問題值得探討的原因,也是本文的意義所在。
其實遇到規範相關問題,我們最先想到的,應該就是參考 Go 源碼本身。很不巧,針對 Go 文件的命名規範問題,Go 源碼做的也不夠好。
正如 doc: filename conventions 問題 issue
提問者列舉的幾個 Go 文件名示例中,存在很大差異:
-
file.extension.go
風格。例如cmd/internal/obj/arm/a.out.go
、cmd/internal/obj/arm64/a.out.go
。 -
snake_case.go
風格。例如cmd/internal/obj/abi_string.go
、internal/syscall/unix/at_sysnum_linux.go
。 -
lowercase.go
風格。例如cmd/compile/internal/ssa/loopreschedchecks.go
、runtime/mksizeclasses.go
。 -
CamelCase.go
風格。例如cmd/compile/internal/ssa/gen/AMD64Ops.go
、cmd/compile/internal/ssa/gen/WasmOps.go
。
這就比較有意思了 😁。
在 issue
中點贊最多的是 peterbourgon
的評論:
There is a de facto standard for Go source file names: all lowercase with underscore separation when necessary, i.e. snake case. (The exceptions are exceptions.) I'd appreciate having it as a documented standard, too, to answer questions like @carnott-snap notes, especially among junior Gophers. In my opinion it needn't be so formal as entering Effective Go, a short new section on CodeReviewComments is more than sufficient.
這個回答的核心是:all lowercase with underscore separation when necessary, i.e. snake case. (The exceptions are exceptions.)
。
即大家達成了三點共識:
-
所有文件名採用小寫單詞
lowercase.go
。例如mksizeclasses.go
。 -
必要時用下劃線分隔,即蛇形命名法
snake_case.go
。例如abi_string.go
。 -
例外情況除外。
前面兩條好理解,例外情況都有哪些?我總結大概有如下幾種:
-
以
_test.go
結尾的測試文件(僅供go test
編譯和運行)。 -
以
.
或_
開頭的 “隱藏文件”(將被go tool
忽略,如果你使用 GoLand 創建一個.a.go
文件,GoLand 會提示'.a.go' is ignored by the build tool since its name starts with '.'
)。 -
具有
OS
和ARCH
特定後綴的文件會遵守特定的約束。例如name_linux.go
僅在 Linux 上構建,name_amd64.go
僅在amd64
上構建(這與在文件頂部的//+build amd64
具有相同作用)。
雖然很多人達成了上面三點共識,但其實文件命名和目錄命名規範一樣,存在 “灰色地帶” —— 必要時
用下劃線分隔。
到底何時是必要的時候?
這也是最不統一的一點規範,我發現很多人推薦,在名稱出現多個單詞時採用下劃線分隔。例如 task_status.go
、web_shell.go
。
但還有一小部分人,更推崇不採用下劃線的命名方式,例如 taskstatus.go
、webshell.go
。
而我個人的偏好則更傾向於後者。如果單詞數量爲 2 個,我會傾向於不使用下劃線分隔,例如 taskstatus.go
;如果單詞數量爲 3~4 個,則傾向於使用下劃線分隔,例如 import_known_versions.go
、default_storage_factory_builder.go
。
但最好的方案,仍然是儘量用簡短有意義的單個單詞或縮寫作爲文件名。
NOTE: 我傾向於不使用下劃線分隔的幾點理由:
與包名對齊。
kubernetes
也經常這麼幹。你看,
testdata
就沒有使用下劃線分隔 😄。... 想到了再說 :)。
如下列舉幾個文件名正反示例。
正確示例:
router.go
middleware.go
webshell.go
反面示例:
routers.go // 複數
fooBar.go
Service.go
總結
計算機科學中只有兩件難事:緩存失效和命名。本文探討的是後者。
Go 語言雖然是後起之秀,但在項目佈局、命名規範等的確沒有一個統一的標準。不過社區還是給出了一些共識性建議:
-
採用
lowercase
,而非camelCase
或PascalCase
。 -
必要時,目錄使用
kebab-case
,文件使用snake_case
。 -
lowercase
以及kebab-case
/snake_case
的選擇存在模糊的 “灰色地帶”。
希望本文能對你有所啓發。
延伸閱讀
-
The Go Blog —— Package names: https://go.dev/blog/package-names
-
Effective Go —— Package names: https://go.dev/doc/effective_go#names
-
Organizing Go code: https://go.dev/talks/2014/organizeio.slide#6
-
Naming Conventions in Golang: https://www.mohitkhare.com/blog/go-naming-conventions/
-
Module naming conventions: https://www.reddit.com/r/golang/comments/rowff0/module_naming_conventions/
-
9 Golang Name Conventions Gophers should follow!: https://blog.devgenius.io/golang-name-convention-gophers-should-follow-e4397fba5dce
-
Go Style Decisions: https://google.github.io/styleguide/go/decisions.html#naming
-
What are conventions for filenames in Go?: https://stackoverflow.com/questions/25161774/what-are-conventions-for-filenames-in-go
-
What is the correct convention for giving filenames in go: https://www.reddit.com/r/golang/comments/16frdsi/what_is_the_correct_convention_for_giving/
-
doc: filename conventions #36060: https://github.com/golang/go/issues/36060
-
如何設計一個優秀的 Go Web 項目目錄結構: https://jianghushinian.cn/2023/02/25/how-to-design-a-good-go-web-project-directory-structure/
-
go: https://github.com/golang/go
-
Kubernetes: https://github.com/kubernetes/kubernetes
-
Kratos: https://github.com/go-kratos/kratos
聯繫我
-
公衆號:Go 編程世界
-
微信:jianghushinian
-
郵箱:jianghushinian007@outlook.com
-
博客:https://jianghushinian.cn
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/reVRrXYexMB6c8RChFW2Lg