JavaRush /Курсы /Claude code /Commit discipline и PR packaging

Commit discipline и PR packaging

Claude code
18 уровень , 3 лекция
Открыта

1. Коммит — не сохранение, а точка отката

На этом этапе баг уже не висит в воздухе: root cause зафиксирован, regression test поймал старое поведение, минимальный fix дал небольшой diff и доказательства закрытия дефекта. Теперь задача меняется — осталось упаковать изменение так, чтобы его можно было читать, откатывать и ревьюить без археологии.

Когда вы только начинаете работать с Git, commit легко перепутать с обычным сохранением файла: раз код уже написан и лежит на диске, кажется, что commit — это просто ещё один способ сказать компьютеру «запомни, пожалуйста». Но в реальной разработке нужен он не компьютеру, а вам завтрашнему, ревьюеру и команде как маленький, законченный и объяснимый шаг.

Полезно держать в голове очень простую разницу:

Что это Для кого На какой вопрос отвечает
Сохранение файла Для вас прямо сейчас «Я не потеряю текст?»
Commit Для вас и команды «Какой законченный шаг я сделал?»
PR Для ревьюера и истории проекта «Что изменилось, зачем и как это проверить?»

В нашем проекте AI Commerce Growth OS возьмём знакомый пример: вы чините баг, где checkout с пустой корзиной возвращал 500 вместо понятной 400. Если всё это лежит одним большим грязным diff’ом, откатывать страшно, а читать тяжело; если же у вас есть маленькие commit’ы, вы буквально режете задачу на проверяемые куски.

Здесь полезно ещё раз вспомнить идею из предыдущих лекций: Claude edits = proposals expressed as diffs. Commit — момент, когда вы говорите: «Этот кусок я понял и готов за него отвечать». Пока его нет, изменение висит в серой зоне между «похоже, работает» и «я отвечаю».

Обычно задача живёт в отдельной ветке. Ветка отвечает на вопрос «над чем мы вообще работаем?», а commit — на «какой шаг завершён?». Например:

git checkout -b bugfix/issue-432-empty-cart-checkout
# Switched to a new branch 'bugfix/issue-432-empty-cart-checkout'

Это уже неплохо: из имени видно, что речь о багфиксе и конкретной задаче. Но если внутри один гигантский commit правки, пользы от аккуратного имени почти ноль. Ветка без хороших commit’ов — как папка, где всё называется новое2_финал_точно.

Хороший commit — это маленькая точка отката с понятной подписью, а не склад всего, что случайно накопилось за вечер.

2. Одну задачу — на маленькие коммиты

Самая частая проблема у новичков здесь не в Git-командах, а в масштабе мышления. Очень хочется сначала довести всё до конца, а потом одним commit’ом зафиксировать весь результат: баг ведь один, значит и commit должен быть один. Но ревьюер читает не название задачи из трекера, а реальную историю изменений — свалите её в один мешок, и она перестаёт быть историей.

Лучший ориентир здесь — approved implementation plan, который вы подготовили раньше: каждый шаг плана — кандидат в отдельный commit. Для бага про пустую корзину нарезка естественная: сначала regression-тест, который доказывает дефект, потом минимальное исправление в контроллере, чтобы вернуть 400 до вызова сервиса. История получается здоровая:

git log --oneline -2
# 7f3c1a2 fix(orders): вернуть 400 при checkout с пустой корзиной
# 19ab8d4 test(orders): добавить regression-тест для пустой корзины

Посмотрите, сколько понятности в двух строках: даже без открытия файлов видно, что сначала мы зафиксировали дефект тестом, а потом исправили поведение. Это не просто красиво — это удобно для rollback, для blame, для будущего вас, который через три недели поймёт, откуда в checkout отдельная проверка.

Практически это означает, что вы не делаете слепой git add ., если Claude по пути затронул что-то ещё. Добавляете только файлы текущего шага:

git add src/orders/OrderControllerTest.java
git commit -m "test(orders): добавить regression-тест для пустой корзины"

git add src/orders/OrderController.java
git commit -m "fix(orders): вернуть 400 при checkout с пустой корзиной"

Такой подход особенно важен в работе с AI. Claude любит положить рядом с полезным изменением ещё и мелкий «бонус»: форматирование в соседнем файле, переименование переменной чуть дальше по коду, импорт «заодно». На уровне одной сессии это кажется безобидным, но на уровне commit’а — уже шум. Файл не должен попадать в commit только потому, что «уже менялся».

Есть хороший простой тест: если вы не можете в одном предложении объяснить, что именно сделал этот commit, значит commit слишком большой.

Именно так commit discipline держит scope creep на уровне PR: пока задача живёт в маленьких тематических commit’ах, вам гораздо труднее незаметно протащить в багфикс ещё рефакторинг, обновление зависимости и «уборку по пути». Git в этом смысле — хороший воспитатель: молчаливый, но принципиальный.

3. Сообщение коммита против археологии

Теперь к самому недооценённому месту всей истории — тексту commit message. Многие относятся к нему как к формальности, тем более что Claude или IDE «что-нибудь сгенерируют». Но это не подпись ради подписи, а краткая документация к одному инженерному шагу: если она слабая, история проекта быстро превращается в археологический раскоп.

Хорошее сообщение коммита отвечает минимум на два вопроса: что изменилось и в каком контексте это нужно. Формат может отличаться от команды к команде, но смысл остаётся — и часто соглашение удобно держать прямо в CLAUDE.md, чтобы Claude не изобретал стиль заново в каждой задаче.

Например, для Commerce OS правило может выглядеть так:

## Правила коммитов

Формат: <тип>(<область>): <краткое действие>

Примеры:
- fix(orders): вернуть 400 при checkout с пустой корзиной
- test(orders): добавить regression-тест для пустой корзины
- docs(api): уточнить пример ответа для order history

Заметьте: тут нет магии. Язык — русский или английский — дело договорённости команды. Важнее другое: одинаковая форма делает историю сканируемой глазами.

Сравните:

Слабый вариант Сильный вариант
fix
fix(orders): вернуть 400 при checkout с пустой корзиной
правки
test(orders): добавить regression-тест для пустой корзины
final fix
fix(orders): прервать checkout до вызова CheckoutService

Разница здесь не косметическая: по слабому варианту через месяц не понять, что сделано, по сильному — понятно даже без открытия diff.

Иногда полезно добавить и тело commit’а, если причина неочевидна:

git commit -m "fix(orders): вернуть 400 при checkout с пустой корзиной" \
  -m "Пустой список товаров доходил до CheckoutService и заканчивался серверной ошибкой.
Проверка добавлена в OrderController до вызова сервиса. Связано с ISSUE-432."

Такое тело особенно ценно, когда вы не просто меняете строчку, а фиксируете мотивацию решения: почему проверка в контроллере, а не в сервисе? Почему 400, а не 422? Короткое пояснение экономит время тому, кто потом будет читать историю.

Claude здесь действительно может помочь — но только если вы даёте ему правильную задачу:

Посмотри на текущий diff и предложи одно сообщение коммита.
Не придумывай изменений, которых нет.
Если видишь файлы вне текущего шага плана, сначала перечисли их отдельно.
Верни короткий заголовок и, если нужно, 1-2 строки тела коммита.

Ключевая мысль здесь — Claude предлагает, вы сверяете с diff. Если сообщение получилось красивым, но в нём упомянут тест, которого вы не добавляли, или файл не из commit’а, — брать такой текст без проверки нельзя: уверенный тон не делает commit message правдивым.

4. От истории коммитов к PR за полминуты

Когда маленькие commit’ы уже собраны, легко подумать: «Ну всё, теперь ревьюер сам разберётся по истории». Иногда — да, но чаще — нет. PR — это не просто пачка commit’ов, а упакованный рассказ об изменении. Ревьюер почти никогда не начинает с git log: он начинает с заголовка, описания и секции проверки — и если там хаос, то дальше diff читается уже в раздражённом режиме.

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

Схема здесь простая:

flowchart TD
    A[Approved plan] --> B[Маленькие commit'ы]
    B --> C[Читаемая история Git]
    C --> D[PR_DESCRIPTION.md]
    D --> E[Review-ready PR]

И ещё один полезный взгляд на артефакты:

Артефакт На какой вопрос отвечает
Ветка Над какой задачей мы работаем?
Commit Какой шаг завершён?
Сообщение коммита Что именно и зачем изменилось в этом шаге?
PR title В чём суть всего изменения целиком?
PR_DESCRIPTION.md
Как проверить результат, какие риски и что не входило в scope?

Очень часто начинающие разработчики делают PR title слишком техническим: add null check in OrderController. Формально это правда, но ревьюеру полезнее видеть смысл на уровне поведения: Запретить checkout с пустой корзиной и вернуть 400. Первый описывает реализацию, второй — результат, и для PR важнее второй. То же самое касается description: не дубль diff, а сжатый контекст.

Если в команде есть шаблон PR, это сильно упрощает жизнь — даже простой шаблон уже держит структуру:

## Что изменилось
## Зачем
## Как проверить
## Риски и крайние случаи
## Вне scope

Это кажется мелочью, но на деле спасает от главной болезни AI-упаковки — красивого, но бесформенного текста: когда у вас есть фиксированные секции, и вы, и Claude гораздо реже скатываетесь в расплывчатое «улучшили обработку ошибок».

5. PR_DESCRIPTION.md: walkthrough и границы

Теперь самое главное — сам PR walkthrough. Это не технический термин ради солидности, а маршрут для ревьюера: вы не заставляете человека прыгать по файлам вслепую, а проводите его через изменение сверху вниз. Храните его как PR_DESCRIPTION.md, а потом переносите в тело pull request.

Хороший walkthrough не пересказывает каждую строку — он отвечает на четыре вопроса: что изменилось, почему, как проверить и что осталось за рамками задачи. И вот здесь очень кстати пригождаются результаты предыдущих лекций: Root cause statement идёт в «Зачем», regression evidence превращается в «Как проверить», approved plan помогает честно заполнить «Вне scope».

Вот как может выглядеть PR_DESCRIPTION.md для нашего bugfix в Commerce OS:

# PR walkthrough — ISSUE-432

## Что изменилось
- `OrderController#checkout`: добавлена проверка пустой корзины
- `OrderControllerTest`: добавлен regression-тест `rejectsEmptyCart`

## Зачем
Пустая корзина доходила до `CheckoutService` и заканчивалась серверной ошибкой,
вместо понятного ответа клиента `400`.

## Как проверить
- `./gradlew :orders:test --tests OrderControllerTest.rejectsEmptyCart`
- POST `/api/orders` с пустым списком товаров должен вернуть `400`

## Риски и крайние случаи
Контракт успешного checkout не менялся. Изменение затрагивает только сценарий пустой корзины.

## Вне scope
- логика deduplication в корзине
- endpoint `/orders/preview`

Обратите внимание, насколько это короче реального diff’а — и насколько полезнее на первом чтении. Ревьюер уже понимает, где искать изменения и как воспроизвести результат, ему не нужно гадать, почему вдруг появился новый тест.

Особенно важен раздел «Как проверить». Здесь абстракции вроде «все тесты зелёные» бесполезны — ревьюер не знает, что именно вы запускали; гораздо сильнее работает конкретика:

./gradlew :orders:test --tests OrderControllerTest.rejectsEmptyCart   # тест проходит
curl -X POST http://localhost:8080/api/orders -d '{ "items": [] }'    # HTTP 400

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

Не менее полезен раздел «Вне scope» — он выглядит скучно, но именно он защищает PR от ложных ожиданий. Если вы чините checkout с пустой корзиной, это не значит, что вы обязаны заодно переписать preview endpoint, улучшить валидацию всех заказов и поправить старый TODO в соседнем сервисе. Когда вы честно называете границы, ревьюер видит их и меньше склонен считать PR «неполным».

А вот раздел с рисками нужен не для драматизации, а для честности. Иногда риск действительно минимальный — тогда так и пишите. Но если есть открытый вопрос — не проверялся старый мобильный клиент, не затрагивался нестандартный flow, — лучше назвать это явно. Честный PR всегда сильнее идеального по тону, но мутного по содержанию.

6. Claude в packaging — помощник, не редактор

Когда дело доходит до оформления commit’ов и PR, Claude очень удобен: сжимает diff в понятный текст, не даёт забыть секции в PR_DESCRIPTION.md, предлагает заголовок, напоминает про проверки. Но именно здесь легче всего расслабиться и начать принимать хороший текст за хорошую инженерную упаковку.

Безопасный способ работать с Claude в packaging-фазе выглядит так:

Посмотри на текущий diff и подготовь черновик `PR_DESCRIPTION.md`.
Опирайся только на:
- текущий diff;
- последние commit'ы;
- список реально выполненных проверок.

Не придумывай файлов, тестов и рисков, которых нет.
Если замечаешь изменения вне scope, сначала предупреди об этом.

Такой запрос хорош тем, что вы сразу ограничиваете источник правды: не «вспомни, что мы делали», не «суммируй сессию», а именно — вот diff, вот commit’ы, вот реальные проверки. Так меньше шанс, что Claude красиво додумает то, чего в PR нет.

После этого ваша работа — сверить черновик с фактами: открываете git diff, git log --oneline, смотрите свои команды проверки и буквально сравниваете. Лишний файл — удаляете. «Обновлены API docs», которых вы не трогали, — исправляете. «All related tests passed» при одной прицельной проверке — переписываете честно.

Если у команды уже настроен CLAUDE.md, шаблон PR и напоминание перед commit, Claude работает заметно лучше, потому что у него появляется контур. Но даже при идеальном контуре финальная инженерная ответственность остаётся у вас — и это не бюрократия, а простая логика: merge делает команда, баги ловит production, а не красиво написанный markdown.

В хорошо упакованном PR ревьюер не должен гадать, что произошло. Он открывает изменение и сразу видит историю: какой баг исправлен, почему выбрано это решение, чем проверено, где границы задачи. Когда вы умеете собирать такую историю из маленьких commit’ов и честного PR_DESCRIPTION.md, ваш diff перестаёт быть просто набором правок и становится нормальным инженерным артефактом.

1
Задача
Claude code, 18 уровень, 3 лекция
Недоступна
Claude CLI draft для PR walkthrough
Claude CLI draft для PR walkthrough
1
Задача
Claude code, 18 уровень, 3 лекция
Недоступна
Создание `PR_DESCRIPTION.md` по diff и плану
Создание `PR_DESCRIPTION.md` по diff и плану
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ