6.2 使用gofmt
截至Go 1.16稳定版,gofmt工具一直是放置在Go安装包中与Go编译器工具一并发布的,这足以说明gofmt工具的重要程度。
gofmt保持了Go语言“简单”的设计哲学,这点通过其帮助手册即可看出:
$ gofmt -help usage: gofmt [flags] [path ...] -cpuprofile string write cpu profile to this file -d display diffs instead of rewriting files -e report all errors (not just the first 10 on different lines) -l list files whose formatting differs from gofmt's -r string rewrite rule (e.g., 'a[b:len(a)] -> a[b:]') -s simplify code -w write result to (source) file instead of stdout
gofmt最大的特点是没有提供任何关于代码风格设置的命令行选项和参数,这样Go开发人员就无法通过设置命令行特定选项来定制自己喜好的风格。不过gofmt却提供了足够在工程上对代码进行按格式查找、代码重构的命令行选项。我们来看一些gofmt的实用技巧。
1. 使用gofmt -s选项简化代码
虽然Go语言推崇一件事情仅有一种方式完成,但难免存在一些事情依然有多种表达方法,比如下面这个例子。
存在一个字符串切片v:
v := []string{...}
如果要迭代访问字符串切片v的各个元素,可以这么做:
for _ = range v { ... }
在Go 1.4及后续版本中,还可以这么做:
for range v { ... }
Go开发者更推崇后面那种简化后的写法。这样的例子在Go语言的演进过程中还存在一些。为了避免将代码转换为简化语法给开发人员带来额外工作量,Go官方在gofmt中提供了-s选项。通过gofmt -s可以将遗留代码中的非简化代码自动转换为简化写法,并且没有副作用,因此一般“-s”选项都会是gofmt执行的默认选项。
2. 使用gofmt -r执行代码“微重构”
代码重构是软件工程过程中的日常操作,Go语言曾经为了支持大规模软件的全局重构加入了类型别名(type alias)语法。gofmt除了具有格式化代码的功能外,对代码重构也具有一定的支撑能力。我们可以通过-r命令行选项对代码进行表达式级别的替换,以达到重构的目的。
下面是-r选项的用法:
gofmt -r 'pattern -> replacement' [other flags] [path ...]
gofmt -r的原理就是在对源码进行重新格式化之前,搜索源码是否有可以匹配pattern的表达式,如果有,就将所有匹配到的结果替换为replacement表达式。gofmt要求pattern和replacement都是合法的Go表达式。比如:
$gofmt -r 'a[3:len(a)] -> a[3:]' -w chapter2/sources/gofmt_demo.go
上面gofmt -r命令执行的意图就是先将源码文件gofmt_demo.go中能与a[3:len(a)]匹配的代码替换为a[3:],然后重新格式化。因此上面的命令对下面的源码片段都可以成功匹配:
- fmt.Println(s[3:len(s)]) + fmt.Println(s[3:]) - n, err := s.r.Read(s.buf[3:len(s.buf)]) + n, err := s.r.Read(s.buf[3:]) - reverseLabels = append(reverseLabels, domain[3:len(domain)]) + reverseLabels = append(reverseLabels, domain[3:])
注意,上述命令中的a并不是一个具体的字符,而是代表的一个通配符。出现在'pattern -> replacement'中的小写字母都会被视为通配符。我们将pattern中的3改为字母b(通配符):
$gofmt -r 'a[b:len(a)] -> a[b:]' -w chapter2/sources/gofmt_demo.go
这样pattern匹配的范围就更大了:
- fmt.Println(s[3:len(s)]) + fmt.Println(s[3:]) - n, err := s.r.Read(s.buf[s.end:len(s.buf)]) + n, err := s.r.Read(s.buf[s.end:]) - reverseLabels = append(reverseLabels, domain[3:len(domain)]) + reverseLabels = append(reverseLabels, domain[3:]) - reverseLabels = append(reverseLabels, domain[i+1:len(domain)]) + reverseLabels = append(reverseLabels, domain[i+1:])
3. 使用gofmt -l按格式要求输出满足条件的文件列表
gofmt提供了-l选项,可以按格式要求输出满足条件的文件列表。比如,输出$GOROOT/src下所有不满足gofmt格式要求的文件列表(以Go 1.12.6版本为例):
$ gofmt -l $GOROOT/src $GOROOT/src/cmd/cgo/zdefaultcc.go $GOROOT/src/cmd/go/internal/cfg/zdefaultcc.go $GOROOT/src/cmd/go/internal/cfg/zosarch.go ... $GOROOT/src/go/build/zcgo.go
我们看到,即便是Go项目自身源码也有“漏网之鱼”,不过这可能是gofmt的格式化标准有过微调,而很多源文件没有及时调整导致的。
我们也可以将-r和-l结合起来使用,输出匹配到pattern的文件列表。比如查找$GOROOT/src下能匹配到'a[b:len(a)]' pattern的文件列表:
$ gofmt -r 'a[b:len(a)] -> a[b:]' -l $GOROOT/src $GOROOT/src/bufio/scan.go $GOROOT/src/crypto/x509/verify.go
不过要注意的是,如果某路径下有很多不符合gofmt格式的文件,这些文件也会被一并输出。