自從我開始用 Go 寫代碼以來,如何組織好代碼並用好 package 關鍵字對我來說一直是個迷樣的難題。package 關鍵字類似於 C# 中的命名空間,但是它的約定卻是將 package 名字與目錄結構綁定在一起。Go 語言有一個網頁試圖解釋如何編寫 Go 代碼。http://golang.org/doc/code.html當我開始用 Go 編程時,這是我最開始讀的資料之一。可能因為之前一直在 Visual Studio 中工作,代碼被解決方案和項目打包的很好,這個文檔中的內容對當時的我來說,完全沒法讀懂。基於檔案系統的目錄來工作曾讓我認為這是個瘋狂的想法。但現在我喜歡上這種簡單的方式了,不過可能需要花上一段時間你才會發覺這個方案的合理之處。“如何編寫 Go 代碼”從工作空間的概念講起。把這個理解為你的項目的根目錄。如果你使用Visual Studio,那麼它應該是解決方案或者專案檔所在的地方。然後在你的工作空間裡面,你需要建立一個名為src的子目錄。這個目錄是必須的,這樣 Go 的工具才能正確運行。在 src 目錄裡你可以按照個人喜好自由的組織你的代碼。但是你需要瞭解 Go 團隊為包和原始碼制定的約定,不然你可能要重構你的程式碼。在我的機器上,我建立了一個工作空間叫 Test ,在其下建立了必要的 src 子目錄。這是建立項目的第一步。![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/package-work/Screen+Shot+2013-07-28+at+10.03.44+AM.png)然後在 LiteIDE 中開啟Test目錄(也就是我的工作空間),然後建立如下的子目錄以及空的 Go 源檔案。首先,為我們建立的應用建立一個子目錄。 main 函數所在的檔案夾名稱就是編譯後的可執行檔的名稱。在我們這個項目中,main.go 包含了main函數,並且位於myprogram目錄下。這意味著我們的可執行檔名就叫myprogram。其它 src 目錄下的子目錄包含了項目中的包。按照約定目錄的名稱就是這個目錄下源檔案所屬的包的名稱,在我這個項目中新的包命名為 samplepkg 和 subpkg ,源檔案的名稱可以自由命名。下一步,建立好同名的包檔案夾和空的 Go 源檔案。![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/package-work/Screen+Shot+2013-07-28+at+10.10.42+AM.png)如果你不把工作空間所屬檔案夾加入 GOPATH 我們會碰到一些問題。![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/package-work/Screen+Shot+2013-07-28+at+10.07.09+AM.png)我花了點時間才意識到自訂檔案夾( Custom Directory )是一個文字框,所以你可以直接編輯那些檔案夾的值,系統的 GOPATH 是唯讀。Go 的設計者在命名他們的包和源檔案時已經做了一些事情。所有的檔案和目錄的名稱都是小寫,並且目錄名不需要用底線將單詞分隔開,所有包的名字與目錄相同,一個目錄下的所有源檔案屬於與目錄同名的包中。看看 Go 源碼目錄中的一些標準庫包:![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/package-work/Screen+Shot+2013-07-28+at+10.09.25+AM.png)`bufio` 和 `builtin` 的目錄是目錄命名規範最好的例子。它們其實也可能被命名為 `buf_io` 和 `built_in`。再看看 Go 源碼目錄中源檔案的名字。注意到有些檔案的名字中使用了底線。當檔案包含測試代碼或者特定為某種平台使用時,就需要使用底線。![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/package-work/Screen+Shot+2013-07-28+at+10.17.49+AM.png)一個不常用的約定是,將檔案命名為目錄的名字。在 bufio 包中是遵守了這個約定的,但是這是一個不常被遵循的約定。在 fmt 包中你會發現並沒有一個叫 fmt.go 的源檔案。我個人也喜歡把源檔案的命名和目錄的命名區分開來。![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/package-work/Screen+Shot+2013-07-28+at+10.20.36+AM.png)最後,開啟 doc.go,format.go,print.go 和 scan.go,它們都在 fmt 包中被聲明。讓我們看看sample。go的代碼:```gopackage samplepkgimport ( "fmt")type Sample struct { Name string}func New(name string) (sample * Sample) { return &Sample{ Name: name, }}func (sample * Sample) Print() { fmt.Println("Sample Name:", sample.Name)}```這段代碼沒啥用處,但是卻可以讓我們看到兩個重要的約定。首先,注意這個包的名稱與目錄的名稱相同。第二,有一個叫 New 的函數。在 Go 的約定中,用於建立一個核心類型或者給應用開發人員使用的不同類型的函數就命名為 New。我們看看在 log.go,bufio.go 和 cypto.go 檔案中 New 函數是如何定義和實現的。```golog.go// New creates a new Logger. The out variable sets the// destination to which log data will be written.// The prefix appears at the beginning of each generated log line.// The flag argument defines the logging properties.func New(out io.Writer, prefix string, flag int) * Logger { return &Logger{out: out, prefix: prefix, flag: flag}}bufio.go// NewReader returns a new Reader whose buffer has the default size.func NewReader(rd io.Reader) * Reader { return NewReaderSize(rd, defaultBufSize)}crypto.go// New returns a new hash.Hash calculating the given hash function. New panics// if the hash function is not linked into the binary.func (h Hash) New() hash.Hash { if h > 0 && h < maxHash { f := hashes[h] if f != nil { return f() } } panic("crypto: requested hash function is unavailable")}```因為每個包起到了命名空間的作用,每個包可以有它們自己版本的 New 函數實現。在 bufio.go 中可以建立多種類型,所以並沒有一個單獨的 New 函數,你可以看到類似 NewReader 和 NewWriter 這樣的函數。再看看 sub.go 的代碼:```gopackage subpkgimport ( "fmt")type Sub struct { Name string}func New(name string) (sub * Sub) { return &Sub{ Name: name, }}func (sub * Sub) Print() { fmt.Println("Sub Name:", sub.Name)}```代碼是基本相同的,除了我們的核心類型改名成了 Sub 。包的名稱與子目錄名相同,並且 New 返回一個 Sub 類型的引用。現在我們可以使用這個已經定義好,並且實現好了的包了。再看看 main.go 中的代碼:```gopackage mainimport ( "samplepkg" "samplepkg/subpkg")func main() { sample := samplepkg.New("Test Sample Package") sample.Print() sub := subpkg.New("Test Sub Package") sub.Print()}```因為我們的 GOPATH 指向了工作空間目錄,這個項目中是 /User/bill/Spaces/Test,我們的 import 指令就是從這個目錄開始引用其他包的。這裡我們引用了目前的目錄結構下兩個包![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/package-work/Screen+Shot+2013-07-28+at+10.23.25+AM.png)接下來,我們分別調用每個包中的 New 函數,並建立對應的變數。現在編譯並且運行程式,你可以看到可執行檔就叫 myprogram。一旦你的程式已經準備分發了,你可以運行 install 命名。install 命令會在工作空間中建立 bin 和 pkg 檔案夾。注意最終的執行檔案放在 bin 檔案夾下。編譯好的包放在 pkg 檔案夾下,這個目錄下建立了一個目標架構的檔案夾,並且把源碼目錄下的目錄結構都複製一份在此檔案夾下。這些編譯好的包都存在,於是go工具可以避免不必要的重新編譯。![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/package-work/Screen+Shot+2013-07-28+at+10.24.16+AM.png)在“如何編寫Go代碼”文章中最後部分講的問題是,Go 工具在以後編譯代碼時會忽略所有的 .a 檔案。沒有源檔案你沒法編譯你的應用。我還沒有找到任何文檔解釋這些 .a 檔案如何直接參与 Go 程式構建的。如果有人知道還請不吝賜教。最後,我們最好遵循 Go 設計者制定的這些約定,讀 Go 的源碼是最好的瞭解這些約定的方法。有很多人為開源社區寫代碼,如果我們都遵循相同的約定,我們可以提高代碼的相容性和可讀性。當有疑問時 在 /usr/local/go/src/pkg 中挖掘答案吧。一如既往的,我希望這篇文章能協助你更好的理解 Go 語言。
via: https://www.ardanlabs.com/blog/2013/07/how-packages-work-in-go-language.html
作者:William Kennedy 譯者:MoodWu 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
263 次點擊