7. go vet. Статический анализ

Если компилятор Go (go build) строго проверяет синтаксис и корректность типов, а тесты (go test) проверяют логику работы программы, то go vet занимает важнейшую промежуточную нишу. Это инструмент статического анализа кода, который ищет подозрительные конструкции, которые формально компилируются, но с высокой вероятностью являются программными ошибками.

Для разработчиков, знакомых с историей Bell Labs, это прямой потомок классической утилиты lint, созданной Стивом Джонсоном в 1978 году. Но в отличие от монструозных линтеров прошлого, go vet встроен прямо в стандартный тулчейн языка и работает молниеносно.


Философия: Ноль ложных срабатываний

Главное отличие go vet от большинства сторонних линтеров — его строгий консерватизм. Создатели инструмента придерживаются правила: лучше пропустить сомнительное место, чем выдать ложное предупреждение.

Цель go vet — сообщать только о тех проблемах, которые точно являются ошибками. Если линтер заваливает инженера сотнями предупреждений о стиле или вкусовых предпочтениях, разработчик очень быстро начинает игнорировать весь вывод утилиты. В случае с go vet действует обратное правило: если go vet ругается, код обязан быть исправлен. Это не вопрос эстетики или форматирования, это реальный скрытый дефект.


Как это работает (Under the hood)

go vet работает с Абстрактным Синтаксическим Деревом (AST) и информацией о типах, которую предоставляет компилятор. Он не строит бинарник, а загружает исходный код в память, парсит его и прогоняет через набор независимых анализаторов (analyzers).

flowchart TD
    classDef process  fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
    classDef success  fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
    classDef error    fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
    classDef warning  fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
    classDef entry    fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
    classDef aux      fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
    classDef system   fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
    classDef data     fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
    classDef network  fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
    classDef accent   fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
    A["Исходный код .go"]:::process --> B["Парсер и Type Checker"]
    B --> C["AST + Информация о типах"]

    subgraph Analyzers ["Набор анализаторов go vet"]
        D["printf"]:::process
        E["copylock"]:::process
        F["lostcancel"]:::process
        G["structtag"]:::process
    end

    C --> Analyzers
    Analyzers --> H["Отчет об ошибках"]
  

Самые частые проверки (с примерами)

go vet содержит десятки чекеров. Вот те, с которыми вы будете сталкиваться чаще всего в реальной практике.

1. Printf family (Несоответствие формата)

Самая известная проверка. Она ловит ошибки в строках форматирования функций семейств fmt.Printf, fmt.Sprintf, log.Printf и кастомных логгеров.

Go
package main

import "fmt"

func main() {
    name := "Alice"
    // ОШИБКА: %d ожидает число, а не строку
    fmt.Printf("User ID: %d", name) 
}

Компилятор это пропустит (поскольку аргументы передаются через ...any), но go vet сразу скажет:
Printf format %d has arg name of wrong type string.

Также он ловит ситуацию, когда аргументов меньше, чем плейсхолдеров:

Go
fmt.Printf("%s %s", "first") // vet: Printf format %s reads arg #2, but only 1 arg passed

2. Copy locks (Копирование мьютексов)

Это классическая ошибка для новичков в Go. Если структура содержит sync.Mutex (или sync.WaitGroup), её нельзя копировать по значению. Мьютексы содержат внутреннее состояние, которое ломается при копировании. Нужно передавать указатель.

Go
package main

import "sync"

type Counter struct {
    mu    sync.Mutex
    count int
}

func BadIdea(c Counter) { // vet: BadIdea passes lock by value: Counter contains sync.Mutex
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

go vet спасет вас от сложноуловимых багов конкурентности, которые проявятся только под нагрузкой.

3. Unreachable code (Мертвый код)

Go не предупреждает о недостижимом коде при компиляции, если только он не находится после return в той же функции в простых случаях. vet делает это глубже, анализируя граф потока управления (CFG).

Go
package main

import "fmt"

func getStatus() string {
    return "ok"
    fmt.Println("This will never print") // vet: unreachable code
}

4. Boolean tautologies (Тавтология в условиях)

Писать условия, которые всегда истинны или ложны, — частая опечатка при копипасте логических выражений.

Go
func isAllowed(role string) bool {
    // Опечатка: role == "user" вместо role == "admin" или просто лишнее условие
    if role == "user" || role == "user" { 
        return true
    }
    return false
}

vet: suspect or: role == "user" || role == "user"

5. Использование range в горутинах

Эта ошибка специфична для замыканий в цикле, когда горутина захватывает одну и ту же переменную итератора.

Go
for _, val := range values {
    go func() {
        fmt.Println(val) // vet: loop variable val captured by func literal
    }()
}

Хотя в Go 1.22 поведение range изменили (переменные теперь создаются новые на каждой итерации цикла), в старых версиях это был критический баг, приводивший к чтению последнего значения всеми горутинами. go vet предупреждает о потенциальной проблеме на старых версиях или в сложных сценариях захвата внешних указателей.


Интеграция и запуск

go vet работает с пакетами, как и другие инструменты экосистемы.

Bash
# Проверить текущий пакет
go vet

# Проверить все пакеты в проекте (рекурсивно)
go vet ./...

# Проверить конкретный файл (обычно работает с пакетом целиком)
go vet main.go

Связка с go test

Важный момент: команда go test автоматически запускает go vet перед запуском тестов. Если vet находит ошибку, тесты не запустятся.

Это делает его неявной частью вашего рабочего процесса. Вам даже не нужно помнить о нем, если вы запускаете тесты регулярно.


Итог

  1. go vet — консервативный статический анализатор, входящий в состав Go.
  2. Его задача — ловить баги, а не проблемы стиля.
  3. Он работает через AST и информацию о типах.
  4. Он автоматически запускается перед go test.
  5. Ключевые проверки: форматирование строк, копирование локов, тавтология в условиях, ошибки в тегах структур (structtag) и утечки контекста (lostcancel).

Мы научились находить ошибки до запуска кода с помощью статического анализа. Но что, если стандарты языка меняются, а старый код становится не просто «плохим», а синтаксически или семантически устаревшим? В арсенале Go есть инструмент, который не просто диагностирует проблемы, а берет скальпель и исправляет код автоматически. Переходим к статье: 7.1. go fix. Автоматическая миграция кода.