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

Ошибки линтера: почему они срабатывают неправильно и как это исправить

Звенит уведомление в Slack. Линтер снова навалил замечаний в PR. Половина — абсолютно бесполезные. Остальные — упустил очевидный баг. Знакомо?

Звенит уведомление в Slack. Линтер снова навалил замечаний в PR. Половина — абсолютно бесполезные. Остальные — упустил очевидный баг. Знакомо?

По-хорошему, линтер должен спасать от идиотизма на коде. Вместо этого он часто становится источником раздражения. Настроен неправильно, ругается на всё подряд или, наоборот, всё пропускает. Или выдаёт false positive'ы, которые разработчик игнорирует, потому что устал от фальшивых срабатываний.

Сегодня разберёмся, почему линтеры ошибаются, как их правильно настроить и когда вообще стоит им доверять. Статья для тех, кто уже устал от eslint: disable next-line на каждой второй строке.

Статический анализ vs динамический: в чём разница и почему линтеры несовершенны

Сначала о терминологии. Линтер — это инструмент статического анализа кода. То есть он смотрит на исходник, не запуская его.

Вот в чём прикол: статический анализ работает без контекста выполнения. Линтер видит строку кода, но не видит, что на самом деле произойдёт во время работы программы. Переменная x может быть строкой или числом в зависимости от ветки кода? Линтер может забить, что это два разных типа, и выдать ложное срабатывание.

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

Линтеры выбрали путём статического анализа ради скорости. За 2 секунды просканировать весь проект? Легко. Но цена — false positive'ы и false negative'ы. Первые — это когда линтер ругается на корректный код. Вторые — когда не видит реальную ошибку.

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

def process_user(data):
    name = data.get('name')
    # Линтер может подумать, что name может быть None
    # и ругаться на следующую строку
    return name.upper()  # Потенциальный баг? Или нет?

Линтер не знает, гарантирует ли логика приложения, что name всегда будет строкой. Может ругаться просто так. Может пропустить реальную проблему, если логика сложнее.

Вот почему полагаться на линтер на 100% нельзя. Но игнорировать его — ещё хуже. Нужен баланс.

Какие ошибки линтеры допускают чаще всего

Я видел штук пять типичных ошибок в работе линтеров. Они встречаются везде.

Слишком строгие правила из коробки. Большинство линтеров поставляются с кучей правил, которые вообще не нужны для твоего проекта. ESLint по умолчанию ругается на console.log — но что если это production-grade логирование? TypeScript линтер может требовать явных типов везде — а может быть, твой код нормально работает с type inference'ом.

Конфликты между правилами. Настроил Prettier для форматирования, а ESLint требует другого стиля. Теперь каждый раз линтер и форматер ругаются друг на друга. Видел проекты, где разработчики буквально отключили половину правил, потому что они конфликтовали.

Ложные срабатывания на сложном коде. Когда логика запутанная, линтер часто не может разобраться. Особенно если есть dynamic imports, eval или хитрые манипуляции с типами.

// Линтер может не понять, что error всегда будет Error
try {
  doSomething();
} catch (error) {
  if (typeof error === 'object' && error !== null) {
    console.log(error.message); // "error может быть undefined" — фальшивое предупреждение
  }
}

Настройка only для одного языка. Если в проекте TypeScript + JavaScript + HTML, каждый требует своей конфигурации. Часто настраивают только TypeScript, потом JavaScript файлы вообще не проверяются. Или наоборот.

Игнорирование контекста проекта. Линтер не знает про бизнес-требования. Может ругаться на код, который сделан специально. Типичный пример — когда нужна оптимизация под старые браузеры и код намеренно написан старым стилем.

По моему опыту, 70% проблем с линтерами решаются правильной конфигурацией. Остальные 30% — это переосмысление, нужен ли вообще этот инструмент в таком виде.

Инструменты: что реально работает, а что — маркетинг

Разработчики часто думают, что есть один волшебный линтер, который всё решит. Нет.

ESLint — король JavaScript. Гибкий, настраивается под всё. Но требует времени на конфигурацию. Из коробки работает так себе.

# Базовая конфиг для ESLint (.eslintrc.json)
{
  "env": {
    "browser": true,
    "es2021": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended"
  ],
  "rules": {
    "no-console": "warn",
    "semi": ["error", "always"],
    "quotes": ["error", "single"]
  }
}

Prettier — не линтер, а форматер. Но часто используется вместе с ESLint. Решает проблему стиля кода, чтобы линтер не отвлекался на пробелы.

TypeScript compiler — если используешь TS, то это уже встроенный статический анализ. Очень мощный. Половину ошибок поймет сам, без дополнительных линтеров.

Pylint, Flake8, Ruff для Python. Pylint самый строгий, Ruff самый быстрый. На одном проекте мы перешли с Pylint на Ruff и прирост скорости был в 20 раз. Правда, пришлось переписать конфиг.

# Быстрая проверка с Ruff
ruff check . --fix

# Медленнее, но строже — Pylint
pylint src/

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

Специализированные линтеры — для безопасности (Bandit для Python), для производительности (Lighthouse для веба), для архитектуры (ESLint плагины для правил по структуре проекта).

Правда в том, что один инструмент не покрывает всё. Нужна комбинация. На проекте с TypeScript + React я обычно использую:

Это работает. Но требует настройки.

Как правильно встроить линтер в CI/CD и не создать ад

Встроить линтер неправильно — значит испортить жизнь всей команде. Видел проекты, где CI падает 50 раз в день из-за линтера.

Вот как это делается правильно:

Шаг 1: Локальная проверка перед коммитом. Не ждать CI. Линтер должен срабатывать сразу, пока разработчик пишет код.

# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
// package.json
{
  "lint-staged": {
    "*.ts": "eslint --fix",
    "*.tsx": "eslint --fix",
    "*.css": "stylelint --fix"
  }
}

Шаг 2: Конфиг в репозитории, не в голове. Все правила должны быть в файлах: .eslintrc.json, pyproject.toml, .golangci.yml. Новый разработчик приходит, клонирует репо, запускает линтер — и он работает как надо.

Шаг 3: Разные правила для разных целей. Не все ошибки одинаковые. Критические (security issues) должны блокировать PR. Warnings (стиль) могут быть просто уведомлением.

# GitHub Actions example
name: Lint
on: [pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm run lint -- --max-warnings=5  # Максимум 5 предупреждений, иначе fail
      - run: npm run lint:security  # Отдельно проверяем безопасность

Шаг 4: Не блокируй мелочи. Если линтер ругается на 100 вещей в PR, разработчик начнёт их игнорировать. Фокусируйся на главном: баги, безопасность, очевидные проблемы производительности. Остальное — в режиме warning.

Шаг 5: Регулярно пересматривай конфиг. Раз в квартал посмотри, какие ошибки линтер находит, какие игнорируются. Если 90% ошибок — это warnings про стиль, значит, надо переконфигурировать. Может, это вообще не нужно.

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

Когда линтер ошибается, а когда нет

Линтер хорошо ловит:

Линтер плохо ловит:

Если ты пишешь критичный код, не полагайся только на линтер. Нужны ещё code review, тесты и может быть, динамический анализ (вроде AddressSanitizer для C++).

# Пример: линтер не поймёт эту логическую ошибку
def calculate_discount(price, customer_type):
    if customer_type == "vip":
        return price * 0.9  # 10% скидка
    elif customer_type == "regular":
        return price * 0.95  # 5% скидка
    else:
        return price * 1.1  # Ошибка! Должна быть скидка, а не наценка

# Линтер не ругнётся, потому что код синтаксически верный
# Но логика неправильная

Вот почему code review человеком остаётся важным. Линтер — помощник, не замена.

Практический совет: AI code review как дополнение

Честно? Линтер — это прошлое. Будущее за AI-анализом кода.

Инструменты вроде CodeRabbit или Distiq используют машинное обучение, чтобы понимать код глубже. Они ловят не только синтаксис, но и логические ошибки, проблемы с производительностью, нарушения архитектуры. И главное — они не выдают столько false positive'ов, потому что обучены на реальных ошибках.

Distiq, например, встраивается в GitHub/GitLab за две минуты и начинает анализировать каждый MR. Находит баги, которые линтер пропустит. И говорит их на русском, без занудства.

Сочетание классического линтера + AI code review — это оптимум. Линтер ловит мелочи быстро, AI видит картину целиком.


TL;DR: Линтер ошибается потому что работает статическим анализом без контекста. Правильная конфиг + встраивание в CI/CD + комбинация инструментов решают 90% проблем. Остальное — за AI code review.

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

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

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

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