golangci-lint для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

golangci-lint: Агрегатор линтеров для Go

1. Что это и зачем нужно

golangci-lint — это мощный агрегатор линтеров для языка Go, который объединяет десятки популярных инструментов статического анализа кода в единый, быстрый и настраиваемый интерфейс. Вместо того чтобы устанавливать и запускать каждый линтер по отдельности (например, vet, staticcheck, errcheck, gocyclo и десятки других), разработчик получает одну команду, которая запускает их все параллельно, кэширует результаты и выдает единый отчет. Это экономит часы настройки CI/CD и локальной разработки.

Зачем это нужно? В больших проектах на Go (микросервисы, CLI-утилиты, веб-серверы) поддержание качества кода вручную невозможно. Ошибки вроде необработанных ошибок, неиспользуемых переменных, неправильного форматирования или потенциальных deadlock'ов могут привести к багам в production. golangci-lint автоматически находит такие проблемы на этапе code review или pre-commit. Он также помогает соблюдать единый стиль кода в команде, что критически важно для читаемости и поддержки.

Ключевые преимущества перед запуском линтеров по отдельности: скорость (линтеры запускаются параллельно, результаты кэшируются), гибкость (можно включить/отключить любой линтер, настроить его правила через YAML-конфиг), интеграция (работает с IDE, CI/CD системами, поддерживает go vet из коробки) и единый формат вывода (JSON, SARIF, цветной текстовый — для любого инструмента).

2. Установка

Установка golangci-lint выполняется несколькими способами. Рекомендуемый способ — через go install (требуется Go 1.18+):

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

После установки бинарник будет доступен в $GOPATH/bin (обычно ~/go/bin). Проверьте установку:

golangci-lint --version

Альтернативные способы (для CI/CD или если нет Go):

  • Linux/macOS (curl): curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
  • macOS (Homebrew): brew install golangci-lint
  • Docker: docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.55.2 golangci-lint run -v

3. Быстрый старт — минимальный рабочий пример

Создайте простой Go-модуль и файл с намеренной ошибкой:

mkdir myproject && cd myproject
go mod init myproject

Создайте файл main.go:

package main

import "fmt"

func main() {
    x := 10
    fmt.Println("Hello")
    // Ошибка: неиспользуемая переменная x
}

Запустите линтер:

golangci-lint run

Вывод будет примерно таким:

main.go:6:2: `x` is unused (deadcode)
    x := 10
    ^

Линтер нашел неиспользуемую переменную. Исправьте код (удалите строку x := 10) и запустите снова — ошибок не будет.

Для более строгой проверки используйте флаг -v (verbose) и --fast (только быстрые линтеры):

golangci-lint run -v --fast

4. Основные методы/функции/команды

В отличие от библиотеки, golangci-lint — это CLI-инструмент. Его "методы" — это команды и флаги. Рассмотрим 8 ключевых команд и опций.

4.1. run — основной запуск линтеров

Сигнатура: golangci-lint run [флаги] [путь]

Что делает: Запускает все активные линтеры для указанной директории (по умолчанию — текущая). Выводит найденные проблемы.

Рабочий пример:

# Запустить линтеры для всего проекта, игнорируя vendor
golangci-lint run ./...

# Запустить только для пакета internal/utils
golangci-lint run ./internal/utils/...

4.2. linters — список доступных линтеров

Сигнатура: golangci-lint linters

Что делает: Выводит полный список всех линтеров, которые можно использовать, с их статусом (включен/выключен по умолчанию) и кратким описанием.

Рабочий пример:

golangci-lint linters

В выводе вы увидите что-то вроде:

Enabled by default:
    errcheck: Errcheck is a program for checking for unchecked errors in go programs.
    gosimple: Linter for Go source code that specializes in simplifying code...
    govet: Vet examines Go source code and reports suspicious constructs...
    ineffassign: Detects when assignments to existing variables are not used.
    staticcheck: Staticcheck is a go vet on steroids...
    unused: Checks Go code for unused constants, variables, functions and types.
Disabled by default (enable with --enable):
    bodyclose: checks whether HTTP response body is closed successfully...
    dupl: Tool for code clone detection...
    funlen: Tool for detection of long functions...
    ...

4.3. --enable и --disable — управление линтерами

Сигнатура: golangci-lint run --enable=линтер1,линтер2 --disable=линтер3

Что делает: Включает или отключает конкретные линтеры для текущего запуска. Полезно для точечной проверки.

Рабочий пример:

# Включить линтер bodyclose (проверка закрытия HTTP-тел) и отключить unused
golangci-lint run --enable=bodyclose --disable=unused ./...

4.4. --config — использование конфигурационного файла

Сигнатура: golangci-lint run --config=путь/к/файлу.yml

Что делает: Загружает настройки из YAML-файла. Позволяет гибко настроить линтеры, исключить папки, задать пороги сложности и т.д.

Рабочий пример:

Создайте файл .golangci.yml в корне проекта:

linters:
  enable:
    - errcheck
    - gosimple
    - govet
    - staticcheck
    - bodyclose
    - dupl
  disable:
    - unused

issues:
  exclude-use-default: false
  max-issues-per-linter: 0
  max-same-issues: 0

run:
  timeout: 5m
  skip-dirs:
    - vendor
    - third_party

Запуск:

golangci-lint run --config=.golangci.yml

4.5. --out-format — формат вывода

Сигнатура: golangci-lint run --out-format=json|line-number|colored-line-number|tab|checkstyle|code-climate|junit-xml|sarif

Что делает: Определяет формат отчета. JSON удобен для автоматической обработки, SARIF — для интеграции с GitHub Code Scanning, colored-line-number — для чтения человеком.

Рабочий пример:

# Вывод в JSON для дальнейшего парсинга
golangci-lint run --out-format=json ./... > report.json

# Вывод в формате SARIF для GitHub
golangci-lint run --out-format=sarif ./... > report.sarif

4.6. --print-issued-lines — показывать строки с ошибками

Сигнатура: golangci-lint run --print-issued-lines=true

Что делает: Включает или отключает вывод строк кода, в которых найдена проблема. По умолчанию включено. Полезно отключить для кратких отчетов.

Рабочий пример:

# Отключить показ строк кода (только файл, строка, сообщение)
golangci-lint run --print-issued-lines=false

4.7. --fast — только быстрые линтеры

Сигнатура: golangci-lint run --fast

Что делает: Запускает только линтеры, которые не требуют полного построения AST (Abstract Syntax Tree) и выполняются быстро. Полезно для pre-commit хуков, когда скорость критична.

Рабочий пример:

# Быстрая проверка перед коммитом
golangci-lint run --fast ./...

4.8. cache — управление кэшем

Сигнатура: golangci-lint cache status | clean

Что делает: status показывает размер и расположение кэша линтера. clean очищает кэш (полезно, если линтер ведет себя странно после обновления).

Рабочий пример:

# Посмотреть статус кэша
golangci-lint cache status

# Очистить кэш
golangci-lint cache clean

5. Реальный практический пример (100-200 строк кода)

Создадим небольшой HTTP-сервер с намеренными ошибками и покажем, как golangci-lint их находит.

Структура проекта:

myapp/
├── main.go
├── handler.go
├── .golangci.yml
└── go.mod

Файл main.go:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/api", apiHandler)

    fmt.Println("Server starting on :8080")
    // Ошибка: не обрабатываем ошибку ListenAndServe
    http.ListenAndServe(":8080", nil)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    // Ошибка: не закрываем тело запроса
    r.Body.Read([]byte{})
    fmt.Fprintf(w, "Home Page")
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
    // Ошибка: неиспользуемая переменная
    data := r.URL.Query().Get("data")
    // Ошибка: не обрабатываем ошибку
    w.Write([]byte("API response"))
    // Ошибка: неиспользуемая переменная
    _ = data
}

Файл handler.go:

package main

import (
    "io"
    "net/http"
)

// Ошибка: функция слишком сложная (высокая цикломатическая сложность)
func complexHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        if r.URL.Path == "/a" {
            w.Write([]byte("a"))
        } else if r.URL.Path == "/b" {
            w.Write([]byte("b"))
        } else if r.URL.Path == "/c" {
            w.Write([]byte("c"))
        } else {
            w.WriteHeader(http.StatusNotFound)
        }
    } else if r.Method == "POST" {
        body, _ := io.ReadAll(r.Body)
        // Ошибка: не закрываем тело после чтения
        w.Write(body)
    }
}

Файл .golangci.yml:

linters:
  enable:
    - errcheck
    - gosimple
    - govet
    - ineffassign
    - staticcheck
    - unused
    - bodyclose
    - gocyclo
    - dupl

issues:
  max-issues-per-linter: 0
  max-same-issues: 0

run:
  timeout: 3m

Запуск линтера:

golangci-lint run --config=.golangci.yml ./...

Вывод (сокращенный):

main.go:14:14: Error return value of `http.ListenAndServe` is not checked (errcheck)
    http.ListenAndServe(":8080", nil)
                ^
main.go:19:5: bodyclose: response body must be closed (bodyclose)
    r.Body.Read([]byte{})
    ^
main.go:26:7: `data` is unused (unused)
    data := r.URL.Query().Get("data")
         ^
main.go:28:5: Error return value of `w.Write` is not checked (errcheck)
    w.Write([]byte("API response"))                    

Рекомендаций