Анализ7 мин чтения2026-03-06

Линтеры Go: полный гайд по статическому анализу кода

Если ты работаешь с Go, то рано или поздно сталкиваешься с вопросом: как гарантировать качество кода, когда в команде десять разработчиков, каждый со своим стил

Если ты работаешь с Go, то рано или поздно сталкиваешься с вопросом: как гарантировать качество кода, когда в команде десять разработчиков, каждый со своим стилем? Линтеры — это ответ. Но не просто "инструмент, который ругается на запятые". Это целая стратегия поиска реальных проблем до того, как код попадёт в продакшн.

В этой статье разберу, как работают линтеры в Go, какие инструменты использовать, как их настроить и интегрировать в CI/CD. И да, я буду честен — не все инструменты одинаково полезны.

Что вообще делают линтеры и почему они важны

Начнём издалека. Код — это не просто текст, который компилируется. Это договор между тобой и твоей командой о том, как всё будет устроено. Компилятор проверяет только синтаксис и типы. Линтеры проверяют остальное.

По-хорошему, здесь нужно различить два подхода:

Статический анализ — это инструменты, которые читают исходный код без его запуска и ищут потенциальные проблемы. Баги, уязвимости, неэффективный код, нарушения стиля. Всё это можно поймать, не запуская приложение.

Динамический анализ — это когда код уже работает. Профайлеры, race detectors, мониторинг утечек памяти. В Go это go test -race, pprof и подобное.

Линтеры — это инструменты статического анализа. И если честно, большинство команд используют их недостаточно эффективно. Просто запустили golangci-lint, увидели 500 ошибок, выключили половину правил и забыли. Неправильный подход.

golangci-lint: король линтеров в Go

На одном проекте я встретился с ситуацией, когда в репозитории было четыре разных линтера, работающих отдельно. Каждый вызывался вручную перед коммитом. Разработчики забывали, конфликты в правилах, chaos. Потом мы перешли на golangci-lint — и это изменило всё.

golangci-lint — это не один линтер. Это агрегатор, который запускает под капотом десятки специализированных инструментов и выдаёт единый отчёт. Вот что там работает:

golint/revive — проверяет стиль кода и соглашения Go. Имена переменных, экспортируемые функции должны иметь документацию и т.д.

go vet — встроенный в Go инструмент, ловит явные ошибки: неправильное использование fmt, потенциальные паники, неправильные тесты.

errcheck — находит необработанные ошибки. Это мощный инструмент, потому что в Go ошибки возвращаются явно, и их легко случайно проигнорировать.

goimports — форматирует импорты и удаляет неиспользуемые. Мелочь, но экономит время при code review.

gosec — поиск проблем безопасности. SQL-инъекции, использование слабых криптографических функций, небезопасные операции с файлами.

golangci-lint имеет встроенные пресеты. Например, default включает самые важные инструменты, а ты всегда можешь настроить свой набор.

Вот как выглядит базовая конфигурация:

# .golangci.yml
linters:
  enable:
    - errcheck
    - gosimple
    - govet
    - ineffassign
    - staticcheck
    - typecheck
    - unused
    - gosec

issues:
  exclude-rules:
    - path: _test\.go
      linters:
        - gosec

Установка простая:

go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run ./...

А вот запуск из CI:

golangci-lint run --deadline=5m --out-format json > lint-report.json

Другие линтеры, которые стоит знать

golangci-lint это мощно, но иногда нужны специализированные инструменты.

staticcheck — по сути, это go vet на стероидах. Находит недостижимый код, неправильные регулярные выражения, логические ошибки. Вообще, если golangci-lint когда-нибудь отключат, я буду использовать именно staticcheck.

go-critic — ловит код, который технически правильный, но странный. Например, условие, которое всегда true, или параметры функции, которые не используются. Это не ошибки, но это красный флаг.

unconvert — находит ненужные преобразования типов. Кажется мелочью, но в больших проектах такие мелочи накапливаются.

На одном проекте я добавил misspell — проверяет опечатки в строках и комментариях. Звучит странно, но когда пользователь видит "Authentification failed" вместо "Authentication failed" — это выглядит непрофессионально.

Вот как включить их все в golangci-lint:

linters:
  enable-all: true
  disable:
    - depguard  # слишком строг
    - exhaustivestruct  # раздражает
    - maligned  # deprecated

Честно? enable-all — это опасная штука. Включишь всё, получишь тысячу ошибок. Лучше начни с малого и добавляй постепенно.

Интеграция в CI/CD: как это работает на практике

Линтеры в локальной среде — это хорошо. Но настоящая мощь проявляется в CI/CD, когда каждый pull request проверяется автоматически.

Вот как мы это делаем в Distiq для своих проектов на Go:

# .github/workflows/lint.yml
name: Lint

on: [push, pull_request]

jobs:
  golangci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v3
        with:
          version: latest
          args: --timeout=5m

Для GitLab:

# .gitlab-ci.yml
lint:
  stage: test
  image: golangci/golangci-lint:latest
  script:
    - golangci-lint run --deadline 5m --out-format json > report.json
  artifacts:
    reports:
      codequality: report.json

Важный момент: линтеры должны работать быстро. Если разработчик ждёт 10 минут результата CI, он будет хитрить и пушить в обход проверок. Поэтому я выставляю timeout в 5 минут и убираю лишние правила из critical path.

Как настроить линтеры под свой проект

Вот здесь начинается реальная работа. На одном проекте я видел конфиг golangci-lint на 200 строк. Половину можно было выкинуть.

Правило номер один: не копируй конфиги с других проектов. Каждая команда имеет свой стиль. Для одного проекта важна максимальная безопасность (финтех), для другого — скорость разработки (стартап). Это влияет на то, какие правила включать.

Вот мой approach:

Шаг 1: начни с минимума

linters:
  enable:
    - errcheck
    - gosimple
    - govet
    - ineffassign
    - staticcheck

Это пять самых важных линтеров. Они находят реальные баги.

Шаг 2: добавь безопасность, если нужна

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

Шаг 3: настрой исключения

Не все правила работают для всех случаев:

issues:
  exclude-rules:
    # Тесты часто нарушают правила стиля
    - path: _test\.go
      linters:
        - gosec
        - govet
    
    # Генерированный код не нужно проверять
    - path: zz_generated
      linters:
        - '*'
    
    # Некоторые ошибки можно игнорировать в определённых пакетах
    - path: internal/legacy
      linters:
        - errcheck
      text: "Error return value not checked"

Шаг 4: не зацикливайся на стиле

Если у тебя уже есть gofmt, то revive становится лишним. Выбери один инструмент форматирования и стоп.

На одном проекте я видел, как разработчики тратили часы на споры о форматировании кода. А потом мы просто включили gofmt, и проблема испарилась. Инструмент решил то, что люди не могли решить.

Локальная разработка: pre-commit hooks

Если линтеры работают только в CI, то feedback loop долгий. Разработчик пушит, ждёт результата, исправляет. Неэффективно.

Лучше запускать линтеры локально перед коммитом.

# .git/hooks/pre-commit
#!/bin/bash
golangci-lint run
if [ $? -ne 0 ]; then
  echo "Lint failed. Commit aborted."
  exit 1
fi

Или используй готовые инструменты. Например, pre-commit framework:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/golangci/golangci-lint
    rev: v1.54.2
    hooks:
      - id: golangci-lint

Потом просто:

pre-commit install
pre-commit run --all-files

Теперь перед каждым коммитом линтер запустится автоматически. Никто не забудет.

Сравнение подходов: когда использовать что

Есть три основных сценария:

Зелёное поле (новый проект)

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

Существующий проект с старым кодом

Не начинай с enable-all. Получишь тысячу ошибок, разработчики возненавидят линтеры. Вместо этого включи постепенно. Сначала самые важные (errcheck, govet), потом остальные.

Я видел такой подход: добавить новое правило, создать отдельную GitHub issue, дать команде неделю на исправления, потом включить в CI. Работает.

Критичный проект (финтех, медицина)

Здесь нужна максимальная строгость. gossec обязателен, depguard для контроля зависимостей, ограничения на использование unsafe и конкурентности.

linters:
  enable:
    - gosec
    - depguard
    - govet
    - errcheck
    - staticcheck
    - unused
    - ineffassign

linters-settings:
  depguard:
    rules:
      main:
        deny:
          - name: io/ioutil
            reason: "Use io or os packages instead"

Когда линтеры становятся вредными

Честно? Если линтер выключают половину разработчиков, значит что-то не так.

Я видел случаи, когда ложные срабатывания линтеров были настолько частыми, что люди перестали им доверять. Линтер ругается на всё подряд, включая совершенно корректный код — и вот уже никто не обращает внимания на его замечания.

Решение простое: регулярно пересматривай конфиг. Если правило срабатывает на 90% кода и каждый раз это false positive — удали его. Линтер должен помогать, а не мешать.

Я также видел проекты, где люди отключают errorcheck везде, потому что "в Go много ошибок". Это неправильно. Если ошибки действительно не нужно обрабатывать (что редко), то явно это закомментируй:

// Ошибку можно безопасно игнорировать, поскольку это вспомогательная логика
_ = someFunc()

// Или через blank identifier
if err != nil {
    // Логируем, но не паникуем
    log.Printf("Warning: %v", err)
}

Практический пример: конфиг для боевого проекта

Вот конфиг, который я бы использовал для production-проекта среднего размера:

# .golangci.yml
run:
  timeout: 5m
  deadline: 5m

linters:
  enable:
    - errcheck      # Необработанные ошибки
    - govet         # Встроенные проверки
    - gosimple      # Упрощение кода
    - staticcheck   # Продвинутые проверки
    - unused        # Неиспользуемые переменные
    - ineffassign   # Неэффективные присваивания
    - gosec         # Безопасность
    - goimports     # Форматирование импортов
  disable-all: false
  fast: false

linters-settings:
  gosec:
    severity: high
    confidence: medium
    
  errcheck:
    check-type-assertions: true
    check-blank: true

output:
  format: colored-line-number

issues:
  exclude-rules:
    - path: _test\.go
      linters:
        - gosec
        - govet
    
    - path: cmd/
      linters:
        - gosec
      text: "G304"  #

Попробуйте Distiq для автоматического code review

AI-бот анализирует каждый MR/PR и оставляет комментарии с замечаниями. Интеграция за 2 минуты.

Попробовать бесплатно

Похожие статьи