Ruff против Flake8, Black и isort для более простой проверки Python-кода
Ruff против Flake8, Black и isort: узнайте, где Ruff заменяет отдельные инструменты, где нет, и как перейти без лишнего напряжения для команды.

Почему стек линтинга в Python расползается
Большинство проблем с линтингом в Python начинается с хороших намерений. Команда добавляет Flake8, чтобы ловить очевидные ошибки. Потом кто-то подключает Black, чтобы закончить споры о стиле. Затем появляется isort, потому что порядок импортов всё время меняется. Каждое решение по отдельности разумно, но стек растёт кусками вместо того, чтобы следовать одному понятному плану.
Вот так и начинается дрейф. Один инструмент проверяет стиль, другой переписывает стиль, а третий меняет импорты так, что это влияет на оба первых. Через несколько месяцев люди перестают спрашивать: "Какое у нас правило?" — и начинают спрашивать: "Почему CI упал, если у меня в редакторе всё было нормально?"
Пересечения только усугубляют ситуацию. Одно правило ругается на длину строки. Форматтер всё равно переносит её по‑своему. Сортировка импортов меняет структуру файла, и линтер тут же находит проблемы, которых минуту назад ещё не было. Команды начинают спорить об инструментах вместо кода.
Качество ревью тоже падает. Если pull request смешивает изменения логики с сотнями правок форматирования, полезные комментарии просто теряются. Ревьюер может пропустить плохое условие или рискованный запрос к базе, потому что diff забит изменением кавычек, переносами импортов и исправлениями пустых строк. Хороший код должен помогать ревью, а не тонуть в нём.
Простой пример показывает, как это выглядит. Один разработчик запускает Black при сохранении. Другой использует только Flake8 в терминале. В CI стоит Flake8 с плагином, который никто не установил локально. Код проходит на одном ноутбуке, падает в пайплайне, а потом снова проходит после коммита с форматированием. Фича не сломалась, но команда всё равно потеряла полчаса.
Расхождение между локальной средой и CI обычно раздражает сильнее всего. В редакторе один конфиг, в pre-commit другой, на сервере сборки третий. Как только появляется такой разрыв, люди перестают доверять проверкам. Они запускают команды по два раза, слишком быстро добавляют игнорирующие комментарии или просто ждут, пока CI сам подскажет, что исправить.
Именно поэтому вопрос "Ruff против Flake8, Black и isort" так часто всплывает в командах, которым нужна более простая настройка. Проблема не только в скорости. Дело в медленном накоплении лишних правил, лишнего конфига и лишних мест, где один и тот же файл может меняться по разным причинам.
Что на самом деле делает каждый инструмент
У большинства Python-команд в старой схеме было три отдельные задачи в инструментальном наборе. Один инструмент проверяет код на проблемы, другой автоматически меняет форматирование, а третий приводит в порядок импорты. Такой раздел работ работал, но он же создал больше файлов конфигурации, больше настроек в редакторах и больше шансов на несоответствие.
Flake8 — это проверяющий инструмент в старой схеме. Он читает Python-файлы и отмечает нарушения правил: неиспользуемые импорты, не определённые имена, проблемы с длиной строк и множество проверок от плагинов. Сам по себе Flake8 довольно компактный. Но команды часто расширяют его плагинами, поэтому два проекта, которые оба говорят "у нас есть Flake8", могут вести себя совершенно по‑разному.
Black делает другую работу. Он автоматически форматирует код и даёт команде очень мало вариантов выбора. В этом и смысл. Вместо того чтобы неделями спорить о стиле кавычек или переносах строк, вы запускаете Black и принимаете результат. Не всем нравятся все решения форматтера, но он хорошо убирает бессмысленные споры.
isort занимается импортами. Он группирует их, сортирует и приводит к одинаковому порядку. Это звучит мелко, пока в проекте не становится сотни файлов и несколько человек не начинают трогать одни и те же модули. Аккуратный порядок импортов уменьшает шум в diff'ах и делает файлы легче для чтения.
Ruff гораздо сильнее сближает эти задачи. Он может работать как быстрый линтер, покрывать многие правила, которые команды раньше брали из Flake8 и его плагинов, сортировать импорты в той же зоне, что и isort, и ещё форматировать код. Для многих команд это значит, что один инструмент может заменить большую часть старого стека или даже весь стек.
Но инструменты не одинаковы. У Black всё ещё свой стиль форматирования и длинная история в Python-проектах. Некоторые плагины Flake8 по‑прежнему проверяют вещи, которые команда хочет сохранить. У isort тоже есть настройки, на которые завязаны отдельные репозитории. И всё же, если цель — меньше движущихся частей, Ruff обычно первый инструмент, который стоит попробовать.
Где Ruff заменяет старые части
Ruff обычно берёт на себя больше, чем люди ожидают. Если у вашей команды есть Flake8 с несколькими плагинами, плюс Black, плюс isort, Ruff часто может покрыть большую часть этого набора в одном месте. Это уменьшает дрейф конфигурации и снижает количество мелких правок форматирования, из-за которых pull request выглядит шумным.
Начните с сопоставления текущей настройки Flake8 с группами правил Ruff. Команды часто забывают, сколько логики спрятано в плагинах, которые они добавили несколько лет назад. Быстрое сопоставление обычно выглядит так:
EиWпокрывают большую часть проверокpycodestyleFпокрывает обнаружение ошибок в стилеPyflakesBзаменяет большую частьflake8-bugbearIотвечает за сортировку импортов вместо isortUPприменяет многие исправления изpyupgrade
Такой быстрый аудит показывает, заменяет ли Ruff большую часть ваших правил линтинга или только часть из них. Если ваш конфиг зависит от редкого правила плагина, проверьте это правило до перехода. Не держите лишний инструмент по привычке. Оставляйте его только если эта недостающая проверка реально ловит баги, которые важны для вашей команды.
С форматированием нужен такой же трезвый взгляд. Ruff Formatter близок к Black, но не идентичен ему. Если у вас в репозитории уже много лет живёт вывод Black и вам нужны почти нулевые diff'ы по форматированию, сначала проверьте Ruff на реальной ветке, а уже потом принимайте решение. Даже небольшие различия в стиле могут создать много шума в оживлённом кодовой базе.
Главная мысль проста: сначала убирайте пересечение. Если два инструмента постоянно трогают одни и те же строки по разным причинам, это обычно первое, что нужно привести в порядок.
Когда отдельные инструменты всё ещё нужны
Ruff умеет многое, но полный переход не всегда лучший первый шаг. Иногда команда экономит больше времени, если временно оставляет один или два старых инструмента, чем если резко рвёт всё сразу, а потом долго разгребает последствия.
Одна из частых причин — правило из плагина Flake8, на которое команда всё ещё опирается. Если это правило находит реальные ошибки в ревью или предотвращает баги в продакшене, оставьте его, пока не убедитесь, что Ruff даёт тот же сигнал. Скорость — это хорошо. Убирать проверку, которой команда доверяет, — плохая сделка.
Форматирование — ещё один случай, когда отдельный инструмент можно оставить. Если в репозитории уже много лет используется Black и вам не нужен вообще никакой diff по форматированию, оставить Black на время — разумно. Даже крошечные изменения стиля могут засыпать pull request шумом и помешать увидеть важный код.
Постепенный переход также хорошо работает, если у вас несколько репозиториев. Один сервис может перейти на Ruff за день, а другой всё ещё зависит от старых CI-задач, других версий Python или более строгих правил релиза. Это нормально, особенно когда у нескольких проектов общие скрипты и шаблоны.
Настройка редакторов часто влияет на сроки сильнее, чем сам линтер. Если разработчики всё ещё завязаны на старые pre-commit-хуки, задачи в IDE или shell-скрипты, которые запускают Flake8, Black и isort отдельно, резкий переход может создать несколько дней путаницы. Одна скучная неделя миграции — это нормально. Месяц сообщений в стиле "почему упал CI" — нет.
Небольшой пример это хорошо показывает. Один новый внутренний инструмент может почти без боли перейти на Ruff, а customer-facing репозиторий остаётся на Black в CI, потому что команде важно сохранить стабильность diff'ов перед релизом. Это разумное разделение. Временные расхождения в консистентности легче пережить, чем шумные diff'ы и сломанные редакторские сценарии.
Сначала убирайте пересечения. Оставляйте то, что защищает качество кода или снижает лишние изменения. Потом удаляйте старые инструменты, когда команда больше от них не зависит. Ещё помогает записать, какие репозитории всё ещё живут на каком наборе инструментов, и назначить дату для пересмотра исключений, чтобы временные решения не превратились в постоянный беспорядок.
Как перейти шаг за шагом
Не переключайте инструменты в одну спешную половину дня. Сначала зафиксируйте текущее поведение, а потом двигайтесь по одному слою за раз. Так переход остаётся скучным, а это хорошо, когда вы трогаете общую систему линтинга.
Сначала запустите Ruff в режиме проверки на том репозитории, какой он есть сейчас. Вам нужно увидеть разрыв до того, как вы измените правила. Если вы сравниваете Ruff с Flake8, Black и isort, первый запуск быстро покажет, в основном ли репозиторий уже согласен с текущими правилами или за годы накопились десятки маленьких исключений.
Обычно достаточно простого плана миграции:
- Добавьте Ruff в репозиторий, не удаляя старые инструменты.
- Включите только те семейства правил, которые команда уже соблюдает.
- Сначала оставьте автоисправления выключенными, чтобы люди могли посмотреть на результат.
- Запланируйте один pull request на шум от форматирования и сортировки импортов.
- Оставьте старые проверки в CI, пока Ruff не перестанет ничего находить в течение нескольких дней.
Второй шаг важнее, чем кажется. Команды часто попадают в ловушку, когда воспринимают миграцию как шанс переписать весь стиль-гайд. Это звучит эффективно, но на самом деле смешивает две разные задачи: замену инструментов и изменение стандартов. Делайте это отдельно. Сначала заставьте новый инструмент как можно ближе совпасть с текущими ожиданиями. А если потом вы всё ещё хотите более строгие правила, добавьте их позже, когда команда сможет спокойно обсудить их отдельно.
Порядок изменений тоже важен. Сначала сравните результаты линтинга. Потом решите, что делать с импортами. Потом проверьте форматирование. Если менять всё сразу, трудно понять, проблема в пропущенных правилах, в другом поведении сортировки или в выводе форматтера.
Закрепите версию Ruff заранее. Если у одного разработчика стоит новее версия, чем в CI, вы снова возвращаетесь к той же проблеме доверия, которую пытались решить.
Простой пример для команды
У команды из четырёх Python-разработчиков есть настройка, знакомая многим. Они используют Flake8 для линтинга, Black для форматирования и isort для импортов. У каждого инструмента свой хук, свои настройки и свой способ падать с ошибкой.
Проблемы начинаются с мелочей. Один разработчик сохраняет файл, Black его переформатирует, и локально коммит проходит. В CI isort меняет импорты, поэтому файл уже не совпадает с тем, что Black проверил минуту назад. Потом Flake8 жалуется на перенос строки, который появился только после этих изменений.
Никто не сломал фичу. Инструменты просто спорят друг с другом.
Через несколько недель pull request'ы становятся шумными. Ревьюер открывает PR, который должен был касаться исправления бага в обработчике API, а половина diff'а — это порядок импортов, пробелы и переносы строк. Комментарии уходят в сторону от самого кода. Люди тратят время на вопрос "Почему этот файл вообще изменился?" вместо проверки логики.
Команда тоже теряет доверие к настройке. Один человек запускает хуки перед push. Другой полагается на CI. Новый коллега копирует старый конфиг из другого проекта, и в репозитории уже две разные настройки длины строки. Вот так дрейф инструментов и проявляется в жизни: не как одна большая катастрофа, а как десять мелких неудобств каждую неделю.
Потом они переходят на Ruff и оставляют настройку скучной. Один файл конфигурации. Одна команда для локальных проверок. Один шаг в CI. Одно место, где меняются правила.
Ruff обрабатывает линтинг, сортировку импортов и форматирование в одном workflow, поэтому порядок операций перестаёт создавать проблемы. Файл исправляется один раз, а не три раза.
Следующий PR выглядит иначе. Diff меньше. Комментарии ревьюеров касаются логики запроса и пограничного случая в обработке ошибок. Команда по‑прежнему заботится о стиле, но стиль больше не перехватывает разговор.
Вот в этом и практическая польза. Более простая настройка не делает код идеальным. Она просто убирает слой лишнего шума, а этого часто достаточно, чтобы маленькая команда работала быстрее.
Ошибки, которые создают шум
Большая часть боли от линтинга связана не с самим Ruff. Она появляется из-за спешки в настройке, старого конфигурационного багажа и того, что команда меняет слишком много сразу.
Самая шумная ошибка — включить все правила в первый же день. Это выглядит строго, но обычно просто заваливает репозиторий предупреждениями, которые никто не собирается исправлять на этой неделе. Люди перестают читать вывод, и настройка линтинга превращается в фоновой шум. Начните с форматирования и небольшого набора проверок, которые ловят реальные ошибки. Добавляйте новые правила только после того, как команда начнёт доверять результатам.
Старые списки игноров создают другой тип беспорядка. Команды часто копируют настройки Flake8 в Ruff и предполагают, что названия правил означают одно и то же. Где-то так и есть, где-то нет, а некоторые игноры вообще отражают старые привычки, о которых уже никто не помнит. Если мигрировать, не проверяя каждый код, можно спрятать полезные предупреждения или оставить мёртвый конфиг на месяцы.
Шум в ревью становится ещё сильнее, когда изменения стиля и изменения поведения попадают в один большой коммит. Ревьюеру не стоит искать реальный баг среди 2 000 строк сортировки импортов, изменения кавычек и перенесённых строк. Разделяйте работу. Сначала прогоните форматтер и закоммитьте только эти правки. Потом внесите изменения в логику в отдельном коммите или отдельном pull request.
Дрейф редакторов — ещё одна частая проблема. Разработчик сохраняет файл, редактор форматирует его по‑своему, а CI отклоняет его с другими настройками. Это не проблема качества кода. Это проблема настройки. Используйте одну и ту же версию Ruff, один и тот же конфиг и одинаковое поведение форматирования при сохранении в локальных инструментах и в CI.
Ещё одна ловушка — спорить о стиле после того, как форматтер уже выбрал его. Если Ruff одинаково форматирует импорты, пробелы и переносы строк каждый раз, не возвращайтесь к этим спорам в code review. Используйте время ревью на названия, тесты, edge cases и на то, будет ли код легко менять потом.
Более спокойный подход не выглядит эффектно. Проверьте старые коды игнора. Оставьте первый набор правил маленьким. Отделяйте коммиты с форматированием от коммитов с логикой. Сделайте поведение редакторов полностью одинаковым с CI. Считайте вывод форматтера окончательным, если только не возникла реальная проблема с читаемостью.
Небольшая команда может потратить полдня на дрейф инструментов. А может за 20 минут один раз привести конфиг в порядок и заняться настоящим кодом.
Что проверить перед переключением
Смена инструмента кажется простой, пока она не изменит сотни файлов, не начнёт спорить с CI или не уберёт правило, которое вашей команде действительно нужно. Прежде чем выбирать сторону в споре Ruff против Flake8, Black и isort, проверьте несколько вещей на своём коде, а не на учебном проекте.
Начните с правил, которые вы используете сегодня. Многие команды годами держат старые плагины и игноры, даже когда они уже не важны, так что это хороший момент навести порядок. Если Ruff делает 90 процентов вашей реальной работы, а недостающие 10 процентов никогда не ловили настоящие баги, обычно это хорошая сделка.
Несколько проверок делают картину понятнее:
- Сравните текущие правила и плагины Flake8 с теми правилами Ruff, которые вы собираетесь включить.
- Прогоните обе настройки на одной и той же выборке файлов и смотрите на реальные отличия, а не только на общее число ошибок.
- Проверьте форматтер Ruff на реальной ветке с грязными импортами, длинными строками, сгенерированными файлами и старыми модулями.
- Попросите коллегу настроить всё с нуля и посчитать, сколько команд ему нужно до первого чистого запуска.
CI важнее, чем идеальная локальная демонстрация. Если разработчики видят один результат на ноутбуке и другой в пайплайне, они перестают доверять инструменту. Закрепите версию Ruff, используйте один и тот же конфиг в обоих местах и проверяйте именно ту команду, которую будет запускать CI-job.
Форматированию нужен отдельный тест. Форматтер может хорошо выглядеть на новых файлах и при этом создавать шумные diff'ы в старых папках. Возьмите смешанную выборку: один активный сервис, один утилитный пакет и один каталог с тестами. Посмотрите на изменения как на обычный pull request. Если diff кажется шумным или его трудно читать, остановитесь и подстройте настройки до запуска в продакшн-процесс.
Последняя проверка простая: сможет ли новый коллега клонировать репозиторий и запустить одну команду? Это важнее, чем многие готовы признать. Чистая настройка не должна требовать длинной памятки или трёх отдельных инструментов в правильном порядке. Если одна команда даёт тот же результат, что и CI, значит, вы действительно сократили дрейф инструментов.
Что делать дальше
Если вы выбираете между Ruff, Flake8, Black и isort, начните с одного репозитория, а не с миграции всей компании. Возьмите активный Python-проект, откройте ветку и запустите Ruff рядом с текущими проверками. Такой небольшой тест очень быстро покажет, есть ли у команды реальные требования к правилам или просто старые привычки к инструментам.
Запишите несколько правил, которые вы действительно хотите сохранить. Большинству команд нужно меньше, чем они думают. Оставьте проверки, которые находят баги, сделайте форматирование предсказуемым и уберите правила, которые только создают шум в ревью.
Обычно лучше работает плавный переход, а не большой день чистки. Попробуйте Ruff на одном репозитории, пока текущие CI-задачи всё ещё работают. Сравните вывод Ruff с Flake8, Black и isort, затем отметьте все важные пробелы. Сложите выбранные правила в один файл конфигурации и считайте его источником истины. Заставьте редакторы, pre-commit-хуки и CI использовать именно этот конфиг, прежде чем удалять старые шаги.
Этот общий конфиг важнее, чем кажется. Дрейф инструментов начинается тогда, когда один разработчик сохраняет файл с настройками редактора по умолчанию, другой использует другую версию hook'а, а CI применяет третий набор правил. Одна команда и один результат могут сэкономить очень много времени на бессмысленном ревью.
Если у вас несколько Python-репозиториев, сначала тестируйте самый грязный. Аккуратный демонстрационный проект может дать ложную уверенность. Репозиторий со старыми игнорами, смешанными стилями и медленным CI покажет, выдержит ли переход ежедневную работу.
Некоторым командам нужна помощь не только с линтингом. Если ваши Python-инструменты, CI и процесс релизов уже превратились в беспорядок, Олег Сотников на oleg.is может посмотреть на настройку как fractional CTO или советник. Такой внешний взгляд особенно полезен, когда проблема не в одном инструменте, а в наборе мелких несостыковок в workflow, из-за которых команда постоянно тормозит.
Создайте ветку, напишите короткий список правил и посмотрите, что сломается. Вы довольно быстро поймёте, упрощает ли Ruff вашу настройку или сначала нужны несколько исключений.
Часто задаваемые вопросы
Может ли Ruff сам заменить Flake8, Black и isort?
Обычно да. Если ваша команда использует Flake8, Black и isort для стандартного линтинга, форматирования и сортировки импортов, Ruff часто может заменить все три инструмента. Но сначала проверьте это на реальном репозитории, особенно если вы опираетесь на старые плагины Flake8 или хотите, чтобы вывод Black почти не менялся.
Будет ли Ruff форматировать код точно так же, как Black?
Не всегда. Ruff Formatter близок к Black, но небольшие отличия всё равно могут затронуть много файлов. Если в репозитории уже много лет используется форматирование Black и вам нужны очень маленькие diff'ы, сначала прогоните Ruff на отдельной ветке и посмотрите на результат.
Что делать, если проект зависит от плагинов Flake8?
Проверьте плагины по одному, а не гадайте. Сопоставьте правила, которые команда использует сейчас, и посмотрите, какие группы правил Ruff их покрывают. Отдельный инструмент оставляйте только тогда, когда он действительно находит проблемы, которые по‑прежнему важны для команды.
Как избежать огромных diff'ов во время перехода?
Разделите форматирование и изменения логики. Сначала прогоните форматтер и сортировку импортов в отдельном коммите с чисткой кода, а затем в отдельном коммите или pull request внесите изменения в саму логику. Тогда ревьюеры смогут сосредоточиться на поведении, а не на пробелах, кавычках и перестановке импортов.
Стоит ли оставить Black, если я внедряю Ruff?
Да, это может быть разумно на время. Команды часто оставляют Black в CI, когда хотят стабильные diff'ы, а линтинг и сортировку импортов переносят на Ruff в первую очередь. Так переход проходит мягче, и не нужно заставлять каждый репозиторий меняться одновременно.
Как безопаснее всего перейти на Ruff?
Начните с малого. Добавьте Ruff, включите только те группы правил, которые вы уже используете, и закрепите одну и ту же версию Ruff в локальных инструментах и CI. Старые проверки оставьте до тех пор, пока Ruff не станет тихим и команда не начнёт доверять результатам.
Почему код проходит локально, но падает в CI?
Обычно расхождение появляется по трём причинам: разные версии инструментов, разные конфиги и разные команды запуска. Исправьте это, закрепив одну версию Ruff, храня один конфиг в репозитории и заставив редакторы, pre-commit и CI выполнять одну и ту же команду.
Нужно ли включать все правила Ruff в первый день?
Нет. Если включить всё сразу, репозиторий завалит предупреждениями, и люди быстро перестанут читать вывод. Начните с правил, которые находят реальные ошибки и держат форматирование стабильным, а остальные добавляйте только тогда, когда команде это действительно нужно.
Как лучше проверить Ruff перед переходом?
Используйте свой код, а не учебный пример. Прогоните Ruff и текущую настройку на одних и тех же файлах, сравните реальные различия и проверьте форматтер на старых модулях тоже. Попросите одного коллегу установить всё с нуля и посмотрите, сколько шагов ему нужно до первого чистого запуска.
Какой репозиторий лучше мигрировать первым, если у меня несколько Python-проектов?
Попробуйте самый проблемный активный репозиторий первым. Старые игноры, смешанные стили и медленный CI гораздо быстрее покажут реальный объём работ, чем аккуратный проект. Как только в этом репозитории всё заработает с одним конфигом и одной командой, остальным обычно становится проще.