在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ GO/ go vet與go tool vet
go install
go clean
go list
go test
go doc與godoc
go build
go fix與go tool fix
go tool pprof
go run
go env
go tool cgo
標(biāo)準(zhǔn)命令詳解
go get
go vet與go tool vet

go vet與go tool vet

命令go vet是一個(gè)用于檢查Go語(yǔ)言源碼中靜態(tài)錯(cuò)誤的簡(jiǎn)單工具。與大多數(shù)Go命令一樣,go vet命令可以接受-n標(biāo)記和-x標(biāo)記。-n標(biāo)記用于只打印流程中執(zhí)行的命令而不真正執(zhí)行它們。-n標(biāo)記也用于打印流程中執(zhí)行的命令,但不會(huì)取消這些命令的執(zhí)行。示例如下:

hc@ubt:~$ go vet -n pkgtool
/usr/local/go/pkg/tool/linux_386/vet golang/goc2p/src/pkgtool/envir.go golang/goc2p/src/pkgtool/envir_test.go golang/goc2p/src/pkgtool/fpath.go golang/goc2p/src/pkgtool/ipath.go golang/goc2p/src/pkgtool/pnode.go golang/goc2p/src/pkgtool/util.go golang/goc2p/src/pkgtool/util_test.go

go vet命令的參數(shù)既可以是代碼包的導(dǎo)入路徑,也可以是Go語(yǔ)言源碼文件的絕對(duì)路徑或相對(duì)路徑。但是,這兩種參數(shù)不能混用。也就是說(shuō),go vet命令的參數(shù)要么是一個(gè)或多個(gè)代碼包導(dǎo)入路徑,要么是一個(gè)或多個(gè)Go語(yǔ)言源碼文件的路徑。

go vet命令是go tool vet命令的簡(jiǎn)單封裝。它會(huì)首先載入和分析指定的代碼包,并把指定代碼包中的所有Go語(yǔ)言源碼文件和以“.s”結(jié)尾的文件的相對(duì)路徑作為參數(shù)傳遞給go tool vet命令。其中,以“.s”結(jié)尾的文件是匯編語(yǔ)言的源碼文件。如果go vet命令的參數(shù)是Go語(yǔ)言源碼文件的路徑,則會(huì)直接將這些參數(shù)傳遞給go tool vet命令。

如果我們直接使用go tool vet命令,則其參數(shù)可以傳遞任意目錄的路徑,或者任何Go語(yǔ)言源碼文件和匯編語(yǔ)言源碼文件的路徑。路徑可以是絕對(duì)的也可以是相對(duì)的。

實(shí)際上,vet屬于Go語(yǔ)言自帶的特殊工具,也是比較底層的命令之一。Go語(yǔ)言自帶的特殊工具的存放路徑是$GOROOT/pkg/tool/$GOOS$GOARCH/,我們暫且稱(chēng)之為Go工具目錄。我們?cè)賮?lái)復(fù)習(xí)一下,環(huán)境變量GOROOT的值即Go語(yǔ)言的安裝目錄,環(huán)境變量GOOS的值代表程序構(gòu)建環(huán)境的目標(biāo)操作系統(tǒng)的標(biāo)識(shí),而環(huán)境變量$GOARCH的值則為程序構(gòu)建環(huán)境的目標(biāo)計(jì)算架構(gòu)。另外,名為$GOOS$GOARCH的目錄被叫做平臺(tái)相關(guān)目錄。Go語(yǔ)言允許我們通過(guò)執(zhí)行go tool命令來(lái)運(yùn)行這些特殊工具。在Linux 32bit的環(huán)境下,我們的Go語(yǔ)言安裝目錄是/usr/local/go/。因此,go tool vet命令指向的就是被存放在/usr/local/go/pkg/tool/linux_386目錄下的名為vet的工具。

go tool vet命令的作用是檢查Go語(yǔ)言源代碼并且報(bào)告可疑的代碼編寫(xiě)問(wèn)題。比如,在調(diào)用Printf函數(shù)時(shí)沒(méi)有傳入格式化字符串,以及某些不標(biāo)準(zhǔn)的方法簽名,等等。該命令使用試探性的手法檢查錯(cuò)誤,因此并不能保證報(bào)告的問(wèn)題確實(shí)需要解決。但是,它確實(shí)能夠找到一些編譯器沒(méi)有捕捉到的錯(cuò)誤。

go tool vet命令程序在被執(zhí)行后會(huì)首先解析標(biāo)記并檢查標(biāo)記值。go tool vet命令支持的所有標(biāo)記如下表。

表0-16 go tool vet命令的標(biāo)記說(shuō)明

標(biāo)記名稱(chēng) 標(biāo)記描述
-all 進(jìn)行全部檢查。如果有其他檢查標(biāo)記被設(shè)置,則命令程序會(huì)將此值變?yōu)閒alse。默認(rèn)值為true。
-asmdecl 對(duì)匯編語(yǔ)言的源碼文件進(jìn)行檢查。默認(rèn)值為false。
-assign 檢查賦值語(yǔ)句。默認(rèn)值為false。
-atomic 檢查代碼中對(duì)代碼包sync/atomic的使用是否正確。默認(rèn)值為false。
-buildtags 檢查編譯標(biāo)簽的有效性。默認(rèn)值為false。
-composites 檢查復(fù)合結(jié)構(gòu)實(shí)例的初始化代碼。默認(rèn)值為false。
-compositeWhiteList 是否使用復(fù)合結(jié)構(gòu)檢查的白名單。僅供測(cè)試使用。默認(rèn)值為true。
-methods 檢查那些擁有標(biāo)準(zhǔn)命名的方法的簽名。默認(rèn)值為false。
-printf 檢查代碼中對(duì)打印函數(shù)的使用是否正確。默認(rèn)值為false。
-printfuncs 需要檢查的代碼中使用的打印函數(shù)的名稱(chēng)的列表,多個(gè)函數(shù)名稱(chēng)之間用英文半角逗號(hào)分隔。默認(rèn)值為空字符串。
-rangeloops 檢查代碼中對(duì)在```range```語(yǔ)句塊中迭代賦值的變量的使用是否正確。默認(rèn)值為false。
-structtags 檢查結(jié)構(gòu)體類(lèi)型的字段的標(biāo)簽的格式是否標(biāo)準(zhǔn)。默認(rèn)值為false。
-unreachable 查找并報(bào)告不可到達(dá)的代碼。默認(rèn)值為false。

在閱讀上面表格中的內(nèi)容之后,讀者可能對(duì)這些標(biāo)簽的具體作用及其對(duì)命令程序檢查步驟的具體影響還很模糊。不過(guò)沒(méi)關(guān)系,我們下面就會(huì)對(duì)它們進(jìn)行逐一的說(shuō)明。

-all標(biāo)記

如果標(biāo)記-all有效(標(biāo)記值不為false),那么命令程序會(huì)對(duì)目標(biāo)文件進(jìn)行所有已知的檢查。實(shí)際上,標(biāo)記-all的默認(rèn)值就是true。也就是說(shuō),在執(zhí)行go tool vet命令且不加任何標(biāo)記的情況下,命令程序會(huì)對(duì)目標(biāo)文件進(jìn)行全面的檢查。但是,只要有一個(gè)另外的標(biāo)記(-compositeWhiteList-printfuncs這兩個(gè)標(biāo)記除外)有效,命令程序就會(huì)把標(biāo)記-all設(shè)置為false,并只會(huì)進(jìn)行與有效的標(biāo)記對(duì)應(yīng)的檢查。

-assign標(biāo)記

如果標(biāo)記-assign有效(標(biāo)記值不為false),則命令程序會(huì)對(duì)目標(biāo)文件中的賦值語(yǔ)句進(jìn)行自賦值操作檢查。什么叫做自賦值呢?簡(jiǎn)單來(lái)說(shuō),就是將一個(gè)值或者實(shí)例賦值給它本身。像這樣:

var s1 string = "S1"
s1 = s1 // 自賦值

或者

s1, s2 := "S1", "S2"
s2, s1 = s2, s1 // 自賦值

檢查程序會(huì)同時(shí)遍歷等號(hào)兩邊的變量或者值。在抽象語(yǔ)法樹(shù)的語(yǔ)境中,它們都被叫做表達(dá)式節(jié)點(diǎn)。檢查程序會(huì)檢查等號(hào)兩邊對(duì)應(yīng)的表達(dá)式是否相同。判斷的依據(jù)是這兩個(gè)表達(dá)式節(jié)點(diǎn)的字符串形式是否相同。在當(dāng)前的場(chǎng)景下,這種相同意味著它們的變量名是相同的。如前面的示例。

有兩種情況是可以忽略自賦值檢查的。一種情況是短變量聲明語(yǔ)句。根據(jù)Go語(yǔ)言的語(yǔ)法規(guī)則,當(dāng)我們?cè)诤瘮?shù)中要在聲明局部變量的同時(shí)對(duì)其賦值,就可以使用:=形式的變量賦值語(yǔ)句。這也就意味著:=左邊的變量名稱(chēng)在當(dāng)前的上下文環(huán)境中應(yīng)該還未曾出現(xiàn)過(guò)(否則不能通過(guò)編譯)。因此,在這種賦值語(yǔ)句中不可能出現(xiàn)自賦值的情況,忽略對(duì)它的檢查也是合理的。另一種情況是等號(hào)左右兩邊的表達(dá)式個(gè)數(shù)不相等的變量賦值語(yǔ)句。如果在等號(hào)的右邊是對(duì)某個(gè)函數(shù)或方法的調(diào)用,就會(huì)造成這種情況。比如:

file, err := os.Open(wp)

很顯然,這個(gè)賦值語(yǔ)句肯定不是自賦值語(yǔ)句。因此,不需要對(duì)此種情況進(jìn)行檢查。如果等號(hào)右邊并不是對(duì)函數(shù)或方法調(diào)用的表達(dá)式,并且等號(hào)兩邊的表達(dá)式數(shù)量也不相等,那么勢(shì)必會(huì)在編譯時(shí)引發(fā)錯(cuò)誤,也不必檢查。

-atomic標(biāo)記

如果標(biāo)記-atomic有效(標(biāo)記值不為false),則命令程序會(huì)對(duì)目標(biāo)文件中的使用代碼包sync/atomic進(jìn)行原子賦值的語(yǔ)句進(jìn)行檢查。原子賦值語(yǔ)句像這樣:

var i32 int32
i32 = 0
newi32 := atomic.AddInt32(&i32, 3)
fmt.Printf("i32: %d, newi32: %d.\n", i32, newi32)

函數(shù)AddInt32會(huì)原子性的將變量i32的值加3,并返回這個(gè)新值。因此上面示例的打印結(jié)果是:

i32: 3, newi32: 3

在代碼包sync/atomic中,與AddInt32類(lèi)似的函數(shù)還有AddInt64、AddUint32、AddUint64AddUintptr。檢查程序會(huì)對(duì)上述這些函數(shù)的使用方式進(jìn)行檢查。檢查的關(guān)注點(diǎn)在破壞原子性的使用方式上。比如:

i32 = 1
i32 = atomic.AddInt32(&i32, 3)
_, i32 = 5, atomic.AddInt32(&i32, 3)
i32, _ = atomic.AddInt32(&i32, 1), 5 

上面示例中的后三行賦值語(yǔ)句都屬于原子賦值語(yǔ)句,但它們都破壞了原子賦值的原子性。以第二行的賦值語(yǔ)句為例,等號(hào)左邊的atomic.AddInt32(&i32, 3)的作用是原子性的將變量i32的值增加3。但該語(yǔ)句又將函數(shù)的結(jié)果值賦值給變量i32,這個(gè)二次賦值屬于對(duì)變量i32的重復(fù)賦值,也使原本擁有原子性的賦值操作被拆分為了兩個(gè)步驟的非原子操作。如果在對(duì)變量i32的第一次原子賦值和第二次非原子的重復(fù)賦值之間又有另一個(gè)程序?qū)ψ兞?code>i32進(jìn)行了原子賦值,那么當(dāng)前程序中的這個(gè)第二次賦值就破壞了那兩次原子賦值本應(yīng)有的順序性。因?yàn)?,在另一個(gè)程序?qū)ψ兞?code>i32進(jìn)行原子賦值后,當(dāng)前程序中的第二次賦值又將變量i32的值設(shè)置回了之前的值。這顯然是不對(duì)的。所以,上面示例中的第二行代碼應(yīng)該改為:

atomic.AddInt32(&i32, 3)

并且,對(duì)第三行和第四行的代碼也應(yīng)該有類(lèi)似的修改。檢查程序如果在目標(biāo)文件中查找到像上面示例的第二、三、四行那樣的語(yǔ)句,就會(huì)打印出相應(yīng)的錯(cuò)誤信息。

另外,上面所說(shuō)的導(dǎo)致原子性被破壞的重復(fù)賦值語(yǔ)句還有一些類(lèi)似的形式。比如:

i32p := &i32
*i32p = atomic.AddUint64(i32p, 1)

這與之前的示例中的代碼的含義幾乎是一樣。另外還有:

var counter struct{ N uint32 }
counter.N = atomic.AddUint64(&counter.N, 1) 

ns := []uint32{10, 20}
ns[0] = atomic.AddUint32(&ns[0], 1)
nps := []*uint32{&ns[0], &ns[1]}
*nps[0] = atomic.AddUint32(nps[0], 1)

在最近的這兩個(gè)示例中,雖然破壞原子性的重復(fù)賦值操作因結(jié)構(gòu)體類(lèi)型或者數(shù)組類(lèi)型的介入顯得并不那么直觀了,但依然會(huì)被檢查程序發(fā)現(xiàn)并及時(shí)打印錯(cuò)誤信息。

順便提一句,對(duì)于原子賦值語(yǔ)句和普通賦值語(yǔ)句,檢查程序都會(huì)忽略掉對(duì)等號(hào)兩邊的表達(dá)式的個(gè)數(shù)不相等的賦值語(yǔ)句的檢查。

-buildtags標(biāo)記

前文已提到,如果標(biāo)記-buildtags有效(標(biāo)記值不為false),那么命令程序會(huì)對(duì)目標(biāo)文件中的編譯標(biāo)簽(如果有的話)的格式進(jìn)行檢查。什么叫做條件編譯?在實(shí)際場(chǎng)景中,有些源碼文件中包含了平臺(tái)相關(guān)的代碼。我們希望只在某些特定平臺(tái)下才編譯它們。這種有選擇的編譯方法就被叫做條件編譯。在Go語(yǔ)言中,條件編譯的配置就是通過(guò)編譯標(biāo)簽來(lái)完成的。編譯器需要依據(jù)源碼文件中編譯標(biāo)簽的內(nèi)容來(lái)決定是否編譯當(dāng)前文件。編譯標(biāo)簽可必須出現(xiàn)在任何源碼文件(比如擴(kuò)展名為“.go”,“.h”,“.c”,“.s”等的源碼文件) 的頭部的單行注釋中,并且在其后面需要有空行。

至于編譯標(biāo)簽的具體寫(xiě)法,我們就不在此贅述了。讀者可以參看Go語(yǔ)言官方的相關(guān)文檔。我們?cè)谶@里只簡(jiǎn)單羅列一下-buildtags有效時(shí)命令程序?qū)幾g標(biāo)簽的檢查內(nèi)容:

  1. 若編譯標(biāo)簽前導(dǎo)符“+build”后沒(méi)有緊隨空格,則打印格式錯(cuò)誤信息。

  2. 若編譯標(biāo)簽所在行與第一個(gè)多行注釋或代碼行之間沒(méi)有空行,則打印錯(cuò)誤信息。

  3. 若在某個(gè)單一參數(shù)的前面有兩個(gè)英文嘆號(hào)“!!”,則打印錯(cuò)誤信息。

  4. 若單個(gè)參數(shù)包含字母、數(shù)字、“_”和“.”以外的字符,則打印錯(cuò)誤信息。

  5. 若出現(xiàn)在文件頭部單行注釋中的編譯標(biāo)簽前導(dǎo)符“+build”未緊隨在單行注釋前導(dǎo)符“//”之后,則打印錯(cuò)誤信息。

如果一個(gè)在文件頭部的單行注釋中的編譯標(biāo)簽通過(guò)了上述的這些檢查,則說(shuō)明它的格式是正確無(wú)誤的。由于只有在文件頭部的單行注釋中編譯標(biāo)簽才會(huì)被編譯器認(rèn)可,所以檢查程序只會(huì)查找和檢查源碼文件中的第一個(gè)多行注釋或代碼行之前的內(nèi)容。

-composites標(biāo)記和-compositeWhiteList標(biāo)記

如果標(biāo)記-composites有效(標(biāo)記值不為false),則命令程序會(huì)對(duì)目標(biāo)文件中的復(fù)合字面量進(jìn)行檢查。請(qǐng)看如下示例:

type counter struct {
    name   string
    number int
}
...
c := counter{name: "c1", number: 0}

在上面的示例中,代碼counter{name: "c1", number: 0}是對(duì)結(jié)構(gòu)體類(lèi)型counter的初始化。如果復(fù)合字面量中涉及到的類(lèi)型不在當(dāng)前代碼包內(nèi)部且未在所屬文件中被導(dǎo)入,那么檢查程序不但會(huì)打印錯(cuò)誤信息還會(huì)將退出代碼設(shè)置為1,并且取消后續(xù)的檢查。退出代碼為1意味著檢查程序已經(jīng)報(bào)告了一個(gè)或多個(gè)問(wèn)題。這個(gè)問(wèn)題比僅僅引起錯(cuò)誤信息報(bào)告的問(wèn)題更加嚴(yán)重。

在通過(guò)上述檢查的前提下,如果復(fù)合字面量中包含了對(duì)結(jié)構(gòu)體類(lèi)型的字段的賦值但卻沒(méi)有指明字段名,像這樣:

var v = flag.Flag{
    "Name",
    "Usage",
    nil, // Value
    "DefValue",
}

那么檢查程序也會(huì)打印錯(cuò)誤信息,以提示在復(fù)合字面量中包含有未指明的字段賦值。

這有一個(gè)例外,那就是當(dāng)標(biāo)記-compositeWhiteList有效(標(biāo)記值不為false)的時(shí)候。只要類(lèi)型在白名單中,即使其初始化語(yǔ)句中含有未指明的字段賦值也不會(huì)被提示。這是出于什么考慮呢?先來(lái)看下面的示例:

type sliceType []string
...
st1 := sliceType{"1", "2", "3"}

上面示例中的sliceType{"1", "2", "3"}也屬于復(fù)合字面量。但是它初始化的類(lèi)型實(shí)際上是一個(gè)切片值,只不過(guò)這個(gè)切片值被別名化并被包裝為了另一個(gè)類(lèi)型而已。在這種情況下,復(fù)合字面量中的賦值不需要指明字段,事實(shí)上這樣的類(lèi)型也不包含任何字段。白名單中所包含的類(lèi)型都是這種情況。它們是在標(biāo)準(zhǔn)庫(kù)中的包裝了切片值的類(lèi)型。它們不需要被檢查,因?yàn)檫@種情況是合理的。

在默認(rèn)情況下,標(biāo)記-compositeWhiteList是有效的。也就是說(shuō),檢查程序不會(huì)對(duì)它們的初始化代碼進(jìn)行檢查,除非我們?cè)趫?zhí)行go tool vet命令時(shí)顯示的將-compositeWhiteList標(biāo)記的值設(shè)置為false。

-methods標(biāo)記

如果標(biāo)記-methods有效(標(biāo)記值不為false),則命令程序會(huì)對(duì)目標(biāo)文件中的方法定義進(jìn)行規(guī)范性的進(jìn)行檢查。這里所說(shuō)的規(guī)范性是狹義的。

在檢查程序內(nèi)部存有一個(gè)規(guī)范化方法字典。這個(gè)字典的鍵用來(lái)表示方法的名稱(chēng),而字典的元素則用來(lái)描述方法應(yīng)有的參數(shù)和結(jié)果的類(lèi)型。在該字典中列出的都是Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)中使用最廣泛的接口類(lèi)型的方法。這些方法的名字都非常通用。它們中的大多數(shù)都是它們所屬接口類(lèi)型的唯一方法。我們?cè)诘?章中提到過(guò),Go語(yǔ)言中的接口類(lèi)型實(shí)現(xiàn)方式是非侵入式的。只要結(jié)構(gòu)體類(lèi)型實(shí)現(xiàn)了某一個(gè)接口類(lèi)型中的所有方法,就可以說(shuō)這個(gè)結(jié)構(gòu)體類(lèi)型是該接口類(lèi)型的一個(gè)實(shí)現(xiàn)。這種判斷方式被稱(chēng)為動(dòng)態(tài)接口檢查。它只在運(yùn)行時(shí)進(jìn)行。如果我們想讓一個(gè)結(jié)構(gòu)體類(lèi)型成為某一個(gè)接口類(lèi)型的實(shí)現(xiàn),但又寫(xiě)錯(cuò)了要實(shí)現(xiàn)的接口類(lèi)型中的方法的簽名,那么也不會(huì)引發(fā)編譯器報(bào)錯(cuò)。這里所說(shuō)的方法簽名包括方法的參數(shù)聲明列表和結(jié)果聲明列表。雖然動(dòng)態(tài)接口檢查失敗時(shí)并不會(huì)報(bào)錯(cuò),但是它卻會(huì)間接的引發(fā)其它錯(cuò)誤。而這些被間接引發(fā)的錯(cuò)誤只會(huì)在運(yùn)行時(shí)發(fā)生。示例如下:

type MySeeker struct {
    // 忽略字段定義
}

func (self *MySeeker) Seek(whence int, offset int64) (ret int64, err error) { 
    // 想實(shí)現(xiàn)接口類(lèi)型io.Seeker中的唯一方法,但是卻把參數(shù)的順序?qū)戭嵉沽恕?    // 忽略實(shí)現(xiàn)代碼
}

func NewMySeeker io.Seeker {
    return &MySeeker{/* 忽略字段初始化 */} // 這里會(huì)引發(fā)一個(gè)運(yùn)行時(shí)錯(cuò)誤。
                                           //由于MySeeker的Seek方法的簽名寫(xiě)錯(cuò)了,所以MySeeker不是io.Seeker的實(shí)現(xiàn)。
}

這種運(yùn)行時(shí)錯(cuò)誤看起來(lái)會(huì)比較詭異,并且錯(cuò)誤排查也會(huì)相對(duì)困難,所以應(yīng)該盡量避免。-methods標(biāo)記所對(duì)應(yīng)的檢查就是為了達(dá)到這個(gè)目的。檢查程序在發(fā)現(xiàn)目標(biāo)文件中某個(gè)方法的名字被包含在規(guī)范化方法字典中但其簽名與對(duì)應(yīng)的描述不對(duì)應(yīng)的時(shí)候,就會(huì)打印錯(cuò)誤信息并設(shè)置退出代碼為1。

我在這里附上在規(guī)范化方法字典中列出的方法的信息:

表0-17 規(guī)范化方法字典中列出的方法

方法名稱(chēng) 參數(shù)類(lèi)型 結(jié)果類(lèi)型 所屬接口 唯一方法
Format "fmt.State", "rune" fmt.Formatter
GobDecode "[]byte" "error" gob.GobDecoder
GobEncode "[]byte", "error" gob.GobEncoder
MarshalJSON "[]byte", "error" json.Marshaler
Peek "int" "[]byte", "error" image.reader
ReadByte "int" "[]byte", "error" io.ByteReader
ReadFrom "io.Reader" "int64", "error" io.ReaderFrom
ReadRune "rune", "int", "error" io.RuneReader
Scan "fmt.ScanState", "rune" "error" fmt.Scanner
Seek "int64", "int" "int64", "error" io.Seeker
UnmarshalJSON "[]byte" "error" json.Unmarshaler
UnreadByte "error" io.ByteScanner
UnreadRune "error" io.RuneScanner
WriteByte "byte" "error" io.ByteWriter
WriteTo "io.Writer" "int64", "error" io.WriterTo

-printf標(biāo)記和-printfuncs標(biāo)記

標(biāo)記-printf旨在目標(biāo)文件中檢查各種打印函數(shù)使用的正確性。而標(biāo)記-printfuncs及其值則用于明確指出需要檢查的打印函數(shù)。-printfuncs標(biāo)記的默認(rèn)值為空字符串。也就是說(shuō),若不明確指出檢查目標(biāo)則檢查所有打印函數(shù)。可被檢查的打印函數(shù)如下表:

表0-18 格式化字符串中動(dòng)詞的格式要求

函數(shù)全小寫(xiě)名稱(chēng) 支持格式化 可自定義輸出 自帶換行
error
fatal
fprint
fprintln
panic
panicln
print
println
sprint
sprintln
errorf
fatalf
fprintf
panicf
printf
sprintf

以字符串格式化功能來(lái)區(qū)分,打印函數(shù)可以分為可打印格式化字符串的打印函數(shù)(以下簡(jiǎn)稱(chēng)格式化打印函數(shù))和非格式化打印函數(shù)。對(duì)于格式化打印函數(shù)來(lái)說(shuō),其第一個(gè)參數(shù)必是格式化表達(dá)式,也可被稱(chēng)為模板字符串。而其余參數(shù)應(yīng)該為需要被填入模板字符串的變量。像這樣:

fmt.Printf("Hello, %s!\n", "Harry") 
// 會(huì)輸出:Hello, Harry!

而非格式化打印函數(shù)的參數(shù)則是一個(gè)或多個(gè)要打印的內(nèi)容。比如:

fmt.Println("Hello,", "Harry!") 
// 會(huì)輸出:Hello, Harry!

以指定輸出目的地功能區(qū)分,打印函數(shù)可以被分為可自定義輸出目的地的的打印函數(shù)(以下簡(jiǎn)稱(chēng)自定義輸出打印函數(shù))和標(biāo)準(zhǔn)輸出打印函數(shù)。對(duì)于自定義輸出打印函數(shù)來(lái)說(shuō),其第一個(gè)函數(shù)必是其打印的輸出目的地。比如:

fmt.Fprintf(os.Stdout, "Hello, %s!\n", "Harry")
// 會(huì)在標(biāo)準(zhǔn)輸出設(shè)備上輸出:Hello, Harry!

上面示例中的函數(shù)fmt.Fprintf既能夠讓我們自定義打印的輸出目的地,又能夠格式化字符串。此類(lèi)打印函數(shù)的第一個(gè)參數(shù)的類(lèi)型應(yīng)為io.Writer接口類(lèi)型。只要某個(gè)類(lèi)型實(shí)現(xiàn)了該接口類(lèi)型中的所有方法,就可以作為函數(shù)Fprintf的第一個(gè)參數(shù)。例如,我們還可以使用代碼包bytes中的結(jié)構(gòu)體Buffer來(lái)接收打印函數(shù)打印的內(nèi)容。像這樣:

var buff bytes.Buffer
fmt.Fprintf(&buff, "Hello, %s!\n", "Harry")
fmt.Print("Buffer content:", buff.String())
// 會(huì)在標(biāo)準(zhǔn)輸出設(shè)備上輸出:Buffer content: Hello, Harry!

而標(biāo)準(zhǔn)輸出打印函數(shù)則只能將打印內(nèi)容到標(biāo)準(zhǔn)輸出設(shè)備上。就像函數(shù)fmt.Printffmt.Println所做的那樣。

檢查程序會(huì)首先關(guān)注打印函數(shù)的參數(shù)數(shù)量。如果參數(shù)數(shù)量不足,則可以認(rèn)為在當(dāng)前調(diào)用打印函數(shù)的語(yǔ)句中并不會(huì)出現(xiàn)用法錯(cuò)誤。所以,檢查程序會(huì)忽略對(duì)它的檢查。檢查程序中對(duì)打印函數(shù)的最小參數(shù)是這樣定義的:對(duì)于可以自定義輸出的打印函數(shù)來(lái)說(shuō),最小參數(shù)數(shù)量為2,其它打印函數(shù)的最小參數(shù)數(shù)量為1。如果打印函數(shù)的實(shí)際參數(shù)數(shù)量小于對(duì)應(yīng)的最小參數(shù)數(shù)量,就會(huì)被判定為參數(shù)數(shù)量不足。

對(duì)于格式化打印函數(shù),檢查程序會(huì)進(jìn)行如下檢查:

  1. 如果格式化字符串無(wú)法被轉(zhuǎn)換為基本字面量(標(biāo)識(shí)符以及用于表示int類(lèi)型值、float類(lèi)型值、char類(lèi)型值、string類(lèi)型值的字面量等),則檢查程序會(huì)忽略剩余的檢查。如果-v標(biāo)記有效,則會(huì)在忽略檢查前打印錯(cuò)誤信息。另外,格式化打印函數(shù)的格式化字符串必須是字符串類(lèi)型的。因此,如果對(duì)應(yīng)位置上的參數(shù)的類(lèi)型不是字符串類(lèi)型,那么檢查程序會(huì)立即打印錯(cuò)誤信息,并設(shè)置退出代碼為1。實(shí)際上,這個(gè)問(wèn)題已經(jīng)可以引起一個(gè)編譯錯(cuò)誤了。

  2. 如果格式化字符串中不包含動(dòng)詞(verbs),而格式化字符串后又有多余的參數(shù),則檢查程序會(huì)立即打印錯(cuò)誤信息,并設(shè)置退出代碼為1,且忽略后續(xù)檢查。我現(xiàn)在舉個(gè)例子。我們拿之前的一個(gè)示例作為基礎(chǔ),即:

    fmt.Printf("Hello, %s!\n", "Harry")

在這個(gè)示例中,格式化字符串中的“%s”就是我們所說(shuō)的動(dòng)詞,“%”就是動(dòng)詞的前導(dǎo)符。它相當(dāng)于一個(gè)需要被填的空。一般情況下,在格式化字符串中被填的空的數(shù)量應(yīng)該與后續(xù)參數(shù)的數(shù)量相同。但是可以出現(xiàn)在格式化字符串中沒(méi)有動(dòng)詞并且在格式化字符串之后沒(méi)有額外參數(shù)的情況。在這種情況下,該格式化打印函數(shù)就相當(dāng)于一個(gè)非格式化打印函數(shù)。例如,下面這個(gè)語(yǔ)句會(huì)導(dǎo)致此步檢查不通過(guò):

fmt.Printf("Hello!\n", "Harry") 
  1. 檢查程序還會(huì)檢查動(dòng)詞的格式。這部分檢查會(huì)非常嚴(yán)格。檢查程序?qū)τ诟袷交址袆?dòng)詞的格式要求如表0-19。表中對(duì)每個(gè)動(dòng)詞只進(jìn)行了簡(jiǎn)要的說(shuō)明。讀者可以查看標(biāo)準(zhǔn)庫(kù)代碼包fmt的文檔以了解關(guān)于它們的詳細(xì)信息。命令程序會(huì)按照表5-19中的要求對(duì)格式化及其后續(xù)參數(shù)進(jìn)行檢查。如上表所示,這部分檢查分為兩步驟。第一個(gè)步驟是檢查格式化字符串中的動(dòng)詞上是否附加了不合法的標(biāo)記,第二個(gè)步驟是檢查格式化字符串中的動(dòng)詞與后續(xù)對(duì)應(yīng)的參數(shù)的類(lèi)型是否匹配。只要檢查出問(wèn)題,檢查程序就會(huì)打印出錯(cuò)誤信息并且設(shè)置退出代碼為1。

  2. 如果格式化字符串中的動(dòng)詞不被支持,則檢查程序同樣會(huì)打印錯(cuò)誤信息后,并設(shè)置退出代碼為1。

表0-19 格式化字符串中動(dòng)詞的格式要求

動(dòng)詞 合法的附加標(biāo)記 允許的參數(shù)類(lèi)型 簡(jiǎn)要說(shuō)明
b “ ”,“-”,“+”,“.”和“0” int或float 用于二進(jìn)制表示法。
c “-” rune或int 用于單個(gè)字符的Unicode表示法。
d “ ”,“-”,“+”,“.”和“0” int 用于十進(jìn)制表示法。
e “ ”,“-”,“+”,“.”和“0” float 用于科學(xué)記數(shù)法。
E “ ”,“-”,“+”,“.”和“0” float 用于科學(xué)記數(shù)法。
f “ ”,“-”,“+”,“.”和“0” float 用于控制浮點(diǎn)數(shù)精度。
F “ ”,“-”,“+”,“.”和“0” float 用于控制浮點(diǎn)數(shù)精度。
g “ ”,“-”,“+”,“.”和“0” float 用于壓縮浮點(diǎn)數(shù)輸出。
G “ ”,“-”,“+”,“.”和“0” float 用于動(dòng)態(tài)選擇浮點(diǎn)數(shù)輸出格式。
o “ ”,“-”,“+”,“.”,“0”和“#” int 用于八進(jìn)制表示法。
p “-”和“#” pointer 用于表示指針地址。
q “ ”,“-”,“+”,“.”,“0”和“#” rune,int或string 用于生成帶雙引號(hào)的字符串形式的內(nèi)容。
s “ ”,“-”,“+”,“.”和“0” rune,int或string 用于生成字符串形式的內(nèi)容。
t “-” bool 用于生成與布爾類(lèi)型對(duì)應(yīng)的字符串值。(“true”或“false”)
T “-” 任何類(lèi)型 用于用Go語(yǔ)法表示任何值的類(lèi)型。
U “-”和“#” rune或int 用于針對(duì)Unicode的表示法。
v “”,“-”,“+”,“.”,“0”和“#” 任何類(lèi)型 以默認(rèn)格式格式化任何值。
x “”,“-”,“+”,“.”,“0”和“#” rune,int或string 以十六進(jìn)制、全小寫(xiě)的形式格式化每個(gè)字節(jié)。
X “”,“-”,“+”,“.”,“0”和“#” rune,int或string 以十六進(jìn)制、全大寫(xiě)的形式格式化每個(gè)字節(jié)。

對(duì)于非格式化打印函數(shù),檢查程序會(huì)進(jìn)行如下檢查:

  1. 如果打印函數(shù)不是可以自定義輸出的打印函數(shù),那么其第一個(gè)參數(shù)就不能是標(biāo)準(zhǔn)輸出os.Stdout或者標(biāo)準(zhǔn)錯(cuò)誤輸出os.Stderr。否則,檢查程序?qū)⒋蛴″e(cuò)誤信息并設(shè)置退出代碼為1。這主要是為了防止程序編寫(xiě)人員的筆誤。比如,他們可能會(huì)把函數(shù)fmt.Println當(dāng)作函數(shù)fmt.Printf來(lái)用。

  2. 如果打印函數(shù)是不自帶換行的,比如fmt.Printffmt.Print,則它必須只少有一個(gè)參數(shù)。否則,檢查程序?qū)⒋蛴″e(cuò)誤信息并設(shè)置退出代碼為1。像這樣的調(diào)用打印函數(shù)的語(yǔ)句是沒(méi)有任何意義的。并且,如果這個(gè)打印函數(shù)還是一個(gè)格式化打印函數(shù),那么這還會(huì)引起一個(gè)編譯錯(cuò)誤。需要注意的是,函數(shù)名稱(chēng)為Error的方法不會(huì)在被檢查之列。比如,標(biāo)準(zhǔn)庫(kù)代碼包testing中的結(jié)構(gòu)體類(lèi)型TB的方法Error。這是因?yàn)樗鼈兛赡軐?shí)現(xiàn)了接口類(lèi)型Error。這個(gè)接口類(lèi)型中唯一的方法Error無(wú)需任何參數(shù)。

  3. 如果第一個(gè)參數(shù)的值為字符串類(lèi)型的字面量且?guī)в懈袷交址胁艖?yīng)該有的動(dòng)詞的前導(dǎo)符“%”,則檢查程序會(huì)打印錯(cuò)誤信息并設(shè)置退出代碼為1。因?yàn)榉歉袷交蛴『瘮?shù)中不應(yīng)該出現(xiàn)格式化字符串。

  4. 如果打印函數(shù)是自帶換行的,那么在打印內(nèi)容的末尾就不應(yīng)該有換行符“\n”。否則,檢查程序會(huì)打印錯(cuò)誤信息并設(shè)置退出代碼為1。換句話說(shuō),檢查程序認(rèn)為程序中如果出現(xiàn)這樣的代碼:

    fmt.Println("Hello!\n")

常常是由于程序編寫(xiě)人員的筆誤。實(shí)際上,事實(shí)確實(shí)如此。如果我們確實(shí)想連續(xù)輸入多個(gè)換行,應(yīng)該這樣寫(xiě):

fmt.Println("Hello!")
fmt.Println()

至此,我們?cè)敿?xì)介紹了go tool vet命令中的檢查程序?qū)Υ蛴『瘮?shù)的所有步驟和內(nèi)容。打印函數(shù)的功能非常簡(jiǎn)單,但是go tool vet命令對(duì)它的檢查卻很細(xì)致。從中我們可以領(lǐng)會(huì)到一些關(guān)于打印函數(shù)的最佳實(shí)踐。

-rangeloops標(biāo)記

如果標(biāo)記-rangeloop有效(標(biāo)記值不為false),那么命令程序會(huì)對(duì)使用range進(jìn)行迭代的for代碼塊進(jìn)行檢查。我們之前提到過(guò),使用for語(yǔ)句需要注意兩點(diǎn):

  1. 不要在go代碼塊中處理在迭代過(guò)程中被賦予值的迭代變量。比如:

    mySlice := []string{"A", "B", "C"} for index, value := range mySlice { go func() { fmt.Printf("Index: %d, Value: %s\n", index, value) }() }

在Go語(yǔ)言的并發(fā)編程模型中,并沒(méi)有線程的概念,但卻有一個(gè)特有的概念——Goroutine。Goroutine也可被稱(chēng)為Go例程或簡(jiǎn)稱(chēng)為Go程。關(guān)于Goroutine的詳細(xì)介紹在第6章和第7章。我們現(xiàn)在只需要知道它是一個(gè)可以被并發(fā)執(zhí)行的代碼塊。

  1. 不要在defer語(yǔ)句的延遲函數(shù)中處理在迭代過(guò)程中被賦予值的迭代變量。比如:

    myDict := make(map[string]int) myDict["A"] = 1 myDict["B"] = 2 myDict["C"] = 3 for key, value := range myDict { defer func() { fmt.Printf("Key: %s, Value: %d\n", key, value) }() }

其實(shí),上述兩點(diǎn)所關(guān)注的問(wèn)題是相同的,那就是不要在可能被延遲處理的代碼塊中直接使用迭代變量。go代碼塊和defer代碼塊都有這樣的特質(zhì)。這是因?yàn)榈鹊絞o函數(shù)(跟在go關(guān)鍵字之后的那個(gè)函數(shù))或延遲函數(shù)真正被執(zhí)行的時(shí)候,這些迭代變量的值可能已經(jīng)不是我們想要的值了。

另一方面,當(dāng)檢查程序發(fā)現(xiàn)在帶有range子句的for代碼塊中迭代出的數(shù)據(jù)并沒(méi)有賦值給標(biāo)識(shí)符所代表的變量時(shí),則會(huì)忽略對(duì)這一代碼塊的檢查。比如像這樣的代碼:

func nonIdentRange(slc []string) {
    l := len(slc)
    temp := make([]string, l)
    l--
    for _, temp[l] = range slc {
        // 忽略了使用切片值temp的代碼。
        if l > 0 {
            l--
        }
    }
}

就不會(huì)受到檢查程序的關(guān)注。另外,當(dāng)被迭代的對(duì)象的大小為0時(shí),for代碼塊也不會(huì)被檢查。

據(jù)此,我們知道如果在可能被延遲處理的代碼塊中直接使用迭代中的臨時(shí)變量,那么就可能會(huì)造成與編程人員意圖不相符的結(jié)果。如果由此問(wèn)題使程序的最終結(jié)果出現(xiàn)偏差甚至使程序報(bào)錯(cuò)的話,那么看起來(lái)就會(huì)非常詭異。這種隱晦的錯(cuò)誤在排查時(shí)也是非常困難的。這種不正確的代碼編寫(xiě)方式應(yīng)該徹底被避免。這也是檢查程序?qū)Φa塊進(jìn)行檢查的最終目的。如果檢查程序發(fā)現(xiàn)了上述的不正確的代碼編寫(xiě)方式,就會(huì)打印出錯(cuò)誤信息以提醒編程人員。

-structtags標(biāo)記

如果標(biāo)記``-structtags有效(標(biāo)記值不為false```),那么命令程序會(huì)對(duì)結(jié)構(gòu)體類(lèi)型的字段的標(biāo)簽進(jìn)行檢查。我們先來(lái)看下面的代碼:

type Person struct {
    XMLName xml.Name    `xml:"person"`
    Id          int     `xml:"id,attr"`
    FirstName   string  `xml:"name>first"`
    LastName    string  `xml:"name>last"`
    Age         int     `xml:"age"`
    Height      float32 `xml:"height,omitempty"`
    Married     bool
    Address
    Comment     string  `xml:",comment"`
}

在上面的例子中,在結(jié)構(gòu)體類(lèi)型的字段聲明后面的那些字符串形式的內(nèi)容就是結(jié)構(gòu)體類(lèi)型的字段的標(biāo)簽。對(duì)于Go語(yǔ)言本身來(lái)說(shuō),結(jié)構(gòu)體類(lèi)型的字段標(biāo)簽就是注釋?zhuān)鼈兪强蛇x的,且會(huì)被Go語(yǔ)言的運(yùn)行時(shí)系統(tǒng)忽略。但是,這些標(biāo)簽可以通過(guò)標(biāo)準(zhǔn)庫(kù)代碼包reflect中的程序訪問(wèn)到。因此,不同的代碼包中的程序可能會(huì)賦予這些結(jié)構(gòu)體類(lèi)型的字段標(biāo)簽以不同的含義。比如上面例子中的結(jié)構(gòu)體類(lèi)型的字段標(biāo)簽就對(duì)代碼包encoding/xml中的程序非常有用處。

嚴(yán)格來(lái)講,結(jié)構(gòu)體類(lèi)型的字段的標(biāo)簽應(yīng)該滿(mǎn)足如下要求:

  1. 標(biāo)簽應(yīng)該包含鍵和值,且它們之間要用英文冒號(hào)分隔。

  2. 標(biāo)簽的鍵應(yīng)該不包含空格、引號(hào)或冒號(hào)。

  3. 標(biāo)簽的值應(yīng)該被英文雙引號(hào)包含。

  4. 如果標(biāo)簽內(nèi)容符合了第3條,那么標(biāo)簽的全部?jī)?nèi)容應(yīng)該被反引號(hào)“`”包含。否則它需要被雙引號(hào)包含。

  5. 標(biāo)簽可以包含多個(gè)鍵值對(duì),其它們之間要用空格“ ”分隔。例如:key:"value" _gofix:"_magic"

檢查程序首先會(huì)對(duì)結(jié)構(gòu)體類(lèi)型的字段標(biāo)簽的內(nèi)容做去引號(hào)處理,也就是把最外面的雙引號(hào)或者反引號(hào)去除。如果去除失敗,則檢查程序會(huì)打印錯(cuò)誤信息并設(shè)置退出代碼為1,同時(shí)忽略后續(xù)檢查。如果去引號(hào)處理成功,檢查程序則會(huì)根據(jù)前面的規(guī)則對(duì)標(biāo)簽的內(nèi)容進(jìn)行檢查。如果檢查出問(wèn)題,檢查程序同樣會(huì)打印出錯(cuò)誤信息并設(shè)置退出代碼為1。

-unreachable標(biāo)記

如果標(biāo)記``-unreachable有效(標(biāo)記值不為false```),那么命令程序會(huì)在函數(shù)或方法定義中查找死代碼。死代碼就是永遠(yuǎn)不會(huì)被訪問(wèn)到的代碼。例如:

func deadCode1() int {
    print(1)
    return 2
    println() // 這里存在死代碼
}

在上面示例中,函數(shù)deadCode1中的最后一行調(diào)用打印函數(shù)的語(yǔ)句就是死代碼。檢查程序如果在函數(shù)或方法中找到死代碼,則會(huì)打印錯(cuò)誤信息以提醒編碼人員。我們把這段代碼放到命令源碼文件deadcode_demo.go中,并在main函數(shù)中調(diào)用它?,F(xiàn)在,如果我們編譯這個(gè)命令源碼文件會(huì)馬上看到一個(gè)編譯錯(cuò)誤:“missing return at end of function”。顯然,這個(gè)錯(cuò)誤側(cè)面的提醒了我們,在這個(gè)函數(shù)中存在死代碼。實(shí)際上,我們?cè)谛拚@個(gè)問(wèn)題之前它根本就不可能被運(yùn)行,所以也就不存在任何隱患。但是,如果在這個(gè)函數(shù)不需要結(jié)果的情況下又會(huì)如何呢?我們稍微改造一下上面這個(gè)函數(shù):

func deadCode1() {
    print(1)
    return
    println() // 這里存在死代碼
}

好了,我們現(xiàn)在把函數(shù)deadcode1的聲明中的結(jié)果聲明和函數(shù)中return語(yǔ)句后的數(shù)字都去掉了。不幸的是,當(dāng)我們?cè)俅尉幾g文件時(shí)沒(méi)有看到任何報(bào)錯(cuò)。但是,這里確實(shí)存在死代碼。在這種情況下,編譯器并不能幫助我們找到問(wèn)題,而go tool vet命令卻可以。

hc@ubt:~$ go tool vet deadcode_demo.go
deadcode_demo.go:10: unreachable code

go tool vet命令中的檢查程序?qū)τ谒来a的判定有幾個(gè)依據(jù),如下:

  1. 在這里,我們把return語(yǔ)句、goto語(yǔ)句、break語(yǔ)句、continue語(yǔ)句和panic函數(shù)調(diào)用語(yǔ)句都叫做流程中斷語(yǔ)句。如果在當(dāng)前函數(shù)、方法或流程控制代碼塊的分支中的流程中斷語(yǔ)句的后面還存在其他語(yǔ)句或代碼塊,比如:

    func deadCode2() { print(1) panic(2) println() // 這里存在死代碼 }

    func deadCode3() { L: { print(1) goto L } println() // 這里存在死代碼 }

    func deadCode4() { print(1) return { // 這里存在死代碼 } }

則后面的語(yǔ)句或代碼塊就會(huì)被判定為死代碼。但檢查程序僅會(huì)在錯(cuò)誤提示信息中包含第一行死代碼的位置。

  1. 如果帶有elseif代碼塊中的每一個(gè)分支的最后一條語(yǔ)句均為流程中斷語(yǔ)句,則在此流程控制代碼塊后的代碼都被判定為死代碼。比如:

    func deadCode5(x int) { print(1) if x == 1 { panic(2) } else { return } println() // 這里存在死代碼 }

注意,只要其中一個(gè)分支不包含流程中斷語(yǔ)句,就不能判定后面的代碼為死代碼。像這樣:

func deadCode5(x int) {
    print(1)
    if x == 1 {
        panic(2)
    } else if x == 2 {
        return
    } 
    println() // 這里并不是死代碼
}
  1. 如果在一個(gè)沒(méi)有顯式中斷條件或中斷語(yǔ)句的for代碼塊后面還存在其它語(yǔ)句,則這些語(yǔ)句將會(huì)被判定為死代碼。比如:

    func deadCode6() { for { for { break } } println() // 這里存在死代碼 }

func deadCode7() {
    for {
        for {
        }
        break // 這里存在死代碼
    }
    println()
}

而我們對(duì)這兩個(gè)函數(shù)稍加改造后,就會(huì)消除go tool vet命令發(fā)出的死代碼告警。如下:

func deadCode6() {
    x := 1
    for x == 1 {
        for {
            break
        }
    }
    println() // 這里存在死代碼
}

以及

func deadCode7() {
    x := 1
    for {
        for x == 1 {
        }
        break // 這里存在死代碼
    }
    println()
}

我們只是加了一個(gè)顯式的中斷條件就能夠使之通過(guò)死代碼檢查。但是,請(qǐng)注意!這兩個(gè)函數(shù)中在被改造后仍然都包含死循環(huán)代碼!這說(shuō)明檢查程序并不對(duì)中斷條件的邏輯進(jìn)行檢查。

  1. 如果select代碼塊的所有case中的最后一條語(yǔ)句均為流程中斷語(yǔ)句(break語(yǔ)句除外),那么在select代碼塊后面的語(yǔ)句都會(huì)被判定為死代碼。比如:

    func deadCode8(c chan int) { print(1) select { case <-c: print(2) panic(3) } println() // 這里存在死代碼 }

func deadCode9(c chan int) {
L:
    print(1)
    select {
    case <-c:
        print(2)
        panic(3)
    case c <- 1:
        print(4)
        goto L
    }
    println() // 這里存在死代碼
}

另外,在空的select語(yǔ)句塊之后的代碼也會(huì)被認(rèn)為是死代碼。比如:

func deadCode10() {
    print(1)
    select {}
    println() // 這里存在死代碼
}

func deadCode11(c chan int) {
    print(1)
    select {
    case <-c:
        print(2)
        panic(3)
    default:
        select {}
    }
    println() // 這里存在死代碼
}

上面這兩個(gè)示例中的語(yǔ)句select {}都會(huì)引發(fā)一個(gè)運(yùn)行時(shí)錯(cuò)誤:“fatal error: all goroutines are asleep - deadlock!”。這就是死鎖!關(guān)于這個(gè)錯(cuò)誤的詳細(xì)說(shuō)明在第7章。

  1. 如果switch代碼塊的所有casedefault case中的最后一條語(yǔ)句均為流程中斷語(yǔ)句(除了break語(yǔ)句),那么在switch代碼塊后面的語(yǔ)句都會(huì)被判定為死代碼。比如:

    func deadCode14(x int) { print(1) switch x { case 1: print(2) panic(3) default: return } println(4) // 這里存在死代碼 }

我們知道,關(guān)鍵字fallthrough可以使流程從switch代碼塊中的一個(gè)case轉(zhuǎn)移到下一個(gè)casedefault case。死代碼也可能由此產(chǎn)生。例如:

func deadCode15(x int) {
    print(1)
    switch x {
    case 1:
        print(2)
        fallthrough
    default:
        return
    }
    println(3) // 這里存在死代碼
}

在上面的示例中,第一個(gè)case總會(huì)把流程轉(zhuǎn)移到第二個(gè)case,而第二個(gè)case中的最后一條語(yǔ)句為return語(yǔ)句,所以流程永遠(yuǎn)不會(huì)轉(zhuǎn)移到語(yǔ)句println(3)上。因此,println(3)語(yǔ)句會(huì)被判定為死代碼。如果我們把fallthrough語(yǔ)句去掉,那么就可以消除這個(gè)死代碼判定。實(shí)際上,只要某一個(gè)case或者default case中的最后一條語(yǔ)句是break語(yǔ)句,就不會(huì)有死代碼的存在。當(dāng)然,這個(gè)break語(yǔ)句本身不能是死代碼。另外,與select代碼塊不同的是,空的switch代碼塊并不會(huì)使它后面的代碼成為死代碼。

綜上所述,死代碼的判定雖然看似比較復(fù)雜,但其實(shí)還是有原則可循的。我們應(yīng)該在編碼過(guò)程中就避免編寫(xiě)可能會(huì)造成死代碼的代碼。如果我們實(shí)在不確定死代碼是否存在,也可以使用go tool vet命令來(lái)檢查。不過(guò),需要提醒讀者的是,不存在死代碼并不意味著不存在造成死循環(huán)的代碼。當(dāng)然,造成死循環(huán)的代碼也并不一定就是錯(cuò)誤的代碼。但我們?nèi)匀恍枰獙?duì)此保持警覺(jué)。

-asmdecl標(biāo)記

如果標(biāo)記``-asmdecl有效(標(biāo)記值不為false```),那么命令程序會(huì)對(duì)匯編語(yǔ)言的源碼文件進(jìn)行檢查。對(duì)匯編語(yǔ)言源碼文件及相應(yīng)編寫(xiě)規(guī)則的解讀已經(jīng)超出了本書(shū)的范圍,所以我們并不在這里對(duì)此項(xiàng)檢查進(jìn)行描述。如果讀者有興趣的話,可以查看此項(xiàng)檢查的程序的源碼文件asmdecl.go。它在Go語(yǔ)言安裝目錄的子目錄src/cmd/vet下。

至此,我們對(duì)go vet命令和go tool vet命令進(jìn)行了全面詳細(xì)的介紹。之所以花費(fèi)如此大的篇幅來(lái)介紹這兩個(gè)命令,不僅僅是為了介紹此命令的使用方法,更是因?yàn)榇嗣畛绦虻臋z查工作涉及到了很多我們?cè)诰帉?xiě)Go語(yǔ)言代碼時(shí)需要避免的“坑”。由此我們也可以知曉應(yīng)該怎樣正確的編寫(xiě)Go語(yǔ)言代碼。同時(shí),我們也應(yīng)該在開(kāi)發(fā)Go語(yǔ)言程序的過(guò)程中經(jīng)常使用go tool vet命來(lái)檢查代碼。

上一篇:go list下一篇:go test