日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

go程序设计语言第十章-包管理和Go工具

發(fā)布時間:2023/12/2 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 go程序设计语言第十章-包管理和Go工具 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

如今一個不太大的程序可能會包含10000個函數(shù)。程序的作者只需要考慮其中的一部分,是自己設計的可能會少,因為大多數(shù)都是其他人所寫的,且能夠通過包實現(xiàn)重復使用。

Go提供了超過100個標準包。Go社區(qū),一個蓬勃發(fā)展的生態(tài),用于包設計、分享、重用和發(fā)展,已經發(fā)布了很多包。本章,我們將會展示如何使用已經存在的包和創(chuàng)建包。

Go也自帶了go tool工具,一個用于go包管理的復雜巧妙的但易于使用的命令。
本書開頭,我們已經展示了如何使用go tool來download、build和run程序。本章中,我們將會看到工具的底層概念underlying concept,探索它的更多能力,其中包括了打印文檔、查詢在命名空間workspace中的包的元數(shù)據(jù)。下一章將探索它的測試特性。

10.1 介紹

包系統(tǒng)的目的是使得大型程序的設計和維護變得practical,通過將相關聯(lián)的組件組合到一起形成一個單元,這些單元義域理解和變化,并且獨立于程序的其他包。這種包特性允許包被其他人分享和重用,在一個組織內分發(fā),或者應用在更廣泛的范圍。

每個包定義一個獨特的名字,包含它自己的標識符。每個名字關聯(lián)一個特定的包,請選擇簡短、清楚的名字作為類型、函數(shù)等我們最常用的標識符來命名,不要和程序中的其他部分造成命名沖突。

包通過控制名字在包外是否可見或者可導出,來提供封裝特性。通過限制包成員的可見性,隱藏了包內API的helper functions和types,允許包的持有者有把握地更改包內應用,包外的代碼將不受影響。限制包成員的可見性,同樣也隱藏了變量,因此clients只能通過導出的函數(shù)來訪問和更新變量,這樣保證了內部變量的一致性(preserve internal invariants)和并發(fā)訪問的相互排斥(在函數(shù)中加互斥鎖?)。

當我們改變一個文件,必須重新編譯文件的包和潛在的依賴此文件的所有包。即使是從頭開始編譯,go語言的編譯速度也明顯得快于其他語言。有三個原因。第一,所有的import必須明確地列在源文件的開頭,因此編譯器不需要查看整個源文件來確定它的依賴。第二,一個包的依賴構成一個非環(huán)的定向圖,因此沒有環(huán),包能夠被單獨編譯有可能還會并發(fā)編譯。最后,對于一個已經編譯好的目標文件,不僅記錄了它自身的導出信息,也記錄了它依賴的導出信息。因此當編譯一個包時,編譯器只需要對每個import而導入一個目標文件,而不需要查看它之下的依賴文件。

10.2 導入路徑

每個包被一個唯一的string標識,稱為import path。import path是在import聲明中的字符串:

import ( "fmt" "math/rand" "encoding/json" "golang.org/x/net/html" "github.com/go-sql-driver/mysql" )

正如我們在2.6.1中提到的那樣,go語言規(guī)范并沒有指明這些string的含義,也沒有定義該如何確定import path,這些都是由tools決定的。在本章中,我們將詳細查看go tools是如何詮釋他們的,這也是go程序用來build,test等操作的大部分功能。另外也有第三方擴展的tools存在,例如,使用google內部的多語言構建系統(tǒng)的go程序員,使用不同的規(guī)則來命名和定位包,定義測試案例等到,因為這會更匹配他們內部的環(huán)境。

對于嘗試分享和發(fā)布的包,導入路徑應該是全球唯一的。為避免沖突,非標準包的包的導入路徑應該以擁有此機構的域名開頭,這也使得查找包變得可能。例如,上面的聲明導入了一個Go機構擁有的HTML解析器包和一個公開的第三方MySql數(shù)據(jù)庫驅動包。

10.3 包聲明 Package Declaration

包聲明要求放在每個go源文件的開頭。主要目的是當此包被另外包導入時,來確定此包的默認標識符。
例如,math/rand包的每個文件都以pack rand開頭,因此當你導入這個包時,你可以通過rand.Int, rand.Float64這種形式訪問它內部的成員。

package main import ( "fmt" "math/rand" ) func main() { fmt.Println(rand.Int()) }

按照慣例,包名是導入路徑的的最后一段,因此,兩個包有可能導入路徑不同包名卻相同。
有三個主要的例外不適用于“最后一段”這個慣例。第一,一個執(zhí)行go程序包名字總是為main,不管包的導入路徑。這個名字是給go build一個信號,告訴它必須調用連接器來生成一個可執(zhí)行文件。
第二,在包的目錄中有一些以_test.go結尾的文件,它們的包名也是以_test結尾。這種目錄可能會定義兩個包:一個常規(guī)包,一個稱之為external test package. 這個_test后綴告訴go test必須構建兩個包,并且指示了哪個文件對應哪個包。(所有以_test為后綴包名的測試外部擴展包都由go test命令獨立編譯,普通包和測試的外部擴展包是相互獨立的。)External test packages包用來避免測試代碼中的循環(huán)導入依賴,具體細節(jié)我們將在11.2.4節(jié)中介紹。
第三,一些為了依賴管理的工具將在包的導入路徑后加版本號,例如"gopkg.in/yaml.v2,。包的名字排除掉后綴,仍然是yaml。

10.4 導入聲明 Import Declarations

源文件在包聲明之后,非導入聲明之前,可以有0個或多個import聲明語句。每個import聲明可以定義一個或多個導入路徑。兩種形式都可用不過第二種形式最常用。

import "fmt" import "os" import ("fmt""os" )

通過使用空格,import 的包被分組,分組表示包的域名不同。導入的順序無關緊要,但每個組內的包按字母排序。

import ( "fmt" "html/template" "os" "golang.org/x/net/html" "golang.org/x/net/ipv4" )

如果我們想要導入兩個相同名字的包,如math/rand 和crypto/rand, 導入聲明過必須定義一個可選的名字來避免沖突。這稱之為renaming import

import ("crypto/rand"mrand "math/rand" // alternative name mrand avoids conflict )

這個可選的名字只作用在此導入文件中,在另外的文件,甚至相同包的其他文件中,可能會按照默認名字或其他名字來導入這個包。

在沒有沖突時renaming package也會很有用。如果導入包名很臃腫,就像有時自動聲明代碼的時候,一個縮寫的包名會很方便使用。短的名字也應該避免產生疑惑。選擇一個可選名字能夠避免本地變量的沖突。例如,文件中許多本地變量名為path,我們在導入path包時就可以用pathpkg名稱。

每個import聲明建立一個從當前包到被導入包的依賴關系。如果出現(xiàn)環(huán)形依賴,go build tool會報錯。

10.5 空白導入 Blank Import

在一個文件中導入一個包但是未引用它定義的名字,就會產生一個錯誤。然而,有時我們必須導入一個包僅僅是為了這些功能:計算包級別變量的初始化表達式,執(zhí)行它的init函數(shù)。為了防止unused import錯誤,我們必須使用_來命名導入的包名,即空標識符。通常,空標識符不會被使用。
```import _ “image/png” // register PNG decoder````

這叫做a blank import. 它通常被用來執(zhí)行一個編譯器的機制,在此機制中主程序能夠通過空白導入可選的包來開啟可選的選項。我們先看一下如何使用它,接著看他是如何工作的。

標準庫的image包導出一個Decode函數(shù),用來從io.Reader讀取字節(jié),計算那個image格式來被使用于編碼數(shù)據(jù),包含合適的解碼器,返回返回Image.Image。使用image.Decode, 很容易創(chuàng)建一個簡單的轉換器,讀取一種格式的圖像,寫入另一種格式:

// The jpeg command reads a PNG image from the standard input // and writes it as a JPEG image to the standard output. package main import ("fmt""image""image/jpeg"_ "image/png" // register PNG decoder"io""os" ) func main() {if err := toJPEG(os.Stdin, os.Stdout); err != nil {fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)os.Exit(1)} } func toJPEG(in io.Reader, out io.Writer) error {img, kind, err := image.Decode(in)if err != nil {return err}fmt.Fprintln(os.Stderr, "Input format =", kind)return jpeg.Encode(out, img, &jpeg.Options{Quality: 95}) }

如果我們將3.3章節(jié)的程序結果給此轉換程序,它檢測出PNG格式的輸入,寫入一個JPEG的輸出:

$ go build gopl.io/ch3/mandelbrot $ go build gopl.io/ch10/jpeg $ ./mandelbrot | ./jpeg >mandelbrot.jpg Input format = png

注意到其中_ "image/png"的空白導入,如果沒有慈航,程序編譯和連接正常但是不能識別和解碼PNG格式:

$ go build gopl.io/ch10/jpeg $ ./mandelbrot | ./jpeg >mandelbrot.jpg jpeg: image: unknown format

查看它是如何工作的。標準庫提供應用于GIF、PNG、JPEG的解碼器,使用者也可以提供其他形式的,但是為了保持可執(zhí)行文件體積小,解碼器并沒有被包含在應用中除非明確要求。image.Decode函數(shù)維護一個支持格式的表。表中的每一項定義四件事:格式的名字,所有使用此種編碼格式的圖片前綴字符串,用來探測此種編碼;一個Decode函數(shù)用于解碼;一個DecodeConfig函數(shù)用來解碼圖片的元數(shù)據(jù),如大小、顏色空間。通過調用image.RegisterFormat,一個表項被加入到表中,通常是在所支持的包的初始化中完成,如image/png.

package png // image/pngfunc Decode(r io.Reader) (image.Image, error) func DecodeConfig(r io.Reader) (image.Config, error)func init() {const pngHeader = "\x89PNG\r\n\x1a\n"image.RegisterFormat("png", pngHeader, Decode, DecodeConfig) // 添加表項 }

這樣應用程序當需要解碼某個格式時,只需要空白導入此格式的包,就可以使image.Deocde函數(shù)能夠解碼此種格式。

database/sql使用相似的機制來使用戶安裝所需的數(shù)據(jù)庫驅動:

import ( "database/mysql" _ "github.com/lib/pq" // enable support for Postgres _ "github.com/go-sql-driver/mysql" // enable support for MySQL ) db, err = sql.Open("postgres", dbname) // OK db, err = sql.Open("mysql", dbname) // OK db, err = sql.Open("sqlite3", dbname) // returns error: unknown driver "sqlite3"

10.6 包和命名 Packages and Naming

本章節(jié),我們將提供一些建議,如何遵循go獨特的轉換來用于命名包名和它的成員。

創(chuàng)建包名要短,但不要短到語義不明。標準包中最常用的包名為bufio,bytes,flag,fmt,http,io.json,os,sort,sync,time.

Be descriptive and unambiguous where possible. 盡可能的描述性和清楚性。例如,當命名一個utility性質的包時,如果有類似imageutil和ioutil這樣簡明的名字,就不要命名為util這樣含糊不清的名字。避免選擇常用于本地變量名稱的名字作為包名,否則使用者將需要將包名重命名。

包名字通常為單數(shù)形式。標準包的bytes,errors,strings使用復數(shù)形式是為了避免掩蓋預定義的類型,而go/types則為了避免和關鍵字沖突。

避免使用已經有其他含義的名字作為包名。例如,我們之前使用temp來作為temperature conversion包名,但是這不持久的。因為temp通常代碼temporary。We went through a brief period with the name temperature, but that was too long and didn’t say what the package did。最后,使用tempconv,足夠短也和strconv保持一致。

現(xiàn)在查看包成員的命名。因為每個其他包成員的使用指定的標識符,如fmt.Println,引用包成員時同時指定了包名。在Println中不需要關注formatting的概念,因為包的名字fmt已經告訴我們了。當設計一個包時,應該考慮這兩部分的整體如何,而不是只看成員名稱。例如:
bytes.Equal flag.Int http.Get json.Marshal

可以指定一些通用名稱樣式。strings包提供一些操作string的獨立函數(shù):

package strings func Index(needle, haystack string) int type Replacer struct{ /* ... */ } func NewReplacer(oldnew ...string) *Replacer type Reader struct{ /* ... */ } func NewReader(s string) *Reader

單詞string在函數(shù)名中不會出現(xiàn)。使用者采用strings.Index,strings.Replacer的形式調用他們。

另外一些包可能只會有一種類型,例如html/template和math/rand,對象暴露一個數(shù)據(jù)類型再加它的方法,通常有一個New函數(shù)用來創(chuàng)造實例。

package rand // "math/rand" type Rand struct{ /* ... */ } func New(source Source) *Ran

這將導致重名,就像template.Template或者rand.Rand, 這也是為什么這些包名短的原因。

另一個極端,有些包想net/http有很多方法,但結構少,因為他們執(zhí)行非常復雜的任務。盡管有超過20種類型和更多的函數(shù),但包的成員名稱簡單:Get, Post, Handle, Error, Client, Server.

10.7 go 工具 GO Tool

余下的章節(jié)介紹go too, 它被用于 downloading, querying, formatting , building, testing , and installing packages of Go code.

go tool將一系列工具特性整合到命令集合中。它是一個包管理(類似apt和rpm),用于查看包清單、查看包依賴,從遠程版本控制系統(tǒng)下載包。它是一個build system, 能夠查看包依賴,包含編譯器、匯編器、鏈接器,比make工具稍微少一點完備性。它是一個測試驅動,就像11章節(jié)看到的那樣。

它的命令行接口使用“Swiss army knife”(瑞士軍刀)形式,包含需要子命令,例如get、run、build和fmt。可以使用go help查看,這里我們列出最常用命令:

$ go ...build compile packages and dependenciesclean remove object filesdoc show documentation for package or symbolenv print Go environment informationfmt run gofmt on package sourcesget download and install packages and dependenciesinstall compile and install packages and dependencieslist list packagesrun compile and run Go programtest test packagesversion print Go versionvet run go tool vet on packages Use "go help [command]" for more information about a command.

為了最小化配置需求,go tool嚴重依賴各種約定。例如,給出geo源碼文件的名字,工具能夠找到它內部包裹的包,因為每個目錄包含一個單獨的包,一個包的import path與工作區(qū)的目錄體系相關。給出一個包的import path, 工具能夠找到存儲目標文件的目錄。它也能找到連接源碼倉庫的服務器的URL。

工作區(qū) Workspace Organization

大多數(shù)使用者唯一需要配置的就是GOPATH這個環(huán)境變量,它定義了工作區(qū)的根目錄。當切換到另一個不同的工作區(qū),使用者更新GOPATH。例如,我們將GOPATH設置為$HOME/gobook:

export GOPATH=$HOME/gobook go get gopl.io/...

當下載了書中的樣例代碼后,工作區(qū)如下所示:

GOPATH/src/gopl.io/.git/ch1/helloworld/main.godup/main.go...golang.org/x/net/.git/html/parse.gonode.go...bin/helloworldduppkg/darwin_amd64/...

GOPATH有三個子目錄。src目錄存儲源碼。每個包存儲的目錄依賴于$GOPATH/src,也就是包的導入路徑,例如gopl.io/ch1/hellword。一個GOPATH工作區(qū)中,在src下面包含許多版本控制倉庫,如gopl.io和golang.org。pkg子目錄是build工具存儲已編譯好的包的地方。bin目錄存放可執(zhí)行文件。

另一個GOROOT環(huán)境變量,定義了Go distribution的root Go directory,它提供標準庫中所有的包。GOROOT下的目錄結構和GOPAT類似,列入fmt包源碼存放在$GOROOT/src/fmt目錄下。用戶不需要設置GOROOT,因為默認go tool工具會使用它安裝的位置。

go env命令與工具鏈相關的環(huán)境變量值, 包括遺漏的默認值(啥意思,就是沒設置的值也顯示)。GOOS描述操作系統(tǒng)(如android, linux, darwin, windows), GOARCH描述目標程序架構,例如amd64,386或者arm。盡管GOPATH是唯一設置的值,其他的值也會顯示:

$ go env GOPATH="/home/gopher/gobook" GOROOT="/usr/local/go" GOARCH="amd64" GOOS="darwin" ...

下載包

當使用go tool, 一個包的import path不僅指明了如何在本地工作區(qū)查找此包,還指明了如果在Internet上查找此包,因此go get能夠獲取和更新此包。

go get命令可以下載單一一個包或者整個樹或倉庫(使用…符號)。工具同樣會計算和下載包的所有依賴。
一旦go get下載了包,它build此包,install 庫和命令。下面第一個命令獲取golint工具,它用于檢查源碼中常用的樣式問題。第二個命令運行golint這個工具,檢查我們之前系的popcout源碼。它會報告出我們忘記在包中寫文檔:

$ go get github.com/golang/lint/golint $ $GOPATH/bin/golint gopl.io/ch2/popcount src/gopl.io/ch2/popcount/main.go:1:1: package comment should be of the form "Package popcount ..."

go get命令支持常見的開源網站如GIthub,Bitbucket,能夠向他們的版本控制系統(tǒng)發(fā)出合適的請求。其他網絡,需要查看在import path中使用哪種版本控制協(xié)議。使用go help importpath查看更多內容。

go get創(chuàng)建的目錄是遠程倉庫的真實fork,而不僅僅是文件的復制,因此可以使用版本控制命令來查看本地文件的變化或者更新到一個不同的吧版本:

$ cd $GOPATH/src/golang.org/x/net $ git remote -v origin https://go.googlesource.com/net (fetch) origin https://go.googlesource.com/net (push)

import path中的域名,如golang.org,不是Git Server的真實域名–go.googlesource.com。這是go tool的一個特性,使得在import path中使用用戶域名,而網絡使用一個通用的服務如googlesource.com或github.com。
在https://golang.org/x/net/html寫的HTML頁面包含下面展示的元數(shù)據(jù),這些元數(shù)據(jù)重定向go tool工具到git倉庫作為真實的網絡地址;

$ go build gopl.io/ch1/fetch $ ./fetch https://golang.org/x/net/html | grep go-import <meta name="go-import" content="golang.org/x/net git https://go.googlesource.com/net">

如果指定-u參數(shù),go get將會將包和他們的依賴在built和installed之前更新到最新版本。如果不指定,則本地已存在的包不會更新。

使用-u參數(shù)通過獲取包的最新版本,當開啟一個新項目時有用,但對于對版本控制嚴格的已部署項目則不太合適。通常的解決辦法是vendor the code, 就是說,將所有需要的依賴做一個長期的本地備份,謹慎地更新這個備份。從Go1.5開始,這將改變包的import path,因此golang.org/x/net/html的備份包將變?yōu)間opl.io/vendor/golang.org/x/net/html。 最近版本的go tool直接支持vendoring,通過go help gopath中的Vendor Directories詳細查看。

Building Packages

go build命令編譯每一個參數(shù)包。如果包是一個庫,結果被舍棄,這可以用于檢查包是否能被正確編譯。如果包名為main,go build包含鏈接器在當前目錄創(chuàng)建一個可執(zhí)行文件;文件名是包導入路徑的最后一段。
因為每個目錄包含一個包,每個可執(zhí)行程序或者Unix命令都要求放入一個獨立的目錄中(就是說每個編譯好的可執(zhí)行文件,都必須放在一個獨立的目錄中)。這些目錄有時會是一個名為cmd目錄的子目錄,如golang.org/x/tools/cmd/godoc 命令,通過web接口提供go包文檔。

包可以通過import path指定,也可以通過一個相對目錄名指定,目錄名以.或者..開頭。如果沒有參數(shù)指定,則相對于當前目錄。因此下面的命令build相同的包,盡管他們將可執(zhí)行文件寫入go build運行的目錄:

$ cd $GOPATH/src/gopl.io/ch1/helloworld$ go build and:$ cd anywhere$ go build gopl.io/ch1/helloworld and:$ cd $GOPATH$ go build ./src/gopl.io/ch1/helloworld but not:$ cd $GOPATH$ go build src/gopl.io/ch1/helloworldError: cannot find package "src/gopl.io/ch1/helloworld".

包也可以被一系列文件名指定,這些長用于小程序或者一些臨時性的實驗。如果包名是main,可執(zhí)行文件名取第一個go文件的名字.

$ cat quoteargs.go package main import ("fmt""os" ) func main() {fmt.Printf("%q\n", os.Args[1:]) } $ go build quoteargs.go $ ./quoteargs one "two three" four\ five ["one" "two three" "four five"]

特別地,對于用后即棄的程序,像如下所示,我們希望在build后就立即運行它,go run命令會組合這兩個步驟:

$ go run quoteargs.go one "two three" four\ five ["one" "two three" "four five"]

第一個不以為.go結尾的參數(shù)將會作為可執(zhí)行程序參數(shù)列表的起始位置。

go build默認build此包和它的所有依賴,丟棄掉除了可執(zhí)行文件外的所有編譯代碼。依賴分析和編譯都會很快,但是隨著項目增長到數(shù)十個包和數(shù)萬行代碼,重新依賴的時間消耗會變得很可觀,有時需要幾秒鐘,即使這些依賴項沒有發(fā)生變化(啥意思,依賴項不變,編譯時間會變長?)。

go install命令和go build很相似,但它保存了編譯代碼和命令,而不是丟棄他們。已經編譯的包位于$GOPATH/pkg目錄下,與保存源碼的src目錄相關聯(lián),命令行執(zhí)行程序存放在bin目錄(用戶常將GOPATH/bin目錄添加到可執(zhí)行文件搜索路徑中)。因此,go build和go install不會為那些沒有變化的包和命令重新編譯,將會使編譯加快。為了方便,go build -i 安裝編譯目標的依賴包。

因為編譯好的包依賴于平臺和架構,因此go install i將他們保存在和GOOS和GOARCH這兩個環(huán)境變量所存儲指的目錄中。例如golang.org/x/net/html包被編譯后安裝在 $GOPATH/pkg/darwin_amd64下的golang.org/x/net/html.a。

跨平臺編譯go程序很簡單,那意味著構建一個用于不同平臺和cpu的可執(zhí)行程序。僅需要在編譯時設置GOOS和GOARCH這兩個變量。這個跨平臺程序會打印出它是為那個平臺和架構編譯的:

gopl.io/ch10/cross func main() {fmt.Println(runtime.GOOS, runtime.GOARCH) }

下面分別產生64位、32位的可執(zhí)行文件:

$ go build gopl.io/ch10/cross $ ./cross darwin amd64 $ GOARCH=386 go build gopl.io/ch10/cross $ ./cross darwin 386

一些包可能需要將代碼編譯成不同的版本來適應不同的架構和平臺。如果文件名包含系統(tǒng)名或架構名,如net_linux.go或者asm_amd64.s, go tool工具則只會在編譯成此架構上的文件時才使用此文件。還有一些特殊的注釋稱為build tags,會進行更詳細的控制。例如:
// +build linux darwin
這個注釋在包聲明和文檔注釋之前,go build也只會在為linux和Mac OS構建時才編譯此文件,像下面則表示從不編譯此文件:
// +build ignore
查看go/build包的文檔中Build Constraints部分訪問更多內容:
$ go doc go/build

總結

以上是生活随笔為你收集整理的go程序设计语言第十章-包管理和Go工具的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。