Skip to content

go vet - 静态代码分析工具

go vet 是 Go 官方提供的静态代码分析工具,用于检查代码中的常见错误和潜在问题。

🎯 核心功能

1. 基本用法

bash
# 检查当前包
go vet

# 检查指定包
go vet ./pkg/...

# 检查所有包(包括子目录)
go vet ./...

# 检查单个文件
go vet file.go

# 详细输出
go vet -v ./...

2. 检查的问题类型

点击查看完整检查项

go vet 会检查以下类型的问题:

  1. Printf 格式错误

    • 参数数量不匹配
    • 格式字符串错误
    • 类型不匹配
  2. 方法签名错误

    • 接口实现不匹配
    • 方法接收者类型错误
  3. 未使用的代码

    • 未使用的变量
    • 未使用的导入
    • 未使用的函数
  4. 错误处理问题

    • 忽略错误返回值
    • 错误检查不正确
  5. 并发安全问题

    • 竞态条件
    • 锁使用错误
  6. 其他常见错误

    • 类型断言错误
    • 循环变量捕获问题

📝 详细示例

示例 1:Printf 格式错误

go
package main

import "fmt"

func main() {
    name := "Alice"
    age := 30
    
    // ❌ 参数数量不匹配
    fmt.Printf("Name: %s, Age: %d\n", name)
    
    // ❌ 格式字符串错误
    fmt.Printf("Name: %s, Age: %s\n", name, age)
    
    // ❌ 类型不匹配
    fmt.Printf("Age: %s\n", age)
}
go
package main

import "fmt"

func main() {
    name := "Alice"
    age := 30
    
    // ✅ 正确的格式
    fmt.Printf("Name: %s, Age: %d\n", name, age)
}

运行检查:

bash
$ go vet printf_example.go
printf_example.go:8:2: Printf format %d reads arg #2, but call has 1 arg
printf_example.go:11:2: Printf format %s has arg #2 of wrong type int
printf_example.go:14:2: Printf format %s has arg #1 of wrong type int

示例 2:方法签名错误

go
package main

type Writer interface {
    Write([]byte) (int, error)
}

type MyWriter struct{}

// ❌ 方法签名不匹配:缺少返回值
func (w MyWriter) Write(data []byte) {
    // ...
}
go
package main

type Writer interface {
    Write([]byte) (int, error)
}

type MyWriter struct{}

// ✅ 方法签名匹配
func (w MyWriter) Write(data []byte) (int, error) {
    return len(data), nil
}

运行检查:

bash
$ go vet signature_example.go
signature_example.go:10:1: MyWriter.Write has wrong signature for Writer.Write

示例 3:未使用的代码

go
package main

import (
    "fmt"
    "os"  // ❌ 未使用的导入
)

var unusedVar = 42  // ❌ 未使用的变量

func unusedFunc() {  // ❌ 未使用的函数
    fmt.Println("never called")
}

func main() {
    fmt.Println("Hello")
}
go
package main

import "fmt"

func main() {
    fmt.Println("Hello")
}

运行检查:

bash
$ go vet unused_example.go
unused_example.go:5:2: "os" imported but not used
unused_example.go:9:5: unusedVar is unused
unused_example.go:11:6: unusedFunc is unused

示例 4:错误处理问题

go
package main

import (
    "fmt"
    "os"
)

func main() {
    // ❌ 忽略错误返回值
    file, _ := os.Open("test.txt")
    defer file.Close()
    
    // ❌ 错误检查不正确
    data := make([]byte, 100)
    n, err := file.Read(data)
    if n > 0 {
        fmt.Println("Read success")
    }
    // err 被忽略了
}
go
package main

import (
    "fmt"
    "os"
)

func main() {
    // ✅ 正确处理错误
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Printf("Error opening file: %v\n", err)
        return
    }
    defer file.Close()
    
    data := make([]byte, 100)
    n, err := file.Read(data)
    if err != nil {
        fmt.Printf("Error reading file: %v\n", err)
        return
    }
    fmt.Printf("Read %d bytes\n", n)
}

运行检查:

bash
$ go vet error_example.go
error_example.go:11:9: file.Read result is not checked

示例 5:并发安全问题

go
package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.count++
    // ❌ 忘记解锁
}

func (c *Counter) Get() int {
    // ❌ 读取时未加锁
    return c.count
}

func main() {
    c := &Counter{}
    c.Increment()
    fmt.Println(c.Get())
}
go
package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()  // ✅ 使用 defer 确保解锁
    c.count++
}

func (c *Counter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()  // ✅ 读取时也加锁
    return c.count
}

func main() {
    c := &Counter{}
    c.Increment()
    fmt.Println(c.Get())
}

🔧 高级用法

1. 自定义检查器

go vet 支持自定义检查器,可以使用 go vet -vettool 指定:

bash
# 使用自定义检查器
go vet -vettool=/path/to/custom-checker ./...

2. 禁用特定检查

bash
# 禁用未使用导入检查
go vet -unused=false ./...

# 禁用所有检查(不推荐)
go vet -all=false ./...

3. 集成到编辑器

VS Code:

json
{
    "go.vetOnSave": true,
    "go.vetFlags": ["-all"]
}

GoLand:

  • Settings → Go → Tools → go vet
  • 启用 "Run go vet"

📊 检查器列表

检查器说明默认启用
assign检查无用的赋值
atomic检查 atomic 包使用
bools检查布尔表达式
buildtag检查构建标签
errorsas检查 errors.As 使用
iface检查接口实现
loopclosure检查循环闭包
lostcancel检查丢失的 context 取消
nilfunc检查 nil 函数调用
printf检查 Printf 格式
shift检查位移操作
stdmethods检查标准方法实现
stringintconv检查字符串整数转换
structtag检查结构体标签
testinggoroutine检查测试中的 goroutine
tests检查测试函数
unmarshal检查 unmarshal 调用
unreachable检查不可达代码
unsafeptr检查 unsafe.Pointer 使用
unusedresult检查未使用的返回值

🎯 最佳实践

1. 在 CI/CD 中集成

yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  vet:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-go@v2
        with:
          go-version: '1.21'
      - run: go vet ./...

2. 使用 Makefile

makefile
.PHONY: vet
vet:
	@echo "Running go vet..."
	@go vet ./...
	@echo "Vet check passed!"

3. 预提交钩子

bash
#!/bin/sh
# .git/hooks/pre-commit

go vet ./...
if [ $? -ne 0 ]; then
    echo "go vet failed. Please fix the issues."
    exit 1
fi

🔍 常见问题

Q1: go vet 和 golangci-lint 的区别?

go vet:

  • Go 官方工具
  • 检查范围有限但准确
  • 运行速度快
  • 适合快速检查

golangci-lint:

  • 第三方工具
  • 集成了多个检查器(包括 go vet)
  • 检查范围更广
  • 配置更灵活

建议: 两者结合使用,go vet 用于快速检查,golangci-lint 用于全面检查。

Q2: 如何忽略特定的警告?

go
// 对于某些检查器,可以使用注释忽略
//nolint:unused
var unusedVar = 42

Q3: go vet 能检查所有问题吗?

不能。go vet 只检查静态可分析的问题,无法检查:

  • 运行时错误
  • 逻辑错误
  • 性能问题
  • 业务逻辑错误

📖 参考资源

正在精进