2. 使用 go fmt 与 go vet 统一代码风格并发现可疑代码

使用 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 与 go vet 的检查通道 main.go(问题源码) 缩进混乱 · if 赋值 · 动词不匹配 gofmt -l -w . 仅重排空白与对齐 go vet ./... 分析语义可疑模式 输出:格式已统一 缩进/对齐/空行规范化 逻辑保持不变 输出:可疑代码报告 if 条件赋值 Printf 动词与参数不匹配 人工修复 → 再次 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 typePrintf 动词与参数类型不匹配改用 %v/%+v 或修正参数
Printf call has arguments but no formatting directives用了 Printf 却没有占位符改用 Println 或补占位符
unreachable codereturn 之后仍有语句删除或调整控制流
struct field ... has json tag but ...结构体标签不一致统一标签命名
gofmt 只改排版,不会修复逻辑错误;go vet 只报告可疑模式,不会自动改写代码。两者都需要人工确认后再提交,避免把误报当成必须修改的硬性错误。