Начну с признания: однажды я потратил три часа на отладку сбоя при развертывании, только чтобы обнаружить, что наша чрезмерно усердная конфигурация ESLint отвергла совершенно корректный код из-за того, что кто-то имел наглость использовать оператор console.log. Три. Целых. Часа. Тогда я понял, что у нас могут быть проблемы.

Не поймите меня неправильно — я не выступаю за «дикий запад» программирования, где точки с запятой являются необязательными предложениями, а отступы следуют теории хаоса. Но где-то по пути наше сообщество разработчиков превратило линтинг кода из полезного помощника в тиранического повелителя, который микроуправляет каждым нажатием клавиши.

Индустриальный комплекс линтинга

Инструменты линтинга кода анализируют исходный код, чтобы выявить ошибки программирования, обеспечить единообразие стиля и обнаружить потенциальные проблемы. Они разбирают ваш код, строят абстрактное синтаксическое дерево (AST) и проверяют его на соответствие предопределенным правилам. Звучит разумно, правда? Ну, было бы, если бы мы коллективно не потеряли рассудок из-за этого.

Современный рабочий процесс разработки стал испытанием на прочность для инструментов линтинга. У нас есть ESLint для JavaScript, Pylint для Python, RuboCop для Ruby, и список можно продолжить. Каждый из них содержит сотни правил, и каким-то образом мы убедили себя, что включение каждого из них делает нас лучшими разработчиками.

Вот типичная настройка современного JavaScript-проекта:

// .eslintrc.js — монстр-конфигурация
module.exports = {
  "env": {
    "browser": true,
    "es2021": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/recommended",
    "plugin:import/errors",
    "plugin:import/warnings",
    "plugin:import/typescript"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "plugins": [
    "react",
    "@typescript-eslint",
    "jsx-a11y",
    "import",
    "react-hooks"
  ],
  "rules": {
    "no-console": "error",
    "no-debugger": "error",
    "no-alert": "error",
    "no-unused-vars": "error",
    "prefer-const": "error",
    "no-var": "error",
    // ... еще 200 правил, почему бы и нет?
  }
};

Посмотрите на этот конфигурационный файл — он длиннее некоторых реальных исходных файлов в вашем проекте! Мы создали вторичную кодовую базу только для того, чтобы сказать нам, как писать нашу основную кодовую базу.

Парадокс производительности

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

// Вы пишете эту прекрасную функцию
function calculateTotal(items: Item[]): number {
    let total = 0;
    for (let i = 0; i < items.length; i++) {
        total += items[i].price;
    }
    return total;
}
// Но у вашего линтера другие планы:
// ❌ Ошибка: Предпочитайте цикл for-of традиционному циклу for
// ❌ Ошибка: Переменная 'i' должна быть названа более описательно
// ❌ Ошибка: Рассмотрите возможность использования reduce() для операций с массивами
// ❌ Ошибка: Отсутствует комментарий JSDoc для публичной функции
// ❌ Ошибка: Функция не должна превышать когнитивную сложность 5

Поэтому следующие 20 минут вы тратите на рефакторинг вполне читаемой функции, чтобы умилостивить богов линтинга:

/**
 * Вычисляет общую цену товаров в массиве
 * @param items — массив товаров со свойством price
 * @returns Сумма всех цен товаров
 */
function calculateTotal(items: Item[]): number {
    return items.reduce(
        (accumulator: number, currentItem: Item): number => 
            accumulator + currentItem.price, 
        0
    );
}

Поздравляем! Вы превратили функцию, которую мог бы понять любой младший разработчик, во что-то, что требует кандидатской степени в области функционального программирования. Ваш линтер доволен, но ваше будущее «я» (и ваши коллеги) будут проклинать вас, когда им придется отлаживать это в 2 часа ночи.

Ложный пророк качества кода

Сообщество линтинга каким-то образом убедило нас, что больше правил равно лучшему качеству кода. Это как сказать, что рецепт с большим количеством ограничений автоматически дает лучшую еду. Иногда лучшие блюда получаются при нарушении правил, и то же самое применимо к коду.

Рассмотрим этот пример на Python, который вызвал бы нервный срыв у большинства линтеров:

# «Плохой» код по мнению линтеров
def process_data(data):
    result = []
    for item in data:
        if item.is_valid:
            processed = item.value * 2
            if processed > 100:
                result.append(processed)
    return result
# «Хороший» код по мнению линтеров
def process_data(data: List[DataItem]) -> List[int]:
    """
    Обрабатывает элементы данных и возвращает значения больше 100.
    Args:
        data: Список объектов DataItem для обработки
    Returns:
        Список обработанных целых значений больше 100
    Raises:
        ValueError: Если данные содержат недопустимые элементы
    """
    return [
        processed_value
        for item in data
        if item.is_valid
        and (processed_value := item.value * 2) > 100
    ]

«Улучшенная» версия использует оператор морковки (:=), который является модным, но делает код менее читаемым. Мы также добавили строку документации, которая длиннее самой функции. Конечно, это более «профессионально», но лучше ли это? Это зависит от вашего определения лучшего.

Когда линтинг становится контрпродуктивным

Давайте поговорим о слоне в комнате: усталость от линтинга. Когда разработчики постоянно сражаются со своими инструментами вместо того, чтобы решать реальные проблемы, что-то идет не так. Вот дерево решений, которое иллюстрирует, когда линтинг перестает быть полезным:

flowchart TD A[Писать код] --> B{Линтер жалуется?} B -->|Нет| C[Отлично! Запускаем] B -->|Да| D{Жалоба обоснована?} D -->|Да| E[Исправляем проблему] D -->|Нет| F{Можно ли легко отключить правило?} F -->|Да| G[Отключаем правило локально] F -->|Нет| H[Тратим часы на борьбу с конфигурацией] E --> I{Есть еще жалобы?} G --> I H --> J[Рассмотреть новую карьеру] I -->|Да| D I -->|Нет| C J --> K[Стать фермером]

Проблема становится серьезной, когда команды тратят больше времени на настройку линтеров, чем на написание реальных функций. Я видел пул-реквесты, где 90% комментариев были от автоматизированных инструментов линтинга, жалующихся на пробелы, имена переменных или баллы когнитивной сложности.

Кошмар конфигурации

Каждый линтер имеет свой собственный синтаксис конфигурации, и синхронизация их между членами команды становится полноценной работой. Вот как выглядит типичная структура проекта:

my-awesome-project/
├── .eslintrc.js
├── .eslintignore
├── .prettierrc.json
├── .prettierignore
├── .stylelintrc.json
├── .commitlintrc.json
├── .markdownlint.json
├── .editorconfig
├── pyproject.toml
├── .flake8
├── .pylintrc
└── tslint.json (устарело, но все еще там)

У нас больше файлов конфигурации, чем каталогов с исходным кодом! У каждого инструмента свое мнение о том, как все должно быть сделано, и они не всегда согласны. ESLint хочет одинарные кавычки, Prettier предпочитает двойные кавычки, а у вашего руководителя команды есть свои сильные предпочтения по обоим пунктам.

Хуже всего то, что эти конфигурации со временем меняются. То, что началось как «давайте установим некоторую базовую согласованность», превращается в археологические раскопки слоев накопленных правил, о добавлении которых никто не помнит.

Человеческая цена

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