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 只检查静态可分析的问题,无法检查:

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

📖 参考资源 ​

正在精进