Ruff как «машина компоновки» кода: как настроить правила под стиль проекта
Покажем, как выбрать набор правил, настроить конфиги и добиться одинакового качества во всей кодовой базе. Разберём типичные кейсы конфликтов и миграции.
Содержание
Ruff как «машина компоновки» кода: как настроить правила под стиль проекта
Ruff сегодня часто воспринимают как «быструю замену линтеру». На практике же его реальная сила шире: Ruff — это единый движок для набора правил (linting) и автоматической фиксации (formatting/organizing), который может стать «машиной компоновки» кода внутри команды. Он помогает удерживать единый стиль, не превращая ревью в бесконечные споры о запятых, именах и порядке импорта.
Но чтобы Ruff реально работал как конвейер качества, а не как очередной инструмент, который «иногда ругается», нужно правильно выбрать набор правил, настроить конфигурацию и заранее продумать миграцию. В этой статье разберём, как это сделать по-взрослому: от структуры конфигурации и выбора источников правил до типичных конфликтов и стратегии перехода.
Что именно делает Ruff и почему это важно для согласованности
Ruff — это инструмент статического анализа, который по умолчанию фокусируется на PEP8/flake8-совместимом поведении, а также включает дополнительные правила (например, из экосистемы isort/pyupgrade/pyflakes и т. п.). Его ключевые функции, которые влияют на «единое качество» кодовой базы:
- Линтинг: находит потенциальные проблемы (unused imports/variables, неиспользуемые выражения, неправильные исключения, подозрительные конструкции и т.д.).
- Автофикс: часть правил умеет предлагать исправления автоматически.
- Конфигурация правил: можно включать/выключать категории правил, выбирать “источники” правил и настраивать исключения.
- Интеграция в процессы: pre-commit, CI, локальные проверки перед коммитом.
Когда Ruff настроен правильно:
- кодовая база выглядит единообразно;
- ревью экономит время на «стилевые» вопросы;
- ошибки и анти-паттерны отсекаются до того, как попадут в основную ветку;
- миграции (например, переход на новый синтаксис Python) проходят контролируемо.
Определяем целевой стиль: baseline проекта и границы автоматики
Прежде чем включать “всё подряд”, нужно сформировать baseline — минимально допустимый стандарт. В идеале вы фиксируете следующие параметры:
Какие аспекты вы хотите доверить Ruff
Обычно разделяют три зоны:
-
Структура и порядок
- порядок импортов;
- выравнивания/форматирование, которое Ruff умеет фиксировать;
- удаление неиспользуемых конструкций (в пределах автофикса).
-
Семантика и потенциальные ошибки
- запрет опасных шаблонов;
- приведение к современному синтаксису;
- правила, которые ловят реальные баги/плохие практики.
-
Личностные предпочтения команды
- например, строгость по длине строк, оформление сложных выражений;
- правила, которые чаще являются “вкусовыми”.
Ruff хорош именно для (1) и (2). Для (3) следует быть осторожным: если вы включите слишком “жёсткие” вкусовые правила, то инструмент начнёт мешать и станет причиной постоянных конфликтов в PR.
Не смешивайте роль линтера и форматтера
Ruff может делать автофиксы, но полноценное форматирование в стиле Black — отдельная тема. В некоторых проектах используют связку:
- Ruff — для линтинга + частичных фиксов (например, импортов и простых преобразований),
- Black — для форматирования,
- isort — если не полагаетесь на Ruff для импорта (хотя Ruff часто закрывает эту задачу).
Если ваша цель — «одинаковое качество по всей базе», сначала определите, кто отвечает за форматирование, а кто — за правила.
Выбор набора правил: от “включить всё” к осознанной стратегии
Ruff поддерживает выбор правил через select, ignore и наборы-агрегаторы вроде lint.select/lint.ignore (конфигурация зависит от версии Ruff). В целом стратегия такая:
- Начните с доказанного набора: например, flake8-совместимые базовые категории + полезные дополнительные правила.
- Добавляйте правила постепенно, особенно “строгие” или потенциально конфликтные.
- Выделяйте исключения точечно:
- на уровне файла;
- на уровне директории;
- точечно по коду (для обоснованных случаев).
Практика: стартовый профиль
Обычно разумный старт — включить базовые ошибки и “дефекты” качества, а вкусовые вещи отложить. Например, для современных проектов на Python 3.10+ вы часто включаете:
- правила, связанные с синтаксическими и логическими подозрениями,
- предупреждения о неиспользуемых импортах/переменных,
- упрощения и приведение к современному стилю.
Ниже пример конфигурации (TOML). Она не претендует на «единственно верную», но показывает подход: задаём target-Python, выбираем правила и управляем автофиксом.
# pyproject.toml
[tool.ruff]
target-version = "py310"
[tool.ruff.lint]
# Выбираем наборы правил. Конкретные коды зависят от версии Ruff.
select = [
"E", # pycodestyle: ошибки форматирования
"F", # pyflakes: потенциальные ошибки
"I", # isort-подобные правила по импорту
"UP", # pyupgrade: приведение к современному синтаксису
"B", # flake8-bugbear: потенциальные баги/подозрительные места
]
# Игнорируем то, что точно конфликтует с вашим подходом или пока “не готово”.
ignore = [
# Например: временно отключить строгие правила по длине строк,
# если у вас форматирование делает другой инструмент.
"E501",
]
# Управление “важностью” правил через категорию per-file.
# Этот блок будет полезен для миграций.
Настройка через pyproject.toml: минимально достаточная конфигурация
Большая часть командных проектов держит конфигурацию Ruff в pyproject.toml. Это удобно, потому что:
- один файл на проект;
- можно версионировать в том же PR, что и изменения правил;
- конфигурация легко переносится между средами.
Настройки, которые почти всегда нужны
target-version— чтобы Ruff корректно рекомендовал преобразования.line-length(если релевантно вашим правилам).- Параметры автокоррекции (
fix,unsafe-fixes) — осторожно. - Исключения по директориям (например,
migrations,vendor,testsесли у вас другая политика).
Пример более «прикладной» конфигурации:
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
# Например, миграции в Django часто содержат специфический код.
"*/migrations/*.py" = ["UP006"]
[tool.ruff.format]
# В Ruff есть форматирование/переформатирование в рамках его компетенции.
# Если у вас есть отдельный форматтер, этот блок может быть пустым или отключённым.
quote-style = "double"
Управляем импортами и автофиксом: где чаще всего возникает хаос
Конфликтов обычно три типа:
- Порядок импортов спорит с другим инструментом
- Ruff делает автофикс там, где это конфликтует с вашими ожиданиями форматирования
- Правила “строгие”, но кодовая база пока не готова
Если у вас уже есть isort/Black
Сценарии:
- Ruff может исправлять порядок импортов (включая правила
I). - Black — форматирует строки и скобки.
- isort (иногда) — управляет порядком импортов.
Если вы включаете и Ruff (I), и isort одновременно, важно согласовать конфигурации обоих инструментов (profile, секции, force_sort_within_sections и т.п.). Иначе вы получаете бесконечные правки: один инструмент меняет — второй снова меняет.
Практическое решение:
- либо оставить порядок импортов Ruff’у и отключить isort;
- либо отключить импортные правила Ruff и оставить isort.
Для согласованности большинство команд выбирают один источник истины.
Осторожно с unsafe-fixes
Ruff может делать автофиксы безопасные и небезопасные (в зависимости от типа правила). Небезопасные фиксы обычно меняют структуру кода глубже, чем вы ожидаете. На этапе внедрения правил лучше начать с безопасных фиксов, а небезопасные — включать после периода стабилизации.
Типичные кейсы конфликтов правил
Кейc 1: «Почему Ruff ругается на тесты иначе, чем на боевой код?»
В тестах часто допускаются:
assertбез дополнительных сообщений,- длинные строки с JSON/табличными данными,
- специфические паттерны моков.
Решение — per-file-ignores и/или разные наборы правил. Простейший пример:
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["B006", "E501"]
Это не «всё разрешить», а сделать различия контролируемыми.
Кейc 2: «Ruff предлагает обновления синтаксиса, но проект ещё не на новом Python»
Если вы включаете UP (pyupgrade) и не выставили target-version, Ruff может рекомендовать изменения, которые не поддерживаются вашей версией интерпретатора в проде/CI.
Решение:
- корректно задать
target-version, - или временно игнорировать “опасные” подклассы правил pyupgrade.
Кейc 3: «Импорт отсортирован по-разному между локально и CI»
Это почти всегда из-за несогласованности конфигураций или разной версии инструментов.
Проверьте:
- совпадает ли
pyproject.tomlвезде; - одинаковые версии Ruff (lockfile для зависимости или pinned версия в CI);
- одинаковая ОС/файловая система (реже, но бывает влияние на некоторые сценарии).
Практика команды: фиксировать версию Ruff в зависимостях проекта или в tooling-окружении.
Кейc 4: «После миграции CI превращается в стену ошибок»
Это симптом слишком резкого включения правил.
Правильный подход:
- включить набор правил постепенно;
- сначала добиться нулевых ошибок в основных директориях;
- затем расширить покрытие.
Стратегия миграции: как перейти на Ruff без остановки разработки
Самая полезная часть внедрения — не «настроить конфиг», а сделать так, чтобы команда могла продолжать работу в процессе.
Шаг 1: оценить текущий ландшафт
Запустите Ruff в “диагностическом” режиме (без фиксов) и соберите статистику по типам ошибок. Это можно сделать командой ruff check с выводом. Идея: понять, какие категории будут мешать больше всего.
Шаг 2: включить правила по сегментам
Например:
- сначала базовые
F/E(потенциальные ошибки и базовые ошибки стиля), - затем
I(импорты), - затем
UP/B(синтаксические обновления и потенциальные баги).
Шаг 3: использовать временные исключения и пер-file игнорирование
Если у вас большая кодовая база, “вылечить всё” в одном PR почти всегда плохо:
- вы получаете огромный дифференс;
- ревью становится механическим;
- высок шанс конфликтов.
Вместо этого:
- создайте временные
per-file-ignoresдля веток, где проблем больше всего; - планируйте постепенное удаление игноров по мере рефакторинга.
Шаг 4: договоритесь о правилах автофикса в команде
Чётко решите:
- кто запускает автопочинку (
ruff check --fixили pre-commit)? - разрешены ли unsafe-fix?
- нужно ли согласование через PR или фикс сразу коммитится?
Там, где это возможно, pre-commit помогает удерживать единообразие. Но важно, чтобы конфигурация pre-commit совпадала с CI.
Портативные примеры команд: как применять Ruff локально и в CI
Ниже — типовые команды, которые помогают поддерживать процесс.
Проверка без фикса
ruff check .
Проверка только по изменённым файлам (полезно в больших репозиториях)
В зависимости от вашей системы контроля версий можно ограничивать список. Например, для Git:
git diff --name-only origin/main...HEAD | xargs -r ruff check
Автофиксы безопасных преобразований
ruff check . --fix
Запуск в CI (чёткий контроль)
Обычно CI запускают так, чтобы не было “тихих” режимов:
ruff check . --output-format=github
Если вы используете форматтеры/линтеры в связке, поддерживайте одинаковые параметры в локальной среде и в CI.
Как добиться одинакового качества: дисциплина конфигурации и тесты на стабильность
Ruff — статический инструмент, и качество его работы зависит от дисциплины:
1) Версионируйте конфигурацию вместе с кодом
Изменения в pyproject.toml должны идти в PR вместе с логикой и рефакторингом. Иначе вы получите ситуацию: CI ловит новое правило, но разработчик не увидит изменения в конфигурации локально.
2) Введите правило: “конфиг — не предмет обсуждения в каждом PR”
Не нужно спорить каждый раз: почему включили/не включили конкретный код. Пусть решение принято один раз и оформлено в конфиг.
3) Делайте “волны” улучшений
Вместо того чтобы каждый месяц расширять набор правил и ловить накопленный долг, удобно:
- раз в спринт добавлять небольшое количество правил,
- фиксировать предупреждения в текущей волне,
- удалять временные игноры.
4) Следите за правдоподобностью сообщений
У Ruff сообщения обычно конкретные. Но если вы отключаете правило “везде” или игнорируете “на всё”, потеряете пользу. Лучше точечные пер-file игноры и понятные причины.
Рекомендованный рабочий процесс для команды
Чтобы Ruff стал «машиной компоновки», команда должна договориться о рутинной схеме:
- Локально разработчик запускает
ruff checkперед коммитом (лучше pre-commit). - PR содержит либо нулевые lint-ошибки, либо логически объяснимое временное исключение.
- CI проверяет всё репозиторий, не скрывая ошибки.
- Правила добавляются итеративно, с планом по устранению “хвоста”.
- Автофиксы применяются единообразно (и фиксируются в диффе, чтобы ревью видело изменения).
Заключение: Ruff как единый стандарт — и когда лучше учиться глубже
Ruff выгоден тем, что позволяет командам держать единый стиль и качество кода в одном движке: выбрали набор правил, настроили target-version, аккуратно применили автофиксы, разрулили импортные вопросы и спланировали миграции без “стены ошибок”. Главное — не пытаться включить максимальную строгость сразу, а выстроить устойчивый процесс: от baseline до постепенного расширения правил.
Если вы хотите разобрать механику выбора правил, конфигов и типичных ошибок внедрения более системно, вам может помочь курс «ruff – для начинающих!» — как способ быстро собрать практическую базу перед тем, как переходить к командной настройке и миграциям.
Комментарии
Пока нет комментариев