go fix与go tool fix


命令go fix会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。这里所说的版本即Go语言的版本。代码包的所有Go语言源码文件不包括其子代码包(如果有的话)中的文件。修正操作包括把对旧程序调用的代码更换为对新程序调用的代码、把旧的语法更换为新的语法,等等。

这个工具其实非常有用。在编程语言的升级和演进的过程中,难免会对过时的和不够优秀的语法及标准库进行改进。这样的改进对于编程语言的向后兼容性是个挑战。我们在前面提到过向后兼容这个词。简单来说,向后兼容性就是指新版本的编程语言程序能够正确识别和解析用该编程语言的旧版本编写的程序和软件,以及在新版本的编程语言的运行时环境中能够运行用该编程语言的旧版本编写的程序和软件。对于Go语言来说,语法的改变和标准库的变更都会使得用旧版本编写的程序无法在新版本环境中编译通过。这就等于破坏了Go语言的向后兼容性。对于一个编程语言、程序库或基础软件来说,向后兼容性是非常重要的。但有时候为了让软件更加优秀,软件的开发者或维护者不得不在向后兼容性上做出一些妥协。这是一个在多方利益之间进行权衡的结果。本小节所讲述的工具正是Go语言的创造者们为了不让这种妥协给语言使用者带来困扰和额外的工作量而编写的自动化修正工具。这也充分体现了Go语言的软件工程哲学。下面让我们来详细了解它们的使用方法和内部机理。

命令go fix其实是命令go tool fix的简单封装。这甚至比go fmt命令对gofmt命令的封装更简单。像其它的Go命令一样,go fix命令会先对作为参数的代码包导入路径进行验证,以确保它是正确有效的。像在本小节开始处描述的那样,go fix命令会把有效代码包中的所有Go语言源码文件作为多个参数传递给go tool fix命令。实际上,go fix命令本身不接受任何标记,它会把加入的所有标记都原样传递给go tool fix命令。go tool fix命令可接受的标记如下表。

表0-15 go tool fix命令的标记说明

标记名称 标记描述
-diff 不将修正后的内容写入文件,而只打印修正前后的内容的对比信息到标准输出。
-r 只对目标源码文件做有限的修正操作。该标记的值即为允许的修正操作的名称。多个名称之间用英文半角逗号分隔。
-force 使用此标记后,即使源码文件中的代码已经与Go语言的最新版本相匹配了,也会强行执行指定的修正操作。该标记的值就是需要强行执行的修正操作的名称,多个名称之间用英文半角逗号分隔。

在默认情况下,go tool fix命令程序会在目标源码文件上执行所有的修正操作。多个修正操作的执行会按照每个修正操作中标示的操作建立日期以从早到晚的顺序进行。我们可以通过执行go tool fix -?来查看go tool fix命令的使用说明以及当前支持的修正操作。

与本书对应的Go语言版本的go tool fix命令目前只支持两个修正操作。一个是与标准库代码包go/printer中的结构体类型Config的初始化代码相关的修正操作,另一个是与标准库代码包``net中的结构体类型IPAddrUDPAddrTCPAddr```的初始化代码相关的修正操作。从修正操作的数量来看,自第一个正式版发布以来,Go语言的向后兼容性还是很好的。从Go语言官网上的说明也可以获知,在Go语言的第二个大版本(Go 2.x)出现之前,它会一直良好的向后兼容性。

值得一提的是,上述的修正操作都是依靠Go语言的标准库代码包go及其子包中提供的功能来完成的。实际上,go tool fix命令程序在执行修正操作之前,需要先将目标源码文件中的内容解析为一个抽象语法树实例。这一功能其实就是由代码包go/parser提供的。而在这个抽象语法树实例中的各个元素的结构体类型的定义以及检测、访问和修改它们的方法则由代码包go/ast提供。有兴趣的读者可以阅读这些代码包中的代码。这对于深入理解Go语言对代码的静态处理过程是非常有好处的。

回到正题。与gofmt命令相同,go tool fix命令也有交互模式。我们同样可以通过执行不带任何参数的命令来进入到这个模式。但是与gofmt命令不同的是,我们在go tool fix命令的交互模式中输入的代码必须是完整的,即必须要符合Go语言源码文件的代码组织形式。当我们输入了不完整的代码片段时,命令程序将显示错误提示信息并退出。示例如下:

hc@ubt:~$ go tool fix -r='netipv6zone'                    
a := &net.TCPAddr{ip4, 8080}
standard input:1:1: expected 'package', found 'IDENT' a

相对于上面的示例,我们必须要这样输入源码才能获得正常的结果:

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)
}

上述示例的输出结果中有这样一行提示信息:“standard input: fixed netipv6zone”。其中,“standard input”表明源码是从标准输入而不是源码文件中获取的,而“fixed netipv6zone”则表示名为netipv6zone的修正操作发现输入的源码中有需要修正的地方,并且已经修正完毕。另外,我们还可以看到,输出结果中的代码已经经过了格式化。