《Go語言聖經》學習筆記 第十章 包和工具
目錄
- 包簡介
- 導入路徑
- 包聲明
- 導入聲明
- 包的匿名導入
- 包和命名
- 工具
注:學習《Go語言聖經》筆記,PDF點擊下載,建議看書。
Go語言小白學習筆記,書上的內容照搬,大佬看了勿噴,以後熟悉了會總結成自己的讀書筆記。
- 現在隨便一個小程序的實現都可能包含超過10000個函數。 然而作者一般只需要考慮其中很小的一部分和做很少的設計, 因爲絕大部分代碼都是由他人編寫的, 它們通過類似包或模塊的方式被重用。
- Go語言有超過100個的標準包( 譯註: 可以用 go list std | wc -l 命令查看標準包的具體數目) , 標準庫爲大多數的程序提供了必要的基礎構件。 在Go的社區, 有很多成熟的包被設計、 共享、 重用和改進, 目前互聯網上已經發布了非常多的Go語音開源包, 它們可以通過http://godoc.org 檢索。 在本章, 我們將演示如果使用已有的包和創建新的包。
- Go還自帶了工具箱, 裏面有很多用來簡化工作區和包管理的小工具。 在本書開始的時候, 我們已經見識過如何使用工具箱自帶的工具來下載、 構件和運行我們的演示程序了。 在本章,我們將看看這些工具的基本設計理論和嘗試更多的功能, 例如打印工作區中包的文檔和查詢相關的元數據等。 在下一章, 我們將探討探索包的單元測試用法。
1. 包簡介
- 任何包系統設計的目的都是爲了簡化大型程序的設計和維護工作, 通過將一組相關的特性放進一個獨立的單元以便於理解和更新, 在每個單元更新的同時保持和程序中其它單元的相對獨立性。 這種模塊化的特性允許每個包可以被其它的不同項目共享和重用, 在項目範圍內、甚至全球範圍統一的分發和複用。
- 每個包一般都定義了一個不同的名字空間用於它內部的每個標識符的訪問。 每個名字空間關聯到一個特定的包, 讓我們給類型、 函數等選擇簡短明瞭的名字, 這樣可以避免在我們使用它們的時候減少和其它部分名字的衝突。每個包還通過控制包內名字的可見性和是否導出來實現封裝特性。 通過限制包成員的可見性並隱藏包API的具體實現, 將允許包的維護者在不影響外部包用戶的前提下調整包的內部實現。 通過限制包內變量的可見性, 還可以強制用戶通過某些特定函數來訪問和更新內部變量, 這樣可以保證內部變量的一致性和併發時的互斥約束。
- 當我們修改了一個源文件, 我們必須重新編譯該源文件對應的包和所有依賴該包的其他包。即使是從頭構建, Go語言編譯器的編譯速度也明顯快於其它編譯語言。 Go語言的閃電般的編譯速度主要得益於三個語言特性。 第一點, 所有導入的包必須在每個文件的開頭顯式聲明,這樣的話編譯器就沒有必要讀取和分析整個源文件來判斷包的依賴關係。 第二點, 禁止包的環狀依賴, 因爲沒有循環依賴, 包的依賴關係形成一個有向無環圖, 每個包可以被獨立編譯, 而且很可能是被併發編譯。 第三點, 編譯後包的目標文件不僅僅記錄包本身的導出信息, 目標文件同時還記錄了包的依賴關係。 因此, 在編譯一個包的時候, 編譯器只需要讀取每個直接導入包的目標文件, 而不需要遍歷所有依賴的的文件( 譯註: 很多都是重複的間接依賴) 。
2. 導入路徑
- 每個包是由一個全局唯一的字符串所標識的導入路徑定位。 出現在import語句中的導入路徑也是字符串。
- 就像我們在2.6.1節提到過的, Go語言的規範並沒有指明包的導入路徑字符串的具體含義, 導入路徑的具體含義是由構建工具來解釋的。 在本章, 我們將深入討論Go語言工具箱的功能,包括大家經常使用的構建測試等功能。 當然, 也有第三方擴展的工具箱存在。 例如, Google公司內部的Go語言碼農, 他們就使用內部的多語言構建系統( 譯註: Google公司使用的是類似Bazel的構建系統, 支持多種編程語言, 目前該構件系統還不能完整支持Windows環境) ,用不同的規則來處理包名字和定位包, 用不同的規則來處理單元測試等等, 因爲這樣可以更緊密適配他們內部環境。
- 如果你計劃分享或發佈包, 那麼導入路徑最好是全球唯一的。 爲了避免衝突, 所有非標準庫包的導入路徑建議以所在組織的互聯網域名爲前綴; 而且這樣也有利於包的檢索。 例如, 上面的import語句導入了Go團隊維護的HTML解析器和一個流行的第三方維護的MySQL驅動。
3. 包聲明
-
在每個Go語音源文件的開頭都必須有包聲明語句。 包聲明語句的主要目的是確定當前包被其它包導入時默認的標識符( 也稱爲包名) 。
-
例如, math/rand包的每個源文件的開頭都包含 package rand 包聲明語句, 所以當你導入這個包, 你就可以用rand.Int、 rand.Float64類似的方式訪問包的成員。
-
通常來說, 默認的包名就是包導入路徑名的最後一段, 因此即使兩個包的導入路徑不同, 它們依然可能有一個相同的包名。 例如, math/rand包和crypto/rand包的包名都是rand。 稍後我們將看到如何同時導入兩個有相同包名的包。
-
關於默認包名一般採用導入路徑名的最後一段的約定也有三種例外情況。 第一個例外, 包對應一個可執行程序, 也就是main包, 這時候main包本身的導入路徑是無關緊要的。 名字爲main的包是給go build( §10.7.3) 構建命令一個信息, 這個包編譯完之後必須調用連接器生成一個可執行程序。
-
第二個例外, 包所在的目錄中可能有一些文件名是以test.go爲後綴的Go源文件( 譯註: 前面必須有其它的字符, 因爲以``前綴的源文件是被忽略的) , 並且這些源文件聲明的包名也是以_test爲後綴名的。 這種目錄可以包含兩種包: 一種普通包, 加一種則是測試的外部擴展包。所有以_test爲後綴包名的測試外部擴展包都由go test命令獨立編譯, 普通包和測試的外部擴展包是相互獨立的。 測試的外部擴展包一般用來避免測試代碼中的循環導入依賴, 具體細節我們將在11.2.4節中介紹。
-
第三個例外, 一些依賴版本號的管理工具會在導入路徑後追加版本號信息, 例如"gopkg.in/yaml.v2"。 這種情況下包的名字並不包含版本號後綴, 而是yaml。
4. 導入聲明
- 可以在一個Go語言源文件包聲明語句之後, 其它非導入聲明語句之前, 包含零到多個導入包聲明語句。 每個導入聲明可以單獨指定一個導入路徑, 也可以通過圓括號同時導入多個導入路徑。 下面兩個導入形式是等價的, 但是第二種形式更爲常見
- 導入的包之間可以通過添加空行來分組; 通常將來自不同組織的包獨自分組。 包的導入順序無關緊要, 但是在每個分組中一般會根據字符串順序排列。 ( gofmt和goimports工具都可以將不同分組導入的包獨立排序。 )
- 如果我們想同時導入兩個有着名字相同的包, 例如math/rand包和crypto/rand包, 那麼導入聲明必須至少爲一個同名包指定一個新的包名以避免衝突。 這叫做導入包的重命名。
- 導入包的重命名隻影響當前的源文件。 其它的源文件如果導入了相同的包, 可以用導入包原本默認的名字或重命名爲另一個完全不同的名字。
- 導入包重命名是一個有用的特性, 它不僅僅只是爲了解決名字衝突。 如果導入的一個包名很笨重, 特別是在一些自動生成的代碼中, 這時候用一個簡短名稱會更方便。 選擇用簡短名稱重命名導入包時候最好統一, 以避免包名混亂。 選擇另一個包名稱還可以幫助避免和本地普通變量名產生衝突。 例如, 如果文件中已經有了一個名爲path的變量, 那麼我們可以將"path"標準包重命名爲pathpkg。
- 每個導入聲明語句都明確指定了當前包和被導入包之間的依賴關係。 如果遇到包循環導入的情況, Go語言的構建工具將報告錯誤。
5. 包的匿名導入
- 如果只是導入一個包而並不使用導入的包將會導致一個編譯錯誤。 但是有時候我們只是想利用導入包而產生的副作用: 它會計算包級變量的初始化表達式和執行導入包的init初始化函數( §2.6.2) 。 這時候我們需要抑制“unused import”編譯錯誤, 我們可以用下劃線 _ 來重命名導入的包。 像往常一樣, 下劃線 _ 爲空白標識符, 並不能被訪問
- 這個被稱爲包的匿名導入。 它通常是用來實現一個編譯時機制, 然後通過在main主程序入口選擇性地導入附加的包。 首先, 讓我們看看如何使用該特性, 然後再看看它是如何工作的。
- 標準庫的image圖像包包含了一個 Decode 函數, 用於從 io.Reader 接口讀取數據並解碼圖像, 它調用底層註冊的圖像解碼器來完成任務, 然後返回image.Image類型的圖像。 使用 image.Decode 很容易編寫一個圖像格式的轉換工具, 讀取一種格式的圖像, 然後編碼爲另一種圖像格式:
gopl.io/ch10/jpeg
- 如果我們將 gopl.io/ch3/mandelbrot ( §3.3) 的輸出導入到這個程序的標準輸入, 它將解碼輸入的PNG格式圖像, 然後轉換爲JPEG格式的圖像輸出( 圖3.3) 。
- 要注意image/png包的匿名導入語句。 如果沒有這一行語句, 程序依然可以編譯和運行, 但是它將不能正確識別和解碼PNG格式的圖像:
- 下面的代碼演示了它的工作機制。 標準庫還提供了GIF、 PNG和JPEG等格式圖像的解碼器,用戶也可以提供自己的解碼器, 但是爲了保持程序體積較小, 很多解碼器並沒有被全部包含, 除非是明確需要支持的格式。 image.Decode函數在解碼時會依次查詢支持的格式列表。
- 每個格式驅動列表的每個入口指定了四件事情: 格式的名稱; 一個用於描述這種圖像數據開頭部分模式的字符串, 用於解碼器檢測識別; 一個Decode函數用於完成解碼圖像工作; 一個DecodeConfig函數用於解碼圖像的大小和顏色空間的信息。 每個驅動入口是通過調用image.RegisterFormat函數註冊, 一般是在每個格式包的init初始化函數中調用, 例如image/png包是這樣註冊的:
- 最終的效果是, 主程序只需要匿名導入特定圖像驅動包就可以用image.Decode解碼對應格式的圖像了。
- 數據庫包database/sql也是採用了類似的技術, 讓用戶可以根據自己需要選擇導入必要的數據庫驅動。 例如:
6. 包和命名
- 在本節中, 我們將提供一些關於Go語言獨特的包和成員命名的約定。
- 當創建一個包, 一般要用短小的包名, 但也不能太短導致難以理解。 標準庫中最常用的包有bufio、 bytes、 flag、 fmt、 http、 io、 json、 os、 sort、 sync和time等包。
- 它們的名字都簡潔明瞭。 例如, 不要將一個類似imageutil或ioutilis的通用包命名爲util, 雖然它看起來很短小。 要儘量避免包名使用可能被經常用於局部變量的名字, 這樣可能導致用戶重命名導入包, 例如前面看到的path包。
- 包名一般採用單數的形式。 標準庫的bytes、 errors和strings使用了複數形式, 這是爲了避免和預定義的類型衝突, 同樣還有go/types是爲了避免和type關鍵字衝突。
- 要避免包名有其它的含義。 例如, 2.5節中我們的溫度轉換包最初使用了temp包名, 雖然並沒有持續多久。 但這是一個糟糕的嘗試, 因爲temp幾乎是臨時變量的同義詞。 然後我們有一段時間使用了temperature作爲包名, 雖然名字並沒有表達包的真實用途。 最後我們改成了和strconv標準包類似的tempconv包名, 這個名字比之前的就好多了。
- 現在讓我們看看如何命名包的成員。 由於是通過包的導入名字引入包裏面的成員, 例如fmt.Println, 同時包含了包名和成員名信息。 因此, 我們一般並不需要關注Println的具體內容, 因爲fmt包名已經包含了這個信息。 當設計一個包的時候, 需要考慮包名和成員名兩個部分如何很好地配合。 下面有一些例子:
- 我們可以看到一些常用的命名模式。 strings包提供了和字符串相關的諸多操作:
- 字符串string本身並沒有出現在每個成員名字中。 因爲用戶會這樣引用這些成員strings.Index、 strings.Replacer等。
- 其它一些包, 可能只描述了單一的數據類型, 例如html/template和math/rand等, 只暴露一個主要的數據結構和與它相關的方法, 還有一個以New命名的函數用於創建實例。
- 這可能導致一些名字重複, 例如template.Template或rand.Rand, 這就是爲什麼這些種類的包名往往特別短的原因之一。
- 在另一個極端, 還有像net/http包那樣含有非常多的名字和種類不多的數據類型, 因爲它們都是要執行一個複雜的複合任務。 儘管有將近二十種類型和更多的函數, 但是包中最重要的成員名字卻是簡單明瞭的: Get、 Post、 Handle、 Error、 Client、 Server等。
7. 工具
- 本章剩下的部分將討論Go語言工具箱的具體功能, 包括如何下載、 格式化、 構建、 測試和安裝Go語言編寫的程序。
- Go語言的工具箱集合了一系列的功能的命令集。 它可以看作是一個包管理器( 類似於Linux中的apt和rpm工具) , 用於包的查詢、 計算的包依賴關係、 從遠程版本控制系統和下載它們等任務。 它也是一個構建系統, 計算文件的依賴關係, 然後調用編譯器、 彙編器和連接器構建程序, 雖然它故意被設計成沒有標準的make命令那麼複雜。 它也是一個單元測試和基準測試的驅動程序, 我們將在第11章討論測試話題。
- Go語言工具箱的命令有着類似“瑞士軍刀”的風格, 帶着一打子的子命令, 有一些我們經常用到, 例如get、 run、 build和fmt等。 你可以運行go或go help命令查看內置的幫助文檔, 爲了查詢方便, 我們列出了最常用的命令:
- 爲了達到零配置的設計目標, Go語言的工具箱很多地方都依賴各種約定。 例如, 根據給定的源文件的名稱, Go語言的工具可以找到源文件對應的包, 因爲每個目錄只包含了單一的包,並且到的導入路徑和工作區的目錄結構是對應的。 給定一個包的導入路徑, Go語言的工具可以找到對應的目錄中沒個實體對應的源文件。 它還可以根據導入路徑找到存儲代碼倉庫的遠程服務器的URL。
1. 工作區結構
- 對於大多數的Go語言用戶, 只需要配置一個名叫GOPATH的環境變量, 用來指定當前工作目錄即可。 當需要切換到不同工作區的時候, 只要更新GOPATH就可以了。 例如, 我們在編寫本書時將GOPATH設置爲 $HOME/gobook :
- 當你用前面介紹的命令下載本書全部的例子源碼之後, 你的當前工作區的目錄結構應該是這樣的:
- GOPATH對應的工作區目錄有三個子目錄。 其中src子目錄用於存儲源代碼。 每個包被保存在與$GOPATH/src的相對路徑爲包導入路徑的子目錄中, 例如gopl.io/ch1/helloworld相對應的路徑目錄。 我們看到, 一個GOPATH工作區的src目錄中可能有多個獨立的版本控制系統, 例如gopl.io和golang.org分別對應不同的Git倉庫。 其中pkg子目錄用於保存編譯後的包的目標文件, bin子目錄用於保存編譯後的可執行程序, 例如helloworld可執行程序。
- 第二個環境變量GOROOT用來指定Go的安裝目錄, 還有它自帶的標準庫包的位置。
- GOROOT的目錄結構和GOPATH類似, 因此存放fmt包的源代碼對應目錄應該爲$GOROOT/src/fmt。 用戶一般不需要設置GOROOT, 默認情況下Go語言安裝工具會將其設置爲安裝的目錄路徑
- 其中 go env 命令用於查看Go語音工具涉及的所有環境變量的值, 包括未設置環境變量的默認值。 GOOS環境變量用於指定目標操作系統( 例如android、 linux、 darwin或windows) ,GOARCH環境變量用於指定處理器的類型, 例如amd64、 386或arm等。 雖然GOPATH環境變量是唯一必需要設置的, 但是其它環境變量也會偶爾用到。
2. 下載包
- 使用Go語言工具箱的go命令, 不僅可以根據包導入路徑找到本地工作區的包, 甚至可以從互聯網上找到和更新包。
- 使用命令 go get 可以下載一個單一的包或者用 … 下載整個子目錄裏面的每個包。 Go語言工具箱的go命令同時計算並下載所依賴的每個包, 這也是前一個例子中golang.org/x/net/html自動出現在本地工作區目錄的原因。
- 一旦 go get 命令下載了包, 然後就是安裝包或包對應的可執行的程序。 我們將在下一節再關注它的細節, 現在只是展示整個下載過程是如何的簡單。 第一個命令是獲取golint工具, 它用於檢測Go源代碼的編程風格是否有問題。 第二個命令是用golint命令對2.6.2節的gopl.io/ch2/popcount包代碼進行編碼風格檢查。 它友好地報告了忘記了包的文檔:
- go get 命令支持當前流行的託管網站GitHub、 Bitbucket和Launchpad, 可以直接向它們的版本控制系統請求代碼。 對於其它的網站, 你可能需要指定版本控制系統的具體路徑和協議,例如 Git或Mercurial。 運行 go help importpath 獲取相關的信息。
- go get 命令獲取的代碼是真實的本地存儲倉庫, 而不僅僅只是複製源文件, 因此你依然可以使用版本管理工具比較本地代碼的變更或者切換到其它的版本。 例如golang.org/x/net包目錄對應一個Git倉庫:
- 需要注意的是導入路徑含有的網站域名和本地Git倉庫對應遠程服務地址並不相同, 真實的Git地址是go.googlesource.com。 這其實是Go語言工具的一個特性, 可以讓包用一個自定義的導入路徑, 但是真實的代碼卻是由更通用的服務提供, 例如googlesource.com或github.com。因爲頁面 https://golang.org/x/net/html 包含了如下的元數據, 它告訴Go語言的工具當前包真實的Git倉庫託管地址:
- 如果指定 -u 命令行標誌參數, go get 命令將確保所有的包和依賴的包的版本都是最新的,然後重新編譯和安裝它們。 如果不包含該標誌參數的話, 而且如果包已經在本地存在, 那麼代碼那麼將不會被自動更新。
- go get -u 命令只是簡單地保證每個包是最新版本, 如果是第一次下載包則是比較很方便的; 但是對於發佈程序則可能是不合適的, 因爲本地程序可能需要對依賴的包做精確的版本依賴管理。 通常的解決方案是使用vendor的目錄用於存儲依賴包的固定版本的源代碼, 對本地依賴的包的版本更新也是謹慎和持續可控的。 在Go1.5之前, 一般需要修改包的導入路徑,所以複製後golang.org/x/net/html導入路徑可能會變爲gopl.io/vendor/golang.org/x/net/html。最新的Go語言命令已經支持vendor特性, 但限於篇幅這裏並不討論vendor的具體細節。 不過可以通過 go help gopath 命令查看Vendor的幫助文檔。
3. 構建包
- go build 命令編譯命令行參數指定的每個包。 如果包是一個庫, 則忽略輸出結果; 這可以用於檢測包的可以正確編譯的。 如果包的名字是main, go build 將調用連接器在當前目錄創建一個可執行程序; 以導入路徑的最後一段作爲可執行程序的名字。
- 因爲每個目錄只包含一個包, 因此每個對應可執行程序或者叫Unix術語中的命令的包, 會要求放到一個獨立的目錄中。 這些目錄有時候會放在名叫cmd目錄的子目錄下面, 例如用於提供Go文檔服務的golang.org/x/tools/cmd/godoc命令就是放在cmd子目錄( §10.7.4) 。
- 每個包可以由它們的導入路徑指定, 就像前面看到的那樣, 或者用一個相對目錄的路徑知指定, 相對路徑必須以 . 或 … 開頭。 如果沒有指定參數, 那麼默認指定爲當前目錄對應的包。 下面的命令用於構建同一個包, 雖然它們的寫法各不相同:
- 或者:
- 或者:
- 但不能這樣:
- 也可以指定包的源文件列表, 這一般這隻用於構建一些小程序或做一些臨時性的實驗。 如果是main包, 將會以第一個Go源文件的基礎文件名作爲最終的可執行程序的名字。
- 特別是對於這類一次性運行的程序, 我們希望儘快的構建並運行它。 go run 命令實際上是結合了構建和運行的兩個步驟:
- 第一行的參數列表中, 第一個不是以 .go 結尾的將作爲可執行程序的參數運行。
- 默認情況下, go build 命令構建指定的包和它依賴的包, 然後丟棄除了最後的可執行文件之外所有的中間編譯結果。 依賴分析和編譯過程雖然都是很快的, 但是隨着項目增加到幾十個包和成千上萬行代碼, 依賴關係分析和編譯時間的消耗將變的可觀, 有時候可能需要幾秒種, 即使這些依賴項沒有改變。
- go install 命令和 go build 命令很相似, 但是它會保存每個包的編譯成果, 而不是將它們都丟棄。 被編譯的包會被保存到GOPATH/bin目錄。 ( 很多用戶會將$GOPATH/bin添加到可執行程序的搜索列表中。 ) 還有, go install 命令和 go build 命令都不會重新編譯沒有發生變化的包, 這可以使後續構建更快捷。 爲了方便編譯依賴的包, go build -i 命令將安裝每個目標所依賴的包。
- 因爲編譯對應不同的操作系統平臺和CPU架構, go install 命令會將編譯結果安裝到GOOS和GOARCH對應的目錄。 例如, 在Mac系統, golang.org/x/net/html包將被安裝到$GOPATH/pkg/darwin_amd64目錄下的golang.org/x/net/html.a文件。
- 針對不同操作系統或CPU的交叉構建也是很簡單的。 只需要設置好目標對應的GOOS和GOARCH, 然後運行構建命令即可。 下面交叉編譯的程序將輸出它在編譯時操作系統和CPU類型:
- gopl.io/ch10/cross
- 下面以64位和32位環境分別執行程序:
- 有些包可能需要針對不同平臺和處理器類型使用不同版本的代碼文件, 以便於處理底層的可移植性問題或提供爲一些特定代碼提供優化。 如果一個文件名包含了一個操作系統或處理器類型名字, 例如net_linux.go或asm_amd64.s, Go語言的構建工具將只在對應的平臺編譯這些文件。 還有一個特別的構建註釋註釋可以提供更多的構建過程控制。 例如, 文件中可能包含下面的註釋:
- 在包聲明和包註釋的前面, 該構建註釋參數告訴 go build 只在編譯程序對應的目標操作系統是Linux或Mac OS X時才編譯這個文件。 下面的構建註釋則表示不編譯這個文件:
- 更多細節, 可以參考go/build包的構建約束部分的文檔。
4. 包文檔
- Go語言的編碼風格鼓勵爲每個包提供良好的文檔。 包中每個導出的成員和包聲明前都應該包含目的和用法說明的註釋。
- Go語言中包文檔註釋一般是完整的句子, 第一行是包的摘要說明, 註釋後僅跟着包聲明語句。 註釋中函數的參數或其它的標識符並不需要額外的引號或其它標記註明。 例如, 下面是fmt.Fprintf的文檔註釋。
- Fprintf函數格式化的細節在fmt包文檔中描述。 如果註釋後僅跟着包聲明語句, 那註釋對應整個包的文檔。 包文檔對應的註釋只能有一個( 譯註: 其實可以有多個, 它們會組合成一個包文檔註釋) , 包註釋可以出現在任何一個源文件中。 如果包的註釋內容比較長, 一般會放到一個獨立的源文件中; fmt包註釋就有300行之多。 這個專門用於保存包文檔的源文件通常叫doc.go。
- 好的文檔並不需要面面俱到, 文檔本身應該是簡潔但可不忽略的。 事實上, Go語言的風格更喜歡簡潔的文檔, 並且文檔也是需要像代碼一樣維護的。 對於一組聲明語句, 可以用一個精煉的句子描述, 如果是顯而易見的功能則並不需要註釋。
- 在本書中, 只要空間允許, 我們之前很多包聲明都包含了註釋文檔, 但你可以從標準庫中發現很多更好的例子。 有兩個工具可以幫到你。
- 首先是 go doc 命令, 該命令打印包的聲明和每個成員的文檔註釋, 下面是整個包的文檔:
- 或者是某個具體包成員的註釋文檔:
- 或者是某個具體包的一個方法的註釋文檔:
- 該命令並不需要輸入完整的包導入路徑或正確的大小寫。 下面的命令將打印encoding/json包的 (*json.Decoder).Decode 方法的文檔:
- 第二個工具, 名字也叫godoc, 它提供可以相互交叉引用的HTML頁面, 但是包含和 godoc 命令相同以及更多的信息。 10.1節演示了time包的文檔, 11.6節將看到godoc演示可以交互的示例程序。 godoc的在線服務 https://godoc.org , 包含了成千上萬的開源包的檢索工具。
- 你也可以在自己的工作區目錄運行godoc服務。 運行下面的命令, 然後在瀏覽器查看http://localhost:8000/pkg 頁面:
5. 內部包
- 在Go語音程序中, 包的封裝機制是一個重要的特性。 沒有導出的標識符只在同一個包內部可以訪問, 而導出的標識符則是面向全宇宙都是可見的。
- 有時候, 一箇中間的狀態可能也是有用的, 對於一小部分信任的包是可見的, 但並不是對所有調用者都可見。 例如, 當我們計劃將一個大的包拆分爲很多小的更容易維護的子包, 但是我們並不想將內部的子包結構也完全暴露出去。 同時, 我們可能還希望在內部子包之間共享一些通用的處理包, 或者我們只是想實驗一個新包的還並不穩定的接口, 暫時只暴露給一些受限制的用戶使用。
- 爲了滿足這些需求, Go語言的構建工具對包含internal名字的路徑段的包導入路徑做了特殊處理。 這種包叫internal包, 一個internal包只能被和internal目錄有同一個父目錄的包所導入。 例如, net/http/internal/chunked內部包只能被net/http/httputil或net/http包導入, 但是不能被net/url包導入。 不過net/url包卻可以導入net/http/httputil包。
6. 查詢包
- go list 命令可以查詢可用包的信息。 其最簡單的形式, 可以測試包是否在工作區並打印它的導入路徑:
- go list 命令的參數還可以用 “…” 表示匹配任意的包的導入路徑。 我們可以用它來列表工作區中的所有包:
- 或者是特定子目錄下的所有包:
- 或者是和某個主題相關的所有包:
- go list 命令還可以獲取每個包完整的元信息, 而不僅僅只是導入路徑, 這些元信息可以以不同格式提供給用戶。 其中 -json 命令行參數表示用JSON格式打印每個包的元信息。
- 命令行參數 -f 則允許用戶使用text/template包( §4.6) 的模板語言定義輸出文本的格式。 下面的命令將打印strconv包的依賴的包, 然後用join模板函數將結果鏈接爲一行, 連接時每個結果之間用一個空格分隔:
- 命令行參數 -f 則允許用戶使用text/template包( §4.6) 的模板語言定義輸出文本的格式。 下面的命令將打印strconv包的依賴的包, 然後用join模板函數將結果鏈接爲一行, 連接時每個結果之間用一個空格分隔:
- 譯註: 上面的命令在Windows的命令行運行會遇到 template: main:1: unclosed action 的錯誤。 產生這個錯誤的原因是因爲命令行對命令中的 " " 參數進行了轉義處理。 可以按照下面的方法解決轉義字符串的問題:
- 下面的命令打印compress子目錄下所有包的依賴包列表:
- 譯註: Windows下有同樣有問題, 要避免轉義字符串的干擾:
- go list 命令對於一次性的交互式查詢或自動化構建或測試腳本都很有幫助。 我們將在11.2.4節中再次使用它。 每個子命令的更多信息, 包括可設置的字段和意義, 可以用 go helplist 命令查看。
- 在本章, 我們解釋了Go語言工具中除了測試命令之外的所有重要的子命令。 在下一章, 我們將看到如何用 go test 命令去運行Go語言程序中的測試代碼。