1. Коміт — не збереження, а точка відкату
На цьому етапі баг уже не висить у повітрі: першопричину зафіксовано, регресійний тест упіймав стару поведінку, мінімальне виправлення дало невеликий diff і докази закриття дефекту. Тепер задача змінюється — лишилося упакувати зміну так, щоб її можна було читати, відкотити й рев’юити без археології.
Коли ви тільки починаєте працювати з Git, коміт легко переплутати зі звичайним збереженням файла: раз код уже написаний і лежить на диску, здається, що коміт — це просто ще один спосіб сказати комп’ютеру «запам’ятай, будь ласка». Але в реальній розробці він потрібен не комп’ютеру, а вам завтрашньому, рев’юеру й команді як малий, завершений і зрозумілий крок.
Корисно тримати в голові дуже просту різницю:
| Що це | Для кого | На яке запитання відповідає |
|---|---|---|
| Збереження файла | Для вас просто зараз | «Я не втрачу текст?» |
| Коміт | Для вас і команди | «Який завершений крок я зробив?» |
| PR | Для рев’юера та історії проєкту | «Що змінилося, навіщо і як це перевірити?» |
У нашому проєкті AI Commerce Growth OS візьмемо знайомий приклад: ви виправляєте баг, де checkout з порожнім кошиком повертав 500 замість зрозумілої 400. Якщо все це лежить одним великим брудним diff’ом, відкотити страшно, а читати важко; якщо ж у вас є маленькі коміти, ви буквально ріжете задачу на перевірювані шматки.
Тут корисно ще раз пригадати ідею з попередніх лекцій: Claude edits = пропозиції, виражені як diff’и. Коміт — момент, коли ви кажете: «Цей шматок я зрозумів і готовий за нього відповідати». Поки його немає, зміна висить у сірій зоні між «схоже, працює» та «я відповідаю».
Зазвичай задача живе в окремій гілці. Гілка відповідає на запитання «над чим ми взагалі працюємо?», а коміт — на «який крок завершено?». Наприклад:
git checkout -b bugfix/issue-432-empty-cart-checkout
# Перемкнулися на нову гілку 'bugfix/issue-432-empty-cart-checkout'
Це вже непогано: з імені видно, що йдеться про багфікс і конкретну задачу. Але якщо всередині один гігантський коміт правки, користі від акуратної назви майже нуль. Гілка без гарних комітів — як папка, де все названо нове2_фінал_точно.
Гарний коміт — це маленька точка відкату з понятним підписом, а не склад усього, що випадково накопичилося за вечір.
2. Одну задачу — на маленькі коміти
Найчастіша проблема у новачків тут не в Git-командах, а в масштабі мислення. Дуже хочеться спочатку довести все до кінця, а потім одним комітом зафіксувати весь результат: баг же один, отже і коміт має бути один. Але рев’юер читає не назву задачі з трекера, а реальну історію змін — скиньте її в один мішок, і вона перестає бути історією.
Найкращий орієнтир тут — схвалений план реалізації, який ви підготували раніше: кожен крок плану — кандидат на окремий коміт. Для бага про порожній кошик нарізка природна: спочатку регресійний тест, який доводить дефект, потім мінімальне виправлення в контролері, щоб повернути 400 до виклику сервісу. Історія виходить здоровою:
git log --oneline -2
# 7f3c1a2 fix(orders): повернути 400 під час checkout з порожнім кошиком
# 19ab8d4 test(orders): додати регресійний тест для порожнього кошика
Подивіться, скільки зрозумілості в двох рядках: навіть без відкриття файлів видно, що спочатку ми зафіксували дефект тестом, а потім виправили поведінку. Це не просто красиво — це зручно для rollback, для blame, для вашого майбутнього «я», яке через три тижні зрозуміє, звідки в checkout окрема перевірка.
Практично це означає, що ви не робите сліпий git add ., якщо Claude дорогою зачепив ще щось. Додаєте лише файли поточного кроку:
git add src/orders/OrderControllerTest.java
git commit -m "test(orders): додати регресійний тест для порожнього кошика"
git add src/orders/OrderController.java
git commit -m "fix(orders): повернути 400 під час checkout з порожнім кошиком"
Такий підхід особливо важливий у роботі з AI. Claude любить покласти поруч із корисною зміною ще й дрібний «бонус»: форматування в сусідньому файлі, перейменування змінної трохи далі по коду, імпорт «заодно». На рівні однієї сесії це здається нешкідливим, але на рівні коміту — уже шум. Файл не повинен потрапляти в коміт тільки тому, що «вже змінювався».
Є гарний простий тест: якщо ви не можете одним реченням пояснити, що саме зробив цей коміт, значить коміт занадто великий.
Саме так дисципліна комітів стримує розповзання scope на рівні PR: поки задача живе в маленьких тематичних комітах, вам набагато важче непомітно протягнути в багфікс ще рефакторинг, оновлення залежності й «прибирання по дорозі». Git у цьому сенсі — хороший вихователь: мовчазний, але принциповий.
3. Повідомлення коміту проти археології
Тепер до найбільш недооціненого місця всієї історії — тексту commit message. Багато хто ставиться до нього як до формальності, тим більше що Claude або IDE «щось згенерують». Але це не підпис заради підпису, а коротка документація до одного інженерного кроку: якщо вона слабка, історія проєкту швидко перетворюється на археологічний розкоп.
Гарне повідомлення коміту відповідає щонайменше на два запитання: що змінилося і в якому контексті це потрібно. Формат може відрізнятися від команди до команди, але сенс лишається — і часто домовленість зручно тримати прямо в CLAUDE.md, щоб Claude не вигадував стиль заново в кожній задачі.
Наприклад, для Commerce OS правило може виглядати так:
## Правила комітів
Формат: <тип>(<область>): <коротка дія>
Приклади:
- fix(orders): повернути 400 під час checkout з порожнім кошиком
- test(orders): додати регресійний тест для порожнього кошика
- docs(api): уточнити приклад відповіді для order history
Зверніть увагу: тут немає магії. Мова — російська чи англійська — справа домовленості команди. Важливіше інше: однакова форма робить історію сканованою очима.
Порівняйте:
| Слабкий варіант | Сильний варіант |
|---|---|
|
|
|
|
|
|
Різниця тут не косметична: за слабким варіантом через місяць не зрозуміти, що зроблено, за сильним — зрозуміло навіть без відкриття diff.
Іноді корисно додати й тіло коміту, коли причина неочевидна:
git commit -m "fix(orders): повернути 400 під час checkout з порожнім кошиком" \
-m "Порожній список товарів доходив до CheckoutService і закінчувався серверною помилкою.
Перевірку додано в OrderController до виклику сервісу. Пов’язано з ISSUE-432."
Таке тіло особливо цінне, коли ви не просто змінюєте рядок, а фіксуєте мотивацію рішення: чому перевірка в контролері, а не в сервісі? Чому 400, а не 422? Коротке пояснення економить час тому, хто потім читатиме історію.
Claude тут справді може допомогти — але лише коли ви даєте йому правильну задачу:
Подивись на поточний diff і запропонуй одне повідомлення коміту.
Не вигадуй змін, яких немає.
Якщо бачиш файли поза поточним кроком плану, спочатку переліч їх окремо.
Поверни короткий заголовок і, за потреби, 1-2 рядки тіла коміту.
Ключова думка тут — Claude пропонує, ви звіряєте з diff. Якщо повідомлення вийшло красивим, але в ньому згадано тест, який ви не додавали, або файл не з коміту, — брати такий текст без перевірки не можна: упевнений тон не робить commit message правдивим.
4. Від історії комітів до PR за пів хвилини
Коли маленькі коміти вже зібрані, легко подумати: «Ну все, тепер рев’юер сам розбереться по історії». Іноді — так, але частіше — ні. PR — це не просто пачка комітів, а упакована розповідь про зміну. Рев’юер майже ніколи не починає з git log: він починає із заголовка, опису й секції перевірки — і якщо там хаос, то далі diff читається вже в роздратованому режимі.
Уявіть людину, яка відкриває ваш PR між двома зустрічами, з горнятком кави й десятьма сповіщеннями. Вона не хоче бути археологом — вона хоче дуже швидко зрозуміти суть, перевірку й межі.
Схема тут проста:
flowchart TD
A[Схвалений план] --> B[Маленькі commit'и]
B --> C[Читабельна історія Git]
C --> D[PR_DESCRIPTION.md]
D --> E[PR, готовий до рев’ю]
І ще один корисний погляд на артефакти:
| Артефакт | На яке запитання відповідає |
|---|---|
| Гілка | Над якою задачею ми працюємо? |
| Коміт | Який крок завершено? |
| Повідомлення коміту | Що саме і навіщо змінилося в цьому кроці? |
| PR title | У чому суть усієї зміни цілком? |
|
Як перевірити результат, які ризики й що не входило в 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 не переказує кожен рядок — він відповідає на чотири запитання: що змінилося, чому, як перевірити і що залишилося за межами задачі. І ось тут дуже доречні результати попередніх лекцій: формулювання першопричини йде в «Навіщо», regression evidence перетворюється на «Як перевірити», approved plan допомагає чесно заповнити «Поза scope».
Ось як може виглядати PR_DESCRIPTION.md для нашого bugfix у Commerce OS:
# Огляд PR — ISSUE-432
## Що змінилося
- `OrderController#checkout`: додано перевірку порожнього кошика
- `OrderControllerTest`: додано регресійний тест `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 — помічник, не редактор
Коли справа доходить до оформлення комітів і 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 і нагадування перед комітом, Claude працює помітно краще, тому що в нього з’являється контур. Але навіть за ідеального контуру фінальна інженерна відповідальність лишається на вас — і це не бюрократія, а проста логіка: merge робить команда, баги ловить production, а не красиво написаний markdown.
У добре упакованому PR рев’юер не повинен гадати, що сталося. Він відкриває зміну й одразу бачить історію: який баг виправлено, чому обрано саме це рішення, чим перевірено, де межі задачі. Коли ви вмієте збирати таку історію з маленьких комітів і чесного PR_DESCRIPTION.md, ваш diff перестає бути просто набором правок і стає нормальним інженерним артефактом.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ