Ruff: мой опыт выселения legacy-линтеров и повышения производительности кода

от автора

Какими инструментами для линтинга и форматирования Python-кода вы пользуетесь? Black, Isort, Flake? Их существует множество, каждый следует своей цели, хотя некоторые пересекаются по функциональности. Одни могут нравиться за автономность, другие — за возможности конфигурирования. И наверняка вы слышали о Ruff, который обещается заменить собой все. 

Привет, Хабр! Я Гена, Python-разработчик в Selectel. В этой статье я опишу свой опыт перевода проекта на Ruff: что понравилось, что — не очень, к чему готовиться и, если все же решитесь, то как это сделать. Добро пожаловать под кат.

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

Что обещает Ruff

Ruff — это чрезвычайно быстрый линтер для Python, написанный на Rust. Он обещает заменить собой многие инструменты, мгновенно проверять код и автоматически исправлять ошибки. К тому же он поддерживает современные стандарты, версии Python, интеграцию с основными IDE.

Вообще-то изначально я не сильно беспокоился о том, что в проектах, над которыми работал, было множество инструментов для форматирования кода. Я думал, что работают, ну и ладно, кто-то до меня же их установил. Но все же в некоторых небольших проектах скорость работы инструментов периферии меня не устраивала. А когда дело доходило до больших, она и вовсе начинала меня напрягать. И не только меня.

Как я перевозил готовый проект

Ну и как вы, наверное, понимаете, в один день я занялся прекрасной инициативой — принялся за исследование линтеров и форматтеров. По заявлениям из официальной документации, Ruff быстрее в 10-100 раз Flake8 и Black. Это внушало надежду.

Для ясности расскажу о первом проекте, который я переводил на Ruff. У нас в Selectel есть геймификация для сотрудников: весь год зарабатываешь баллы (селекоины) за различные достижения, а потом тратишь их на мерч, сувениры и разные прикольные штуки. Это называется Selectel Shop. Так вот, работает этот Selectel Shop на Python, и в нем была как раз уйма олдовых инструментов форматирования, от которых хотелось избавиться.

Как было «до»

Я быстро загорелся идеей перейти в этом проекте от легаси-инструментов к чему-то поновее. К тому же каждый раз при работе над фичей в тестовых окружениях приходилось ждать выполнение пайплайна CI/CD, в котором тоже выполнялись автоматические проверки кода линтерами.

Если рассказать подробнее, то в Selectel Shop использовались те самые Black и Flake8 со множеством плагинов, isort для сортировки импортов, pyupgrade, Autoflake и несколько pre-commit хуков (trailing-whitespace, end-of-file-fixer, debug-statements). По сути, все эти настройки с плагинами и аргументами можно было настроить в Ruff в виде правил проверки, выполняющих те же задачи.

Как стало «после»

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

Стоит сказать, что процесс перехода занял далеко не пять минут, как это бывает с коробочными решениями. Пришлось поэтапно определять функционал старых инструментов, находить правило в Ruff и тестировать. К этому делу нужно подходить с головой и закладывать время, чтобы со всем разобраться. Но результат безусловно того стоит.

Немного наглядности

Давайте я покажу несколько примеров того, что конкретно и как изменилось. Ниже на скрине один из сотни фиксов, которые Ruff предложил исправить. Если в новой версии Python улучшили синтаксис или типизацию, вы получаете рекомендацию по доработке кода:

Затем убираем легаси и улучшаем код:

В тоже время может быть предложена корректировка типизации, которая функционально сути не меняет:

Что вы тут видите? Казалось бы, какие-то мелкие изменения на уровне небольших настроек, мелкой оптимизации и прочего. Но в итоге когда я внедрил Ruff и убрал лишнее, мой конфиг pre-commit, выполняемый в процессе разработки, стал отрабатывать не пять секунд (по большей части, из-за плагинов Flake8), а за считанное мгновение. А если использовать Ruff в более объемных проектах, то можно сэкономить несколько дней жизни при ожидании работы линтера локально или в пайплайнах 🙂

Игровой сервер с криперами и порталом в Незер. Добывайте ресурсы, стройте объекты, исследуйте мир Selectel в Minecraft и получайте призы.

Исследовать →

Мои выводы

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

Также радуют преимущества, которые получились в итоге:

  • единая конфигурация — все правила и настройки управляются в одном файле toml,

  • скорость — Ruff действительно работает быстрее аналогов,

  • автоматические исправления определенных правил.

Впрочем, если вы решитесь переходить на Ruff, то вам будет полезно знать о подводных камнях. Особенность этого инструмента в том, что его нужно обязательно конфигурировать. Не получится установить и забыть, как было, например, с flake8. Как выяснилось на практике, у коробочных решений были свои очевидные плюсы. Вы не тратите время на конфигурацию и исследование возможностей, а просто устанавливаете и используете. 

Ruff нужно изучить и подумать о том, какие проверки нужны вашему проекту. Теперь есть необходимость работать над списком правил проверки и отрабатывать те, которые ранее игнорировались. А еще обсуждать это все с коллегами и разбираться, что полезно, а что нет. Это, кстати, хорошая возможность внедрить новое полезное правило и получить респект от команды! А когда конфигурация будет готова, останется только наслаждаться скоростью исполнения проверок.

Хорошо это или нет, решать вам. Поскольку с Python нашей команде приходится работать много, мы решили масштабировать опыт с Ruff на другие проекты. Скорректировали список правил для проверки в зависимости от специфики сервиса и полезности применения, протестировали, обсудили, внедряли — это новый командный процесс, который появился у нас. А чтобы в будущем ничего не пропало, все нюансы фиксировали в Confluence. Кстати, угадайте, кто этим занимался 🙂 

Итак, если вы решите переехать на Ruff, то вот вам инструкция. Надеюсь, она окажется полезна.

Алгоритм действий по внедрению в проект

Ниже я пошагово опишу, что и как нужно делать, чтобы переехать на Ruff. 

Установка Ruff

Установить Ruff в виртуальном окружении можно согласно официальной документации:

# Установка с помощью UV uv add --dev ruff # Установка с помощью Poetry poetry add ruff --group dev

Настройка файла конфигурации ruff.toml

  1. Добавляем максимальное число правил, от которых точно понимаем полезный эффект. Если не уверены в полезности, изучаем и обсуждаем с командой. Выбор правил может зависеть от проекта. Например, для проектов на FastApi следует добавить правило «FAST».

  2. Исправляем ошибки, если добавленные правила приводят к несущественным по времени исправлениям в коде. Если требуются серьезные исправления, добавляем правила в список игнорируемых (чем конкретнее код правила, тем лучше).

Пример одной из моих конфигураций ruff.toml:

# Поддержка версии Python target-version = "py312"   # Основные настройки line-length = 120 # Максимальная длина строки для кода indent-width = 4 # Ширина отступа   # Каталоги, которые следует учитывать при разрешении импорта из first/third-party источников src = ["src"]   # Настройки линтера [lint] # Нестабильные и находящиеся в предварительной версии правила preview = true # Правила, которые должен применять линтер select = [     "UP",   # pyupgrade     "I",    # isort     "E",    # flake8-errors     "W",    # flake8-warnings     "F",    # flake8-pyflakes     "B",    # flake8-bugbear     "C4",   # flake8-comprehensions     "S",    # flake8-bandit     "BLE",  # flake8-blind-except     "T20",  # flake8-print     "T10",  # flake8-debugger     "PT",   # flake8-pytest-style     "RUF",  # Ruff     "DTZ",  # flake8-datetimez     "ERA",  # eradicate     "FURB", # refurb ] # Игнорирование конкретных предупреждений линтера ignore = [     "C408",  # Ненужный вызов dict, list и т.д.     "RUF001", # Игнорирование похожих символов с разными ASCII     "RUF002", # Игнорирование похожих символов с разными ASCII     "RUF003", # Игнорирование похожих символов с разными ASCII     "S110",  # try-except-pass ] # Исключение директорий и файлов из линтинга exclude = [     "src/tests/**", ] # Ошибки, которые могут быть исправлены автоматически fixable = [     "I", # cортировка испортов ]   # Настройки форматтера [format] quote-style = "double" # 'single' для одинарных, "double" для двойных кавычек indent-style = "space"   # Дополнительные настройки для плагинов [lint.flake8-bandit] check-typed-exception = true # Проверка типизированных исключений

Также конфигурацию можно поместить в pyproject.toml и не выделять в отдельный файл. Полный список правил можно посмотреть в официальной документации.

Обновление конфига .pre-commit

Чтобы Ruff проверял код автоматически перед отправкой в git, необходимо добавить его в файл .pre-commit-config.yaml

Нужно указать хуки для запуска линтера и форматтера. Для линтера рекомендую указать аргументы:

  • —fix (исправить правила, добавленные в раздел fixable конфигурации Ruff),

  • —show-fixes (для вывода исправлений в консоль).

Подробнее можно прочитать в документации в разделе интеграции с pre-commit.

- repo: https://github.com/astral-sh/ruff-pre-commit   rev: v0.9.7   hooks:     # Run the linter.     - id: ruff       args: [ --fix, --show-fixes]     # Run the formatter.     - id: ruff-format

Удаление ненужных зависимостей

  • Нужно убрать из pyproject.toml неактуальные конфигурации. Пример: «tool.isort».

  • Удалить файлы конфигураций неактуальных инструментов. Пример: tox.ini, .isort.cfg, .pylintrc.

  • Удалить из файла .pre-commit-config.yaml конфигурации неактуальных инструментов. Пример: flake8, isort, autoflake, pyupgrade.

Локальное тестирование Ruff

Необходимо запустить скрипт pre-commit или отдельные команды Ruff для линтинга и форматирования кода.

# получаем отчет об ошибках, найденным в проекте ruff check --statistics    # получаем отчет о файлах, требующих форматирования в проекте ruff format --check   ruff format --check --diff # Также покажет какие именно изменения будут в виде diff   # Для реального исправление ошибок линтинга и форматирования, запускаем команды ниже.  # Ruff автоматически внесет доступные исправления. Остальные рекомендации линтера нужно обработать самостоятельно.   # Исправление через pre-commit pre-commit run --all-files   # Исправление через команды ruff ruff check --fix ruff format

Дополнительно

Для соблюдения оптимальной сборки Docker-образа рекомендуется добавить .ruff_cache в файл .dockerignore.

Какой опыт с Ruff был у вас? Возможно, у вас сложилось другое мнение об этом инструменте. Расскажите в комментариях! Оставлю небольшое напутствие: применяйте новые тулзы для улучшения своей работы и кайфуйте!


ссылка на оригинал статьи https://habr.com/ru/articles/928272/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *