Go 语言代码检查

代码格式化

go fmt 在 Go 语言中用于格式化代码。格式化后,代码风格保持一致,更容易被维护。

格式化处理包括但不限于以下方面:

  • 缩进使用 tab 键而非空格。
  • 左大括号 { 总是放在与控制语句同一行末尾。
  • 在运算符和逗号后自动添加空格。
  • 确保导入包声明按字典顺序排列,并且格式化为组。
  • 自动删除多余空格和空行。

基本用法

命令基本格式为:go fmt [packages]。这里 [packages] 表示想要格式化的包路径。如果不指定包路径,go fmt 将作用于当前目录。

例如格式化当前目录及其子目录下所有 Go 文件:

go fmt ./...

通过 -x 参数可以看到,go fmt 实际上是对 gofmt -l -w 命令的简单封装:

PS D:\Software\Programming\Go\new> go fmt -x main.go
D:\Software\Program\go\go1.22.0\bin\gofmt.exe -l -w main.go

直接使用 gofmt 命令可实现更多功能,例如下面这些选项:

  • -w:修改后直接保存到文件。默认只打印格式化后结果。
  • -s:尝试简化代码。
  • -r:应用自定义替换规则来格式化代码。

简化代码

简化代码是指安全地减少代码冗余度和复杂度。例如下面代码中存在冗余的变量声明和没必要的索引方式:

package main

import "fmt"

func main() {
	var s string = "123"
	fmt.Println(s[0:len(s)])
}

通过 gofmt -s 命令简化代码效果如下:

D:\Software\Programming\Go>gofmt -s main.go
package main

import "fmt"

func main() {
        var s string = "123"
        fmt.Println(s[0:])
}

可以看到 s[0:len(s)] 被简化为 s[0:],但是 var s string = "123" 并没有被简化为 s := "123"var s = "123"。因为有些情况下必须显式声明变量类型,不能使用自动推断的类型,要判断具体情况估计逻辑太复杂,还得靠人工优化。

gofmt -s 尽管优化力度有限,但还是建议加入到日常开发中,例如配置到 GoLand 自动触发:

  1. 打开「设置」-「工具」-「File Watcher」,点击新建「custom」自定义模板;
  2. 「名称」填入:简化代码,「文件类型」选择「Go 文件」,「作用域」选择「当前文件」;
  3. 「程序」字段填入:gofmt,「实参」字段填入:-s -w $FilePathRelativeToProjectRoot$,「输出路径」填入:$FilePathRelativeToProjectRoot$。内置变量指代当前文件的绝对路径;
  4. 展开「高级选项」,取消勾选「自动保存编辑的文件以触发观察程序」,只用保持「进行外部更改时触发观察程序」。这样配置确保简化命令不会在自动保存时触发,免得与内置「代码样式」功能冲突;
  5. 点击「确定」按钮完成配置,测试效果。

配置后,GoLand 在每次手动执行代码格式化时,会自动调用简化代码命令。

替换规则

gofmt 最强大的功能要数自定义替换规则,gofmt -r 使用 Go 语言抽象语法树(AST)来进行模式匹配和替换,可以用于自动重构代码。

语法为:gofmt -r 'pattern -> replacement' -w [path ...]。说明如下:

  • pattern 是希望匹配的代码片段。
  • replacement 是用来替换的新代码片段。
  • 规则必须被单引号「'」包围。如果在 Windows 平台,则使用双引号「"」代替。
  • 可以同时应用多个替换规则,只需在引号内用逗号分隔每个规则。

最简单的用处是替换包名、常量名、类型名、结构体字段名、函数调用和方法调用,例如换掉已废弃的 ioutil.WriteFile 函数:gofmt -r "ioutil.WriteFile -> os.WriteFile" -w .

也可以将所有形如 a[b:len(a)] 的代码替换为 a[b:]gofmt -r "a[b:len(a)] -> a[b:]" -w *.go

导入维护

goimportsgofmt 增强版,除了有 gofmt 所有功能外,还能自动处理导入依赖:自动添加缺失与自动删除多余的 import 语句。

goimports 需要先下载安装:

go install golang.org/x/tools/cmd/goimports@latest

然后就像使用 gofmt 一样使用它:

goimports -w -l -s .

升级修复

每当 Go 语言版本更新时,都可能引入一些破坏性变更,go fix 命令用来帮助修复升级导致的问题代码。go fix 会检查代码中已弃用的语法或库函数,并尝试替换为新方法或函数。

go fix 用法非常简单,直接指定路径或文件即可:

go fix .

随着 Go 语言日趋稳定,go fix 已很少使用。前面提到 ioutil 包已标记弃用很久,包中函数被分散到其他包中,例如 ioutil.TempDir 被替换为 os.MkdirTemp,这种情况并不能用 go fix 来修复。

规范检查

一般 IDE 中都带有代码分析与审查功能,否则可以尝试使用 golint 来检查代码规范,并获得改进建议。

先通过 go install 来安装工具:

go install golang.org/x/lint/golint@latest

安装好后运行 golint 命令对指定文件或目录进行检查:

D:\Software\Programming\Go\new>golint
main.go:11:2: don't use underscores in Go names; var replace_str should be replaceStr

上面检查结果提出变量命名不规范。大多数时候,报告结果都需要仔细阅读,并一一对照改正。

错误检查

go vet 是代码静态分析工具,用于分析代码中各种逻辑和构造错误。这些错误不影响编译,但可能导致程序运行时异常。例如:

  • 格式化字符串问题:格式化时参数类型不匹配,例如用 %d 来打印字符串。
  • 死代码:永远无法运行的代码,例如在函数 return 语句后的代码。
  • 变量问题:未使用的变量或包导入。
  • 布尔表达式类型错误:使用一个非布尔值作为条件表达式。
  • 误用比较:对函数类型值进行比较,或者比较结构体类型时,结构体包含不能比较字段。
  • 误用通道:对一个 nil 通道读写或关闭。

还有更多 go vet 能检测到的错误类型,这里不逐一列举。

golint 一样,go vet 只负责检查并报告错误,需要开发手动修复代码:

D:\Software\Programming\Go\new>go vet
# new
# [new]
vet.exe: .\main.go:12:2: newstr declared and not used

尽管 go vet 可以捕获许多常见错误,但也有一些错误检测不到或误报。应该与其他工具配合使用,提高 Go 语言代码质量。