$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогПайплайн проверки качества: pre-commit, линтеры и форматирование без «войны стилей»

Пайплайн проверки качества: pre-commit, линтеры и форматирование без «войны стилей»

$ sudo teach IT
·23 июля 2026 г.·10 мин·49
Пайплайн проверки качества: pre-commit, линтеры и форматирование без «войны стилей»

Соберём практическую схему: что запускать локально, что в CI и как договориться о едином стиле команды с минимумом шума.

Содержание
Зачем нужен пайплайн: где именно возникают конфликтыИнструментальная база: что именно использовать в PythonБазовая схема: локально быстро, в CI гарантированноЧто запускать локально (pre-commit)Что запускать в CIОпционально: split “format vs lint” в конфигурацииНастройка ruff и форматирования без конфликтовПример pyproject.tomlНастройка pre-commit: делаем хук, который не превращает жизнь в адБазовый .pre-commit-config.yamlКогда форматтер должен быть “check”, а когда “fix”Автообнаружение файлов и скоростьCI: как гарантировать, что в PR нет сюрпризовПример GitHub ActionsВажность кэширования и версийДоговорённость о стиле: как избежать “войны правил”Практика: принцип “миграции к стандарту”Командные правила: фиксируйте не “мнения”, а параметрыПротиворечия: когда ruff ругается на то, что сам форматтер сделалПолитика авто-исправлений: сколько “магии” допустимоПрактический чеклист внедрения в командуШаг 1. Закрепите версии инструментовШаг 2. Настройте единый pyproject.tomlШаг 3. Приведите код к стандарту (если есть legacy)Шаг 4. Включите pre-commit и договоритесь о политике фиксовШаг 5. Настройте CI как репликацию локальной логикиШаг 6. Настройте коммуникациюКак это выглядит для разработчика: поведенческая модельТипичные ошибки и как их избежатьОшибка 1. Два форматтера в проектеОшибка 2. CI проверяет одно, а pre-commit другое

Когда в команде появляется несколько разработчиков, очень быстро выясняется, что качество кода — это не только про “правильные алгоритмы”, но и про единообразие: одинаковые правила оформления, воспроизводимые проверки, отсутствие случайных отличий в стиле и форматировании, которые создают лишние диффы в PR. Проблема усугубляется тем, что линтеры, форматтеры и проверки часто спорят друг с другом: один ругается на стиль, второй форматирует обратно, третий считает это “изменением”. Итог — ощущение, что инструментов много, а пользы мало.

Ниже — практическая схема пайплайна проверки качества, которая снижает шум в PR, делает поведение инструментов предсказуемым и помогает команде договориться о едином стиле с минимальными “войнами” — как на локальных машинах, так и в CI. Речь пойдёт про Python, но подход применим почти к любому языку: локально — быстрые проверки, в CI — гарантии, а форматирование — отдельный этап без двусмысленностей.

Зачем нужен пайплайн: где именно возникают конфликты

Почти все конфликты в стиле и качестве сводятся к трём источникам:

  1. Непоследовательные инструменты. Например, форматирование одним инструментом, а линт на другой набор правил. В результате линтер считает “ошибкой” то, что форматтер намеренно сделал.
  2. Неподходящее место запуска. Если форматтер гоняется в CI, но разработчики не запускают его локально, каждый PR превращается в повторный “раунд правок”.
  3. Отсутствие “единого источника правды”. Команда не зафиксировала правила (версии, настройки, приоритеты), и каждый применяет “свои” параметры.

Правильная архитектура пайплайна выглядит так:

  • pre-commit отвечает за быстрые проверки и авто-исправления до коммита.
  • линтеры отвечают за статические проблемы качества (ошибки, потенциальные баги, анти-паттерны).
  • форматтер отвечает за внешний вид (стандартизирует код).
  • CI подтверждает, что всё прошло и результат воспроизводим.

Ключевой принцип: форматтер должен либо запускаться перед линтером, либо быть частью правила, которое линтер не ломает. Иначе вы получаете вечный цикл “линтер ругается — форматтер правит — линтер снова ругается”.

Инструментальная база: что именно использовать в Python

Для Python сегодня есть “устойчивый” стек, который обычно хорошо работает в командах:

  • ruff как линтер и набор правил стиля/антипаттернов.
  • ruff format как форматтер, чтобы не плодить несколько движков форматирования.
  • pre-commit для управления хукaми на локальной стороне.
  • CI (GitHub Actions / GitLab CI / другая система) для проверки каждого PR.

Почему именно ruff:

  • Он умеет быть и линтером, и частью форматирования (через ruff format).
  • Настройки можно свести в единый pyproject.toml.
  • В отличие от “зоопарка” линтеров, проще договориться о приоритетах.

Чтобы читателю было проще стартовать, можно рассматривать курс “ruff – для начинающих!” как дополнительный путь освоения инструмента глубже: /course/ruff-free. Важно, что в рамках команды основная договорённость всё равно делается через конфигурацию и пайплайн, а не через “знания откуда-то”.

Базовая схема: локально быстро, в CI гарантированно

Ниже — пример архитектуры, которая минимизирует шум и гарантирует согласованность.

Что запускать локально (pre-commit)

На локальной стороне важно:

  • проверять и исправлять форматирование,
  • ловить очевидные проблемы качества,
  • не заставлять разработчика ждать 3–10 минут на каждом коммите.

Практически: pre-commit конфигурируется так, чтобы:

  1. форматтер запускался первым (он может вносить изменения),
  2. линтер запускался вторым (он проверяет результат форматирования),
  3. в случае ошибок линтер/форматтер “останавливают” коммит.

Что запускать в CI

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

  • линтер/статические проверки для всего репозитория (а не только изменившихся файлов),
  • единообразная версия инструментов (зафиксировать в зависимостях),
  • (опционально) прогон тестов, mypy и т.д.

Если линтер и форматтер уже отрабатывают в pre-commit, то в CI мы в основном подтверждаем соответствие “всему репозиторию стандарту”.

Опционально: split “format vs lint” в конфигурации

Хорошая практика — разделять:

  • formatting (приведение к стилю),
  • linting (семантические и структурные проверки).

Форматирование должно либо автоматически исправлять, либо чётко фиксировать ожидания (“код обязан быть отформатирован”). Линтер — всегда проверяет, не исправляя “стиль” втихаря (или вы явно делаете это частью команды).

Настройка ruff и форматирования без конфликтов

Сначала определим единый набор правил. Всё удобно хранить в pyproject.toml.

Пример pyproject.toml

code
[tool.ruff]
target-version = "py311"
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "W", "I"]  # пример: ошибки/flake8-совместимые + imports
ignore = []

# Правила можно настраивать точнее, но главное — договориться команде.
# Например, можно явно отключать то, что вас не волнует.
# [tool.ruff.lint.per-file-ignores]
# "tests/*" = ["S101"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false

Что важно:

  • Одна конфигурация для команды. pyproject.toml становится “источником правды”.
  • Один форматтер. Если вы используете ruff format, не добавляйте альтернативный форматтер, который будет менять то же самое (или делайте это осознанно и не в одной итерации).
  • Одинаковая версия ruff в команде. Иначе одна и та же конфигурация может давать разные результаты форматирования/линтинга.

Если команда ещё не уверена в наборах правил ruff, лучше начать с “меньшего” набора и накапливать опыт. Принцип такой: сначала устранить шум (format + базовые ошибки), затем расширять правила до “качества, которое реально защищает от багов”.

Настройка pre-commit: делаем хук, который не превращает жизнь в ад

pre-commit управляет цепочкой проверок. Он поддерживает хуки, которые могут исправлять файлы (например, форматтер). В этом случае pre-commit обычно делает так: обнаружил несоответствие — запустил инструмент — применил изменения — повторно проверил.

Базовый .pre-commit-config.yaml

Пример для ruff:

code
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.9
    hooks:
      - id: ruff-format
        args: ["--check"]
      - id: ruff
        args: ["--fix"]
        # Важно: decide whether you want auto-fix in linters.
        # Если включить --fix, ruff будет править часть проблем.
        # Это снижает ручную работу, но может менять код неожиданно.

Разберём нюансы:

  • ruff-format с --check означает: форматтер проверяет, что файл уже отформатирован. В зависимости от конфигурации pre-commit и конкретного хука может быть режим авто-исправления. Если вам нужно именно “почини сам”, обычно убирают --check и позволяют хуку форматировать.
  • ruff --fix — полезная опция, если вы доверяете автоматическим исправлениям. Но если команда хочет минимизировать изменения на коммите (и обсуждать правки осмысленно), можно отключить --fix и оставить только диагностику.
  • Версию rev лучше закреплять. Иначе через месяц другой участник может получить другой формат.

Практический компромисс, который часто хорошо работает в командах:

  • форматирование всегда выполняется или проверяется в строгом режиме,
  • линтер обычно диагностирует, а авто-фикс включают только для безопасных правок (в ruff это более тонко настраивается, но концепт такой).

Когда форматтер должен быть “check”, а когда “fix”

Выбор влияет на пользовательский опыт:

  • Форматтер “check”: разработчик получает ошибку, если формат не соответствует; коммит блокируется. Плюс — меньше неожиданных изменений в коммите. Минус — нужно запускать форматтер вручную или иметь отдельный “шаг” перед коммитом.
  • Форматтер “fix”: pre-commit сам меняет файлы и коммит в итоге проходит. Плюс — меньше лишних команд. Минус — разработчик может быть удивлён тем, что его “исходник” изменился.

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

Если хотите именно управлять процессом, делайте явную дисциплину:

  • “Мы форматируем всегда до линтинга”.
  • “После pre-commit всё уже в консистентном виде”.

Автообнаружение файлов и скорость

pre-commit по умолчанию анализирует только затронутые файлы, что делает локальную проверку быстрой. Но в CI вы всё равно хотите прогнать всё репозиторий целиком, иначе вы можете пропустить “древние” несоответствия.

CI: как гарантировать, что в PR нет сюрпризов

CI должен быть “скептиком”: если локальный пайплайн должен привести код к стандарту, CI проверяет, что стандарт действительно соблюдён.

Пример GitHub Actions

code
name: quality

on:
  pull_request:
  push:
    branches: [ "main" ]

jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install ruff

      - name: Ruff format check
        run: ruff format --check .

      - name: Ruff lint
        run: ruff check .

Что стоит отметить:

  • ruff format --check . гарантирует, что в репозитории нет “неотформатированных” файлов.
  • ruff check . выполняет линтинг.

Если вы хотите добавить автоматические исправления в CI, обычно это плохая идея: CI не должен превращаться в “робота, который коммитит обратно”. Лучше заблокировать PR и попросить привести код в соответствие. Это устраняет часть конфликтов в процессе ревью.

Важность кэширования и версий

Чтобы CI не был слишком медленным, обычно добавляют кэш пакетов или используют “замороженные” окружения. Но первично — воспроизводимость. Версия ruff в CI должна совпадать с версией в pre-commit.

Если вы держите ruff как зависимость в проекте, можно закрепить версию в requirements-dev или pyproject (через tool.poetry/uv/etc.) и использовать её в обоих местах.

Договорённость о стиле: как избежать “войны правил”

Самая частая ошибка — “давайте включим всё самое строгое”. В итоге появляются тысячи замечаний, и команда перестаёт доверять инструментам. Лучше строить стиль как систему, которую легко поддерживать.

Практика: принцип “миграции к стандарту”

Есть два сценария:

  1. Зелёное поле: проект новый или вы начинаете с нуля — включаете проверки сразу.
  2. Унаследованный код: есть большой объём файлов со старым стилем — мигрируете постепенно.

Для второго сценария типичный подход:

  • Сначала включаете форматирование и автоматически приводите репозиторий к единому виду (один раз).
  • Затем запускаете линтер с минимальным набором правил, которые не ломают архитектурные решения.
  • После того, как шум ушёл, расширяете правила постепенно.

Это особенно важно для ruff: его “select”/“ignore” позволяет контролировать степень строгости.

Командные правила: фиксируйте не “мнения”, а параметры

Ваша цель — превратить обсуждение “как принято оформлять” в чтение конфигурации:

  • line-length и правила переносов,
  • семейство правил (какие ошибки ловим, какие предупреждения игнорируем),
  • параметры форматирования (кавычки, отступы, etc.).

Когда обсуждение сходит на нет, остается только “что написано в pyproject.toml”. Это снимает эмоциональную часть споров.

Противоречия: когда ruff ругается на то, что сам форматтер сделал

Такое бывает, если:

  • вы используете разные инструменты форматирования (black/yapf/autopep8),
  • в линтере включены правила, которые не совпадают с форматтером,
  • версии инструментов расходятся.

Решение:

  • используйте один форматтер (например, ruff format),
  • линтер настраивайте так, чтобы он не конфликтовал с форматом (обычно это автоматически достигается, когда оба инструмента от ruff),
  • закрепляйте версии.

Если всё равно видите конфликт, полезно сделать репродукцию: один файл, команда ruff format и ruff check в чистом окружении. Тогда становится ясно, что именно идёт “в разрез”.

Политика авто-исправлений: сколько “магии” допустимо

Опыт подсказывает: авто-фикс в pre-commit — это хорошо, если он:

  • делает небольшие и ожидаемые правки (import, простые замены, приведение к стилю),
  • не меняет поведение программы.

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

  • Если авто-фикс включён, разработчик может не заметить, что файл изменился.
  • Ревьюеру придётся проверять дифф, который кажется “не по делу”.

Компромисс:

  • форматтер пусть правит (или пусть строго проверяет),
  • линтер пусть чаще диагностирует, а --fix включайте только на “безопасные” наборы.

В ruff можно тонко регулировать, что считать фиксируемым, но концептуально команда должна решить: “мы доверяем автоматическим исправлениям, но в пределах разумного”.

Практический чеклист внедрения в команду

Ниже — последовательность, которая помогает избежать типичных “не взлетело”.

Шаг 1. Закрепите версии инструментов

  • Выберите версию ruff.
  • Используйте её и в pre-commit (через rev), и в CI (через зависимость или явный pip install ruff==...).

Шаг 2. Настройте единый pyproject.toml

  • Правила линтинга и форматирования — там.
  • Не храните часть настроек в отдельных местах.

Шаг 3. Приведите код к стандарту (если есть legacy)

  • Запустите ruff format и при необходимости ruff check --fix.
  • Коммитьте результат отдельным PR/коммитом, чтобы потом не было “трудно понять, что изменилось”.

Шаг 4. Включите pre-commit и договоритесь о политике фиксов

  • Форматирование: либо “fix”, либо “check”, но согласованно.
  • Линтер: “diagnose” и/или “auto-fix” — осознанно.

Шаг 5. Настройте CI как репликацию локальной логики

  • В CI прогоняйте те же шаги: формат check + lint check.
  • CI не должен “молчаливо” использовать другой набор команд.

Шаг 6. Настройте коммуникацию

Даже лучший пайплайн не работает, если разработчики не понимают, что делать при красном статусе:

  • Введите правило: “если pre-commit не прошёл — запусти форматтер/линтер локально”.
  • Дайте короткий документ: команды и порядок.

Как это выглядит для разработчика: поведенческая модель

Представим процесс:

  1. Разработчик меняет код.
  2. Делает commit.
  3. pre-commit:
    • форматирует/или проверяет формат,
    • запускает ruff check,
    • блокирует коммит при несоответствиях.
  4. В PR CI повторяет:
    • ruff format --check,
    • ruff check.

Результат:

  • Дифф в PR почти всегда “чистый” и связан с реальными изменениями, а не с ручной правкой стиля.
  • Команда перестаёт спорить о кавычках и отступах: это вопрос автоматики.
  • Линтер концентрируется на том, что действительно может быть проблемой.

Типичные ошибки и как их избежать

Ошибка 1. Два форматтера в проекте

Например, ruff format + black. Итог — циклы правок. Решение: оставьте один форматтер, второй уберите.

Ошибка 2. CI проверяет одно, а pre-commit другое

Например, в pre-commit включены дополнительные правила, а CI использует базовый ruff check . без тех

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

ruff – для начинающих!

Курс по теме

ruff – для начинающих!

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

Бесплатно
Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

Запуск и поддержка проекта на Ruff в команде: формат “как в CI”, а не “как договорились”
запуск

Запуск и поддержка проекта на Ruff в команде: формат “как в CI”, а не “как договорились”

Как правильно выстроить конфигурацию Ruff (линтинг, форматирование, автозамены) и сделать так, чтобы одинаковые правила гарантировались в локальной сборке и на CI без ручного шаманства.

16 сентября 2026 г.
1050
Введение в ruff-линтеры для старта: правила, которые быстрее всего окупаются
введение

Введение в ruff-линтеры для старта: правила, которые быстрее всего окупаются

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

14 сентября 2026 г.
650
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1090
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

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

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Зачем нужен пайплайн: где именно возникают конфликтыИнструментальная база: что именно использовать в PythonБазовая схема: локально быстро, в CI гарантированноЧто запускать локально (pre-commit)Что запускать в CIОпционально: split “format vs lint” в конфигурацииНастройка ruff и форматирования без конфликтовПример pyproject.tomlНастройка pre-commit: делаем хук, который не превращает жизнь в адБазовый .pre-commit-config.yamlКогда форматтер должен быть “check”, а когда “fix”Автообнаружение файлов и скоростьCI: как гарантировать, что в PR нет сюрпризовПример GitHub ActionsВажность кэширования и версийДоговорённость о стиле: как избежать “войны правил”Практика: принцип “миграции к стандарту”Командные правила: фиксируйте не “мнения”, а параметрыПротиворечия: когда ruff ругается на то, что сам форматтер сделалПолитика авто-исправлений: сколько “магии” допустимоПрактический чеклист внедрения в командуШаг 1. Закрепите версии инструментовШаг 2. Настройте единый pyproject.tomlШаг 3. Приведите код к стандарту (если есть legacy)Шаг 4. Включите pre-commit и договоритесь о политике фиксовШаг 5. Настройте CI как репликацию локальной логикиШаг 6. Настройте коммуникациюКак это выглядит для разработчика: поведенческая модельТипичные ошибки и как их избежатьОшибка 1. Два форматтера в проектеОшибка 2. CI проверяет одно, а pre-commit другое