命令go fix會把指定代碼包的所有Go語言源碼文件中的舊版本代碼修正為新版本的代碼。這里所說的版本即Go語言的版本。代碼包的所有Go語言源碼文件不包括其子代碼包(如果有的話)中的文件。修正操作包括把對舊程序調(diào)用的代碼更換為對新程序調(diào)用的代碼、把舊的語法更換為新的語法,等等。
這個工具其實非常有用。在編程語言的升級和演進的過程中,難免會對過時的和不夠優(yōu)秀的語法及標(biāo)準(zhǔn)庫進行改進。這樣的改進對于編程語言的向后兼容性是個挑戰(zhàn)。我們在前面提到過向后兼容這個詞。簡單來說,向后兼容性就是指新版本的編程語言程序能夠正確識別和解析用該編程語言的舊版本編寫的程序和軟件,以及在新版本的編程語言的運行時環(huán)境中能夠運行用該編程語言的舊版本編寫的程序和軟件。對于Go語言來說,語法的改變和標(biāo)準(zhǔn)庫的變更都會使得用舊版本編寫的程序無法在新版本環(huán)境中編譯通過。這就等于破壞了Go語言的向后兼容性。對于一個編程語言、程序庫或基礎(chǔ)軟件來說,向后兼容性是非常重要的。但有時候為了讓軟件更加優(yōu)秀,軟件的開發(fā)者或維護者不得不在向后兼容性上做出一些妥協(xié)。這是一個在多方利益之間進行權(quán)衡的結(jié)果。本小節(jié)所講述的工具正是Go語言的創(chuàng)造者們?yōu)榱瞬蛔屵@種妥協(xié)給語言使用者帶來困擾和額外的工作量而編寫的自動化修正工具。這也充分體現(xiàn)了Go語言的軟件工程哲學(xué)。下面讓我們來詳細了解它們的使用方法和內(nèi)部機理。
命令go fix其實是命令go tool fix的簡單封裝。這甚至比go fmt命令對gofmt命令的封裝更簡單。像其它的Go命令一樣,go fix命令會先對作為參數(shù)的代碼包導(dǎo)入路徑進行驗證,以確保它是正確有效的。像在本小節(jié)開始處描述的那樣,go fix命令會把有效代碼包中的所有Go語言源碼文件作為多個參數(shù)傳遞給go tool fix命令。實際上,go fix命令本身不接受任何標(biāo)記,它會把加入的所有標(biāo)記都原樣傳遞給go tool fix命令。go tool fix命令可接受的標(biāo)記如下表。
表0-15 go tool fix命令的標(biāo)記說明
|
標(biāo)記名稱
|
標(biāo)記描述
|
|---|
|
-diff
|
不將修正后的內(nèi)容寫入文件,而只打印修正前后的內(nèi)容的對比信息到標(biāo)準(zhǔn)輸出。
|
|
-r
|
只對目標(biāo)源碼文件做有限的修正操作。該標(biāo)記的值即為允許的修正操作的名稱。多個名稱之間用英文半角逗號分隔。
|
|
-force
|
使用此標(biāo)記后,即使源碼文件中的代碼已經(jīng)與Go語言的最新版本相匹配了,也會強行執(zhí)行指定的修正操作。該標(biāo)記的值就是需要強行執(zhí)行的修正操作的名稱,多個名稱之間用英文半角逗號分隔。
|
在默認情況下,```go tool fix```命令程序會在目標(biāo)源碼文件上執(zhí)行所有的修正操作。多個修正操作的執(zhí)行會按照每個修正操作中標(biāo)示的操作建立日期以從早到晚的順序進行。我們可以通過執(zhí)行```go tool fix -?```來查看```go tool fix```命令的使用說明以及當(dāng)前支持的修正操作。
與本書對應(yīng)的Go語言版本的```go tool fix```命令目前只支持兩個修正操作。一個是與標(biāo)準(zhǔn)庫代碼包```go/printer```中的結(jié)構(gòu)體類型```Config```的初始化代碼相關(guān)的修正操作,另一個是與標(biāo)準(zhǔn)庫代碼包``net```中的結(jié)構(gòu)體類型```IPAddr```、```UDPAddr```和```TCPAddr```的初始化代碼相關(guān)的修正操作。從修正操作的數(shù)量來看,自第一個正式版發(fā)布以來,Go語言的向后兼容性還是很好的。從Go語言官網(wǎng)上的說明也可以獲知,在Go語言的第二個大版本(Go 2.x)出現(xiàn)之前,它會一直良好的向后兼容性。
值得一提的是,上述的修正操作都是依靠Go語言的標(biāo)準(zhǔn)庫代碼包```go```及其子包中提供的功能來完成的。實際上,```go tool fix```命令程序在執(zhí)行修正操作之前,需要先將目標(biāo)源碼文件中的內(nèi)容解析為一個抽象語法樹實例。這一功能其實就是由代碼包```go/parser```提供的。而在這個抽象語法樹實例中的各個元素的結(jié)構(gòu)體類型的定義以及檢測、訪問和修改它們的方法則由代碼包```go/ast```提供。有興趣的讀者可以閱讀這些代碼包中的代碼。這對于深入理解Go語言對代碼的靜態(tài)處理過程是非常有好處的。
回到正題。與```gofmt```命令相同,```go tool fix```命令也有交互模式。我們同樣可以通過執(zhí)行不帶任何參數(shù)的命令來進入到這個模式。但是與```gofmt```命令不同的是,我們在```go tool fix```命令的交互模式中輸入的代碼必須是完整的,即必須要符合Go語言源碼文件的代碼組織形式。當(dāng)我們輸入了不完整的代碼片段時,命令程序?qū)@示錯誤提示信息并退出。示例如下:
hc@ubt:~$ go tool fix -r='netipv6zone'
a := &net.TCPAddr{ip4, 8080}
standard input:1:1: expected 'package', found 'IDENT' a
相對于上面的示例,我們必須要這樣輸入源碼才能獲得正常的結(jié)果:
hc@ubt:~$ go tool fix -r='netipv6zone'
package main
import (
"fmt"
"net"
)
func main() {
addr := net.TCPAddr{"127.0.0.1", 8080}
fmt.Printf("TCP Addr: %s\n", addr)
}
standard input: fixed netipv6zone
package main
import (
"fmt"
"net"
)
func main() {
addr := net.TCPAddr{IP: "127.0.0.1", Port: 8080}
fmt.Printf("TCP Addr: %s\n", addr)
}
上述示例的輸出結(jié)果中有這樣一行提示信息:“standard input: fixed netipv6zone”。其中,“standard input”表明源碼是從標(biāo)準(zhǔn)輸入而不是源碼文件中獲取的,而“fixed netipv6zone”則表示名為netipv6zone的修正操作發(fā)現(xiàn)輸入的源碼中有需要修正的地方,并且已經(jīng)修正完畢。另外,我們還可以看到,輸出結(jié)果中的代碼已經(jīng)經(jīng)過了格式化。