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*")
# BUILD SUCCESSFUL

(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. reject out-of-scope changes;
2. cherry-pick isolated safe commits only;
3. create a follow-up issue for the rest.

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

6. Конфликт — подсказка о проблеме выше

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

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

1
Задача
Claude code, 16 уровень, 2 лекция
Недоступна
Сбор diff signals по двум конфликтующим tracks
Сбор diff signals по двум конфликтующим tracks
1
Задача
Claude code, 16 уровень, 2 лекция
Недоступна
Перенос trusted output и safe merge в target branch
Перенос trusted output и safe merge в target branch
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ