gofmt и go vet
| Язык | Go |
|---|---|
| gofmt | форматирование, без настроек |
| go vet | статический анализ подозрительного кода |
| Поставляются | вместе с Go (go fmt, go vet) |
| Сторонние | goimports, staticcheck, golangci-lint, gofumpt |
| Интеграция | gopls (сервер языка), pre-commit, CI |
gofmt и go vet — два инструмента, которые поставляются вместе с Go. gofmt автоматически приводит исходный код к единственному каноническому виду, а go vet ищет конструкции, которые компилируются, но почти наверняка являются ошибками. Они задали культуру Go-сообщества: споры о стиле закончились, потому что стиль один, а базовый статический анализ запускается одной командой без настройки.[1][2]
gofmt
gofmt появился одновременно с языком в 2009 году. Его ключевая особенность — отсутствие настроек: нет выбора между табами и пробелами, нет длины строки, нет вариантов расстановки скобок. Отступы — табы, выравнивание — пробелы, открывающая фигурная скобка — на той же строке (это требует и сам синтаксис из-за автоматической вставки точек с запятой).[4] Роб Пайк сформулировал это так: «стиль gofmt никому не нравится, но gofmt нравится всем».[3]
gofmt -l . # список файлов, которые отформатированы не так
gofmt -d main.go # показать diff
gofmt -w . # переписать файлы на месте
go fmt ./... # то же для всех пакетов модуля (gofmt -l -w)
gofmt -s -w . # дополнительно упростить код
Флаг -s упрощает выражения: например, []T{T{1}, T{2}} превращается в []T{{1}, {2}}, а s[a:len(s)] — в s[a:]. Флаг -r позволяет делать механические замены по шаблону: gofmt -r 'bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)' -w ..
Поскольку формат детерминирован, gofmt безопасно запускать автоматически: при сохранении в редакторе (через сервер языка gopls), в pre-commit и в CI/CD. Диффы в ревью содержат только смысловые изменения. Идея оказалась заразительной: rustfmt в Rust (см. Clippy и rustfmt), Black и Ruff в Python, Prettier в JavaScript переняли подход «форматтер без споров».
goimports и gofumpt
- goimports делает всё то же, что gofmt, и дополнительно добавляет недостающие и удаляет неиспользуемые импорты, группируя стандартную библиотеку отдельно. Многие редакторы используют именно его.
- gofumpt — более строгий форматтер-надмножество: убирает лишние пустые строки, объединяет объявления и т. п. Его результат по-прежнему совместим с gofmt.
go vet
go vet — статический анализатор, который проверяет корректность, а не стиль. Он запускается как go vet ./... и, что важно, автоматически выполняет часть проверок при go test.[2] Набор анализаторов включает:
| Анализатор | Что ловит |
|---|---|
printf |
несоответствие формата и аргументов в fmt.Printf("%d", "строка") |
copylocks |
копирование sync.Mutex по значению |
loopclosure |
захват переменной цикла в горутине (актуально до Go 1.22) |
lostcancel |
context.WithCancel без вызова cancel |
structtag |
некорректные теги полей вроде json:name без кавычек |
unreachable |
недостижимый код |
httpresponse |
использование resp.Body до проверки ошибки |
nilfunc, bools, assign |
бессмысленные сравнения и присваивания |
unusedresult |
игнорирование результата чистых функций вроде fmt.Sprintf |
Пример, который скомпилируется, но vet отклонит:
var mu sync.Mutex
func f(m sync.Mutex) {} // vet: f passes lock by value
fmt.Printf("%s\n", 42) // vet: Printf format %s has arg 42 of wrong type int
Анализаторы построены на общем фреймворке golang.org/x/tools/go/analysis, поэтому их легко комбинировать и писать собственные.[7] Отдельно стоит детектор гонок -race — он не статический, а работает во время выполнения тестов (см. Состояние гонки и блокировки).
Более строгие линтеры
go vet намеренно консервативен: в нём только проверки почти без ложных срабатываний. Для большего используют:
- staticcheck — самый известный сторонний анализатор: ошибки (SA), упрощения (S), стиль (ST), устаревшие API; многие его проверки в итоге попадают в gopls.[5]
- golangci-lint — агрегатор, который запускает десятки линтеров (vet, staticcheck, errcheck, gosec, revive, ineffassign и др.) параллельно, с общим кэшем и одним конфигом
.golangci.yml.[6] Это стандарт де-факто для CI.
# .golangci.yml (фрагмент)
version: "2"
linters:
enable: [errcheck, govet, staticcheck, gosec, revive]
formatters:
enable: [gofumpt, goimports]
Линтер errcheck особенно полезен: он находит проигнорированные ошибки, что для Go с его явной обработкой ошибок — частый источник багов.
В CI
Типичная проверка в пайплайне GitHub Actions:
test -z "$(gofmt -l .)" # упасть, если что-то не отформатировано
go vet ./...
golangci-lint run
go test -race ./...
Набор из форматтера, vet, линтера и тестов с -race дёшев и покрывает большую часть типовых ошибок ещё до ревью.