Если ты работаешь на Go, рано или поздно встанет вопрос: как уловить баги и проблемы в коде до того, как они попадут в production? Ручная проверка на code review — это медленно. Тесты ловят логические ошибки, но не стиль и не потенциальные уязвимости. Нужен инструмент, который будет смотреть код за тебя.
Это и делает golang linter.
Что вообще такое linter и зачем он нужен
Короче, linter — это автоматический инспектор кода. Он сканирует исходник и ищет проблемы без запуска программы. Это не компилятор и не тестовый фреймворк. Компилятор проверяет синтаксис. Линтер смотрит глубже.
Есть два подхода в анализе кода: статический и динамический. Их часто путают.
Статический анализ — это когда инструмент читает исходный код и ищет проблемы, не запуская программу. Именно это делает golang linter. Вот что он может найти:
- Неиспользуемые переменные, импорты, функции
- Нарушения стиля кодирования (отступы, имена переменных)
- Потенциальные баги (nil pointer dereference, race conditions)
- Проблемы с производительностью
- Уязвимости безопасности
- Сложный код, который сложно тестировать
Динамический анализ — это когда программу запускают и смотрят, что происходит во время выполнения. Используют профайлеры, детекторы утечек памяти, race detector в Go. Но это не линтер.
По моему опыту, большинство команд начинают с линтера, потом добавляют динамический анализ. Правильный подход.
golangci-lint: де-факто стандарт
На рынке есть несколько инструментов (gofmt, go vet, golint), но в production используют один: golangci-lint.
Это не один линтер, а оркестр из 60+ линтеров. Приходит одна команда — а она запускает:
gofmt— проверка форматированияgo vet— встроенные проверки Gostaticcheck— продвинутый статический анализgosec— поиск уязвимостей безопасностиerrcheck— проверка обработки ошибокgovet— анализ типов и интерфейсов
И ещё 50+ других. Каждый специализируется на своём.
Установка простая:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest
Или через homebrew на макосе:
brew install golangci-lint
Запустить:
golangci-lint run ./...
Вот что ты увидишь в выводе:
main.go:5:2: `unused` is a unused variable (varcheck)
main.go:10:1: exported function `ProcessData` should have comment or be unexported (golint)
main.go:15:5: possible nil pointer dereference (vet)
Каждая строка — это потенциальная проблема. Линтер говорит: файл, строка, столбец, описание, какой именно линтер это нашёл.
Конфигурация: как не запустить 60 линтеров подряд
Если запустить golangci-lint с дефолтными настройками на реальном проекте, упадёшь в вывод на 500 строк. Половина из них — ложноположительные. Нужно настроить, какие линтеры включать.
Создай в корне проекта файл .golangci.yml:
run:
timeout: 5m
tests: true
skip-dirs:
- vendor
- mocks
- generated
linters:
enable:
- gofmt
- goimports
- govet
- errcheck
- staticcheck
- gosec
- ineffassign
- unconvert
- unparam
- unused
disable:
- golint # deprecated, use revive instead
linters-settings:
errcheck:
check-type-assertions: true
check-blank: false
goimports:
local-prefixes: github.com/mycompany
staticcheck:
go: "1.20"
gosec:
severity: medium
confidence: medium
issues:
exclude-rules:
- path: _test\.go
linters:
- errcheck
- path: cmd/
text: "should have comment or be unexported"
Вот что здесь происходит:
run — общие настройки. Timeout в 5 минут, чтобы линтер не висел. tests: true — анализировать и тестовые файлы. skip-dirs — пропускать папки (вендор, генерированный код).
linters — какие включить. Я выбрал основной набор, который ловит 95% проблем и не даёт много false positives. Есть специфичные линтеры вроде dupl (поиск дублирующегося кода) или gocritic (сложные проверки), но они требуют настройки.
linters-settings — тонкая настройка каждого линтера. Например, errcheck проверяет, что ты не забыл обработать ошибку. check-type-assertions включает проверку type assertions.
issues — исключения. Например, в тестовых файлах (_test.go) не требуем обработку ошибок везде, потому что это ненужно.
Честно? Лучше начать с минимального набора линтеров, а потом добавлять. На одном проекте мы в спешке включили 50 линтеров и потом неделю разбирались, почему CI падает.
Как встроить в CI/CD pipeline
Линтер локально — это хорошо, но нужно чтобы он запускался автоматически в CI. Иначе разработчик просто не запустит его перед пушем.
Вот пример для GitHub Actions:
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.20'
- uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout=5m
Для GitLab CI:
lint:
stage: test
image: golangci/golangci-lint:latest
script:
- golangci-lint run ./...
allow_failure: false
Для Gitea (у них свой CI):
test-lint:
image: golangci/golangci-lint:latest
commands:
- golangci-lint run ./...
Теперь каждый MR/PR будет проверяться автоматически. Если линтер найдёт проблемы — CI упадёт, и разработчик должен будет их исправить перед мержем.
Когда линтер ошибается: как игнорировать false positives
Бывает, что линтер ошибается. Например, говорит, что функция не используется, хотя она вызывается через reflection. Или требует обработать ошибку, которая по логике не может произойти.
Есть несколько способов игнорировать:
Inline комментарий — прямо в коде:
func processData(data []byte) {
// nolint:errcheck
ioutil.WriteFile("output.txt", data, 0644)
}
Правило в конфиге — для целого класса проблем:
issues:
exclude-rules:
- text: "G104" # Errors unhandled
paths:
- cmd/main.go
Отключение линтера для строк:
//nolint:all
var unusedVar = 42
Но честно — если часто игнорируешь линтер, значит либо конфиг неправильный, либо в коде действительно проблемы.
Сравнение подходов: локально vs CI vs IDE
Локально (перед коммитом):
golangci-lint run ./...
Плюс: ловишь ошибки сразу, не тратишь время на CI. Минус: зависит от ответственности разработчика.
В CI/CD (автоматически):
Плюс: гарантирует, что ничто не пройдёт в main. Минус: feedback медленнее, разработчик уже закончил работу.
В IDE (в реальном времени):
Для VS Code есть плагин Go от Google, который интегрирует golangci-lint прямо в редактор. Ошибки подсвечиваются красным прямо при печати.
Правильный подход — все три. Локально быстро поймёшь ошибку. IDE подсвечивает при написании. CI гарантирует.
Сравнение с другими инструментами
На рынке есть альтернативы:
go vet — встроенный инструмент Go. Быстрый, но ловит только серьёзные ошибки. Недостаточно.
gofmt — форматирование кода. Это не анализ, это просто приведение в порядок. Используй goimports вместо него.
revive — более лёгкая альтернатива golangci-lint. Быстрее, но меньше проверок. Подходит для микросервисов.
Платные инструменты вроде SonarQube или Codacy имеют красивые UI и историю изменений, но для стартапа это дорого.
По моему опыту, golangci-lint — это sweet spot. Один раз настроишь, и забудешь.
Практический совет: как внедрить в существующий проект
Если прибиваешь линтер к старому проекту, сразу включишь его на 1000 ошибок. Разработчики будут в шоке.
Правильный путь:
- Запусти линтер и собери все ошибки:
golangci-lint run ./... > lint-issues.txt
-
Включи в конфиг опцию
max-issues-per-linter: 10000временно. -
Добавь в CI как
allow_failure: true— CI не падает, но видишь результаты. -
За неделю-две разработчики постепенно исправляют ошибки.
-
Как уменьшилось количество — убираешь
allow_failure, линтер становится обязательным.
Если сразу включить на полную, проект взорвётся в конфликтах.
Что дальше?
Линтер — это первая линия защиты. После него нужны:
- Unit-тесты (ловят логические ошибки)
- Integration-тесты (проверяют взаимодействие компонентов)
- Code review (люди видят то, что линтер не видит)
Но без линтера не обойтись.
Если хочешь ещё больше автоматизации — обрати внимание на инструменты вроде Distiq. Это AI-бот, который делает code review автоматически, анализирует не только стиль, но и архитектуру, логику, потенциальные баги. Работает с Go, Python, JavaScript и другими языками. Интегрируется в GitHub, GitLab и GitVerse за две минуты. Особенно полезно в больших командах, где code review становится узким местом.
