JavaRush /Курси /Claude code /Конфлікти та стратегія злиття треків

Конфлікти та стратегія злиття треків

Claude code
Рівень 16 , Лекція 2
Відкрита

1. Конфлікт — це не завжди конфлікт рядків

Коли розробник чує слово «конфлікт», у голові майже автоматично спливає екран Git із червоними маркерами та ручним merge. Образ правильний, але вузький. Git — чесний бухгалтер: рахує рядки, файли та коміти, але не знає бізнес-сенс, ownership, API-контракти та ваші приховані припущення. Частину небезпечних конфліктів він помічає пізно, частину — зовсім. У Commerce OS це видно на стику checkout і promo: один трек спрощує розрахунок підсумкової суми, інший доопрацьовує знижки. Обидва формально «правильні», а разом дають дичину — купон застосовується двічі, округлення пливе, інтеграційний тест падає після merge.

Ситуація Git помітить проблему Що реально зламано
Два треки редагують один і той самий рядок у CartTotal.java Так Прямий конфлікт рядків
Один трек змінює сигнатуру методу розрахунку, інший використовує старий контракт у сусідньому модулі Не завжди Семантичний конфлікт, який спливе в тестах або в рантаймі
Один трек заліз у файли поза своїм scope і «заодно покращив» сусідній модуль Може й не помітити Конфлікт ownership і порушення домовленостей команди
Один трек зелений локально, але ламає сценарій іншого після об’єднання Ні Конфлікт припущень, а не рядків

Ось чому небезпечно мислити так: «раз Git змержив без запитань, значить усе добре». Насправді це означає лише, що він склав текст. Паралельна розробка ламається не на символах — на сенсах: один трек вважає, що податок після знижки, інший — що до. Git не читає ваші думки. Корисно тримати в голові просте правило: конфлікт рядків — технічний симптом. Семантичний — конфлікт моделі світу: хто володіє областю, який контракт головний, де закінчується чужий scope. Такі трапляються найчастіше.

2. Хто правий — вирішує ownership, а не красномовство

Коли два треки розходяться за змістом, перша реакція новачка часто виглядає так: «давайте запитаємо Claude, який варіант кращий». Звучить зручно, але це пастка. Оберете за красою відповіді — проголосуєте не за правильне рішення, а за переконливий текст. Питайте не про красу, а за інженерними критеріями:

Критерій Що запитуємо Чому це важливо
Ownership Чий трек відповідає за спірну ділянку коду Власник області зазвичай краще розуміє її обмеження
Scope discipline Чи вийшов трек за погоджені межі Результат поза scope менш надійний, навіть якщо виглядає корисним
Evidence Чи є тести, логи, невеликий і зрозумілий diff Впевненість без evidence нічого не варта
Ціна помилки Скільки коштуватиме неправильне merge Іноді краще обрати більш нудний, але безпечний варіант

Шаблон таких правил зручно тримати у Workflow Kit, а запис по конкретному треку — у parallel-session-log.md поруч із проєктом. Тоді вибір довіреного результату перестає бути суперечкою «мені здається», а спирається на вже наявний контракт.

# parallel-session-log.md

Track: feature/checkout-refactor
Owner: session-checkout
Scope: src/checkout/*, tests/checkout/*
Out of scope: src/promo/*, src/payments/*
Stop condition: checkout tests green
Decision rule: human review before merge

Якщо другий трек раптом поліз у src/checkout/CartTotal.java, хоча scope був лише src/promo/*, це вже серйозний сигнал. Навіть розумний код втрачає довіру — межі порушено. Як бригада, яку найняли фарбувати стіни, а вона заодно пересунула електрику: люди талановиті, але запитання виникнуть. І тут важливо ще одне уточнення: довірений результат не завжди беруть цілком — власник області може принести надто широкий diff або слабкі перевірки, і тоді довірена не вся гілка, а лише її частина.

3. Розбір конфлікту: спочатку diff, потім сенс

Коли два паралельні треки стикаються, хочеться швидше відкрити merge tool і «якось домовитися». Але правильний розбір починається раніше: diff кожного треку відносно main, потім порівняння треків між собою, далі ownership, і лише потім тести та рішення. Почнете навпаки — легко переплутаєте симптом із причиною. Схему нижче варто тримати як робочий шаблон, вона навмисно проста, щоб нею можна було користуватися прямо під час роботи, а не милуватися в методичці:

flowchart TD
    A[Виявили розбіжність між треками] --> B[Порівняли diff кожного треку з main]
    B --> C[Перевірили ownership і межі scope]
    C --> D[Подивилися tests, logs та артефакти]
    D --> E{Що безпечніше прийняти?}
    E -->|Один трек придатний цілком| F[accept]
    E -->|Корисна лише частина| G[cherry-pick]
    E -->|Зміни змішані| H[split]
    E -->|Трек шкідливий або поза scope| I[reject або revert]
    F --> J[Записали рішення у session log]
    G --> J
    H --> J
    I --> J

У Commerce OS розбір checkout проти promo виглядає приземлено:

git diff main...feature/checkout-refactor -- src/checkout/CartTotal.java
git diff main...feature/promo-discount -- src/promo/PromoEngine.java
git diff feature/checkout-refactor...feature/promo-discount -- src/checkout src/promo
(cd ../commerce-os.checkout-flow && ./gradlew test --tests "*Checkout*")
# ЗБІРКА УСПІШНА

(cd ../commerce-os.promo-flow && ./gradlew test --tests "*Promo*")
# 1 test failed: PromoDiscountContractTest

Ці команди корисні не тому, що в них є магія, а тому, що показують конфлікт із трьох боків: три diff дивляться на нього з трьох сторін, а targeted tests б’ють по спірній області, не тонучи в повному прогоні. Але навіть зелені тести ще не означають, що конфліктів немає: обидва набори зелені, бо кожен трек перевіряв лише своє. Тоді треба дивитися на контракт. Чи не зрушила сигнатура методу? Порядок знижки та податку? Чи не виніс один трек логіку в helper, поки інший викликає стару гілку? Семантичний конфлікт живе якраз у зазорі між «моє працює» і «наше працює разом».

Тут дуже часта помилка звучить так: «Claude, злий конфлікт». Якщо ви не вказали, який трек володіє областю, який результат довірений і що зберегти, ви, по суті, просите ШІ прийняти архітектурне рішення за вас. Іноді вгадає, іноді ні. І найнеприємніше — звучатиме впевнено в обох випадках.

4. Стратегія злиття готується заздалегідь

Коли конфлікт уже стався, вигадувати правила з нуля пізно. Хороша стратегія злиття — заздалегідь зрозумілий набір рішень: ви не сперечаєтеся про принципи, а застосовуєте погоджену модель. Особливо там, де тисне спокуса «злити все, шкода ж роботи». На практиці рішень небагато, але кожне доречне по-своєму, і плутати їх не варто.

Рішення Коли обирати Що відбувається
Прийняти цілком Трек у своєму scope, diff зрозумілий, evidence хороші Гілка вливається як є
Відхилити Трек поза scope, ламає контракт або дає зайвий ризик Зміни не приймаються
Вибірково перенести коміт (cherry-pick) Корисна лише частина гілки Береться окремий безпечний коміт
Розділити Гілка змішала кілька різних типів змін Зміни розносяться по окремих PR
Відкотити (revert) Проблемний merge вже потрапив до спільної гілки Створюється явний відкат

Особливо корисно розуміти логіку cherry-pick. Новачки то бояться його без причини, то натискають як на чарівну кнопку. Насправді це вибіркове перенесення ізольованого коміту з атомарною безпечною користю. Promo-трек приніс невдалий рефакторинг, але хороші тести? «Заберемо тести, рефакторинг — в окрему задачу» — здорова логіка.

git checkout feature/integration-branch
git log --oneline feature/promo-discount
# 9ac12f4 promo tests for discount rounding

git cherry-pick 9ac12f4
# перенесли лише коміт із тестами

Reject теж не означає «усе марно». Відхилений трек часто приносить цінність: знайшов прихований ризик, підсвітив слабке місце архітектури, написав фрагмент документації, виявив відсутній тест. Просто їй не обов’язково приїжджати в main цілою гілкою — цінність вимірюється не кількістю merged-коду. А от стратегія «злити все, потім полагодимо» майже завжди виглядає привабливо лише перші п’ять хвилин. Далі — змішаний diff, спірні контракти, невиразний follow-up і позаплановий PR на ремонт. Економія та сама, що від «не мити посуд до приїзду гостей».

5. Рішення щодо конфлікту — в артефакт

Навіть правильне рішення втрачає цінність, якщо лишилося в голові або в одному довгому чаті з Claude Code. Паралельна робота дозріває не тоді, коли команда рідко конфліктує, а тоді, коли кожне рішення можна потім прочитати, зрозуміти та пояснити. Для цього потрібен parallel-session-log.md — той самий лог треку, лише з секцією Conflicts. Фіксуйте не лише підсумок, а й причину: який конфлікт, який трек визнали довіреним, що зробили з іншим, що винесли у follow-up. Це гасить повторні суперечки через тиждень, коли пам’ятають уже не факти, а емоції.

## Конфлікт 2026-05-19 checkout-vs-promo
Type: semantic conflict
Trusted output: feature/checkout-refactor
Decision: cherry-pick tests from promo track
Reason: checkout owner, smaller diff, checkout tests green
Follow-up: PROMO-118 align PromoEngine contract

Такий фрагмент можна тримати прямо всередині parallel-session-log.md по треку — головне, щоб рішення не розчинилося: інший розробник, свіжа сесія Claude або ви самі через три дні побачите не «що зробили», а «чому саме так». Ще один корисний рівень — спільне правило в CLAUDE.md Workflow Kit, щоб повторювані конфлікти не вирішувалися з нуля.

Якщо паралельний трек править файли поза своєю зоною:
1. відхиляйте зміни поза scope;
2. cherry-pick лише ізольовані безпечні коміти;
3. створіть подальшу задачу для решти.

Фрагмент маленький, а робить дві речі: людині — менше імпровізації під стресом, Claude Code — рамку, де «корисно» не означає «можна безконтрольно вливати». Ліки від надто старанного ШІ.

6. Конфлікт — підказка про проблему вище

У конфліктів є корисний бік: вони чесно показують, де зламалася не гілка, а більш ранній шар процесу. Якщо повторюється один і той самий semantic conflict, справа рідко в останньому merge. checkout і promo стикаються щоразу — ймовірно, нечітка межа між розрахунком суми та знижками. Треки постійно лізуть у чужі файли «зовсім трішки» — scope і ownership у task brief розмиті. Конфлікти вилізають лише після merge, локально все зелене — бракує інтеграційних або контрактних перевірок на стику зон відповідальності.

Тому після кожного серйозного конфлікту корисно ставити собі не лише запитання «як це змержити», а й «чому він узагалі був можливий». Відповідь — змінити decomposition, оновити CLAUDE.md або уточнити шаблон треку в parallel-session-log.md. І це нормальне зростання процесу. Паралельна робота не зобов’язана бути безконфліктною. Вона зобов’язана бути зрозумілою, ремонтопридатною та достатньо дисциплінованою, щоб будь-який конфлікт перетворювався не на драму, а на акуратне інженерне рішення.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ