2. 使用 go fmt 与 go vet 统一代码风格并发现可疑代码
准备一个“故意有问题”的源文件
要理解 gofmt 与 go vet 的分工,先准备一个同时存在格式混乱和可疑代码的示例文件。新建模块并写入 main.go:
mkdir -p vetdemo && cd vetdemo
go mod init example.com/vetdemo
package main
import (
"fmt"
"os"
)
type User struct {
Name string
Age int
}
func main() {
u := User{Name: "Ada", Age: 36}
if u.Age = 36 {
fmt.Println("age is 36")
}
fmt.Printf("user: %s\n", u)
fmt.Println("done")
os.Exit(0)
}
这个文件包含三类典型问题:缩进与对齐混乱、if 条件中误用赋值 =、Printf 动词与参数类型不匹配。
gofmt 与 go vet 的职责边界
两者都随 Go 工具链提供,但检查对象完全不同:gofmt 只关心源码的语法树排版,不判断逻辑;go vet 在编译通过的前提下检查可疑语义模式。下图展示同一份源码经过两条独立通道后的结果差异。
执行 gofmt 统一格式
先看格式检查结果,再原地修复:
gofmt -l .
# 输出:main.go
gofmt -w .
gofmt -l .
# 无输出,说明格式已统一
-l 列出需要格式化的文件,-w 直接写回。执行后 import 块、结构体字段和 if 块缩进都会被规范化,但 if u.Age = 36 和 Printf 动词问题依然存在——这正是 gofmt 不负责逻辑的体现。
执行 go vet 发现可疑代码
go vet ./...
# ./main.go:15:5: assignment to u.Age
# ./main.go:18:2: fmt.Printf format %s has arg u of wrong type main.User
两条报告分别对应:if 条件中使用了赋值而非比较;%s 期望字符串,却传入了 User 结构体。修复方式:
if u.Age == 36 {
fmt.Println("age is 36")
}
fmt.Printf("user: %+v\n", u)
再次运行 gofmt -l . 与 go vet ./...,两者均无输出,说明格式与可疑模式检查都已通过。
把
gofmt -l . 和 go vet ./... 串成一条命令,可在提交前快速自检:gofmt -l . && go vet ./...。前者有输出说明格式未统一,后者有输出说明存在可疑代码。常见 vet 报告模式速查
| 报告关键词 | 含义 | 典型修复 |
|---|---|---|
| assignment to ... | if/for 条件中误用赋值 | 改为 == 或调整逻辑 |
| format %s has arg ... of wrong type | Printf 动词与参数类型不匹配 | 改用 %v/%+v 或修正参数 |
| Printf call has arguments but no formatting directives | 用了 Printf 却没有占位符 | 改用 Println 或补占位符 |
| unreachable code | return 之后仍有语句 | 删除或调整控制流 |
| struct field ... has json tag but ... | 结构体标签不一致 | 统一标签命名 |
gofmt 只改排版,不会修复逻辑错误;go vet 只报告可疑模式,不会自动改写代码。两者都需要人工确认后再提交,避免把误报当成必须修改的硬性错误。