Disagreement, escalation, RFC процесс, принятие решений
Конфликт в ревью почти никогда не про код. Он про то, что нет договорённости, кто принимает решение, — и оба участника считают, что это они.
Вы научитесь различать три типа разногласий, каждый из которых решается по-своему; определять момент, когда переписку надо прекратить; и вводить в команде правило принятия решений, после которого спор становится процедурой, а не противостоянием.
Половина затяжных споров возникает потому, что участники решают разные типы вопроса, не заметив этого.
Разногласие о факте. «Этот запрос будет медленным» — утверждение, которое можно проверить. Решается измерением, а не аргументацией. Правильный ход: не спорить, а замерить.
Ревьюер: «Кеш здесь избыточен, БД справится»
Автор: «Замерил на копии прода: без кеша 180 мс на p95,
с кешем 12 мс. Требование — до 50 мс.»Спор закончен. Данные снимают вопрос быстрее любых доводов, и первым делом стоит спросить: а это вообще проверяемое утверждение?
Разногласие о вкусе. «Мне не нравится такое именование», «я бы разбил на две функции». Проверить нельзя, потому что нет критерия. Правило: в вопросах вкуса решает автор. Ревьюер, продавливающий свои предпочтения, тратит время команды на переносимость собственных привычек.
Это правило работает только если оно объявлено заранее. Иначе каждый такой спор превращается в состязание упорства, и побеждает тот, кому важнее.
Разногласие о ценностях. «Надо выпустить сегодня» против «надо сделать правильно». Здесь оба правы в своей системе координат, и техническими доводами это не решается. Такое разногласие не разрешается внутри PR — оно требует решения того, кто отвечает за приоритеты, и решение должно быть зафиксировано, а не выведено голосованием в треде.
Проверьте себя. Возьмите последний затянувшийся спор в PR и определите его тип. Совпадает ли способ, которым его пытались решить, с типом?
Частая ошибка. Спорить фактами о вкусе и вкусом о фактах. Первое выглядит как придирка, второе — как упрямство.
Затяжной тред стоит дороже, чем кажется: он тратит время двоих, блокирует работу, читается всей командой и портит отношения тем сильнее, чем длиннее.
Останавливаться пора, когда:
Три способа выйти:
Разделить решения. Часто спорят о двух вещах сразу: «здесь баг» и «архитектура неудачная». Первое решается в этом PR, второе — тикетом. Смешение делает разговор безвыходным, потому что согласие по одному пункту воспринимается как проигрыш по другому.
Перейти в синхронный разговор. Пятнадцать минут голосом закрывают то, на что уходит два дня переписки, — в основном потому, что в разговоре быстро выясняется: один из двоих не знал важной детали. Результат обязательно фиксируется в PR.
Эскалировать. «Мы не сходимся, давай позовём третьего» — нормальная процедура, а не жалоба. Важно позвать до того, как тред станет неприятным: эскалация из спокойного состояния воспринимается как поиск решения, из напряжённого — как попытка привести подкрепление.
Проверьте себя. Найдите в репозитории тред длиннее двадцати комментариев. На какой реплике надо было остановиться?
Частая ошибка. Продолжать, потому что «осталось немного, сейчас я его убежу».
Главная причина затяжных конфликтов — отсутствие ответа на этот вопрос. Пока его нет, каждый спор решается упорством.
Работающее распределение, которое стоит объявить в команде:
| Вопрос | Кто решает |
|---|---|
| Вкус, стиль, именование | Автор |
| Наличие бага, уязвимости, потери данных | Ревьюер: это blocker, обсуждается только факт |
| Локальное архитектурное решение внутри модуля | Автор, с учётом возражений |
| Решение, влияющее на несколько модулей | Владелец области или тимлид |
| Приоритет: выпустить сейчас или доделать | Тот, кто отвечает за приоритеты, не участники спора |
| Общая договорённость команды | Команда, через изменение CONVENTIONS.md |
Ключевая строка — вторая. Наличие бага не является предметом переговоров: спорить можно о том, есть он или нет (факт), но не о том, стоит ли его исправлять. Если это разделение не проведено, ревьюер вынужден каждый раз заново отстаивать право блокировать.
И последняя строка: любой спор, который возник дважды, — сигнал, что нужна общая договорённость. Одноразовое решение в треде PR ничего не меняет: через месяц те же двое поспорят снова.
Проверьте себя. Знает ли ваша команда, кто решает при несогласии автора и ревьюера? Спросите двоих отдельно — ответы могут разойтись.
Частая ошибка. Считать, что решает тот, кто опытнее. Это работает, пока не столкнутся двое равных, — и тогда спор не имеет механизма завершения.
Вмешательство тимлида в спор — сильный инструмент с побочным эффектом: оно завершает конкретный конфликт и приучает команду ждать арбитра вместо того, чтобы договариваться.
Вмешиваться нужно, когда: спор перешёл на личности; тред блокирует работу дольше суток; вопрос выходит за пределы компетенции участников; один из участников явно в невыгодном положении из-за разницы в статусе.
Не вмешиваться, когда обсуждение идёт по существу, даже если оно долгое и вам очевиден ответ. Особенно если очевиден: сказанное тимлидом мнение обычно завершает обсуждение независимо от аргументов.
Как вмешиваться:
❌ «Делаем через сервис, я так решил»
✅ «Вижу, что не сходитесь. Резюмирую: @alice беспокоит
тестируемость, @bob — что абстракция преждевременна.
Оба аргумента верные.
Решение: в этом PR оставляем как есть, потому что срок
ближе, чем цена переделки. Тикет PROJ-1902 на выделение
сервиса, когда появится второй потребитель.
Записал в ADR-015.»Три элемента, без которых вмешательство не работает: обе позиции названы и признаны, решение обосновано критерием, а не властью, решение зафиксировано письменно.
Отдельно: тимлиду полезно высказываться в спорных тредах последним. Мнение, высказанное первым, останавливает обсуждение — остальные соглашаются, и команда лишается собственного вывода.
Проверьте себя. Тимлид: в последнем споре вы высказались первым или последним?
Частая ошибка. Решать спор в личных сообщениях. Остальные видели только конфликт и не увидели, как он был разрешён, — норма не изменилась.
Спор об архитектуре в комментариях к PR обречён: код уже написан, автор вложил в него время, и любое возражение воспринимается как требование выбросить работу.
Отсюда правило: крупные решения обсуждаются до кода. Короткий документ — проблема, предлагаемое решение, отвергнутые альтернативы, компромиссы — стоит день работы и снимает недельный спор на этапе ревью.
Признак, что нужен RFC: изменение затрагивает несколько модулей, вводит новую зависимость или технологию, меняет публичный контракт, или его отмена будет стоить недели.
Ключевая деталь: у RFC должен быть срок сбора возражений и явное завершение. «Комментарии до пятницы, потом принимаю» — иначе документ висит месяц, а работа стоит. И принятое решение переезжает в ADR, где остаётся навсегда.
Проверьте себя. Вспомните архитектурный спор, случившийся в ревью. Помог бы RFC до начала работы?
Частая ошибка. Требовать RFC на всё. Порог должен быть высоким — иначе процесс станет способом замедлить любую инициативу.
Конфликты в ревью распределены неравномерно: большинство приходится на несколько предсказуемых причин.
Отсутствие уровней у замечаний. Автор не знает, что блокирует. Введение blocker / major / minor / nit убирает целый класс споров, потому что nit перестаёт быть предметом торга.
Отсутствие калибровки. Разные ревьюеры требуют разного. Час раз в квартал — и противоречащие требования исчезают.
Договорённости в чате. Правило, которого нет в репозитории, каждый помнит по-своему. CONVENTIONS.md превращает спор о том, «как у нас принято», в ссылку на строку.
Большие PR. Чем больше дифф, тем больше поводов для замечаний и тем дороже любая переделка. Конфликты почти линейно связаны с размером.
Отсутствие явного «кто решает». Разобрано выше — самая частая структурная причина.
Все пять — процессные. Ни один не решается призывом быть вежливее, и это главный вывод темы.
Проверьте себя. Какая из пяти причин чаще всего срабатывает в вашей команде?
Частая ошибка. Считать частые конфликты следствием характеров. Обычно это следствие того, что процесс не отвечает на вопрос, кто прав.
Двое senior-разработчиков, PR на 200 строк, тред на 47 комментариев. Приводим ключевые реплики.
Автор (A): Добавил обработку вебхуков платёжного шлюза.
Ревьюер (B): Почему обработка внутри HTTP-хендлера? Это должно быть
в очереди — вебхуки надо принимать быстро и обрабатывать
асинхронно.
A: Обработка занимает 30 мс, очередь тут избыточна.
Добавит сложности и точку отказа.
B: Дело не в 30 мс, а в принципе. Шлюз ждёт 200 мс и ретраит.
Если наша БД тормозит, мы начнём получать дубли.
A: Идемпотентность есть, дубли обработаются корректно.
B: Всё равно это неправильная архитектура. Мы так делаем
во всех остальных интеграциях.
A: В остальных обработка занимает секунды. Здесь другой случай.
B: Консистентность важнее. Давай как везде.
A: То есть добавим Celery, Redis и мониторинг очереди
ради 30 мс?
[обмен репликами продолжается 6 дней]
B: Я не могу это одобрить.
A: Хорошо, позову @lead.
[тимлид в отпуске, ещё 4 дня]
Тимлид: Делайте через очередь, у нас так везде.Что произошло. Оба аргументировали разумно, никто не грубил, и результат — две недели и решение, принятое по признаку «как везде», а не по существу.
Разберём по типам разногласий. Первая реплика B — утверждение о факте: «если БД тормозит, будут дубли». Проверяемо. Ответ A — тоже о факте: «идемпотентность есть». Эти две реплики могли закрыть вопрос: достаточно было проверить, действительно ли идемпотентность покрывает случай медленного ответа.
Дальше спор сменил тип, и никто этого не заметил. «Дело не в 30 мс, а в принципе» и «консистентность важнее» — это уже разногласие о ценностях: единообразие против простоты. Такое не решается техническими доводами, и следующие сорок комментариев были потрачены впустую.
«Мы так делаем во всех остальных интеграциях» — ссылка на договорённость, которой нет в репозитории. Если бы правило «все внешние вебхуки обрабатываются через очередь» было записано, спор закончился бы на второй реплике: либо A следует правилу, либо предлагает его изменить — и это уже разговор с командой, а не с одним ревьюером.
«Я не могу это одобрить» на шестой день — правильное действие, выполненное на пять дней позже, чем следовало. Эскалировать надо было после третьей реплики.
Отсутствие резервного арбитра: тимлид в отпуске, и процесс остановился на четыре дня. Эскалация должна вести к роли, а не к человеку.
И решение тимлида «делайте как везде» без разбора аргументов закрепило худшее: команда узнала, что спор решается ссылкой на единообразие, а не рассмотрением случая.
Как это должно было идти:
A: Добавил обработку вебхуков. Обработка 30 мс, поэтому синхронно;
идемпотентность по event_id. Знаю, что в остальных интеграциях
очередь — там обработка секундная, здесь случай другой.
B: major: если БД замедлится, шлюз отретраит по таймауту.
Идемпотентность покрывает дубли? Проверь случай, когда
первый запрос ещё в транзакции, а второй уже пришёл.
A: Проверил — не покрывала: уникальный индекс срабатывал,
но ошибка возвращалась шлюзу как 500, и он ретраил снова.
Поправил: на конфликт отвечаем 200.
B: Хорошо. Остаётся вопрос единообразия — у нас правило про
очередь для вебхуков не записано, но фактически так везде.
Это не blocker для твоего PR. Давай вынесу в обсуждение
команды: нужно ли правило и с каким порогом.
Одобряю.Полтора дня. Замечание о факте проверено и обнаружило настоящий баг — которого в реальном споре так и не нашли, потому что обсуждали принципы. Вопрос о единообразии отделён от PR и адресован туда, где решается, — команде.
Чем закончилось в реальности. Очередь добавили. Через месяц на разборе процесса восстановили хронологию и внесли изменения:
CONVENTIONS.md записали правило: вебхуки с обработкой дольше 100 мс — через очередь, короче — синхронно с идемпотентностью. Спор больше не возникал.blocker.Баг с ответом 500 на конфликт нашли позже, в проде, — он давал по три дубликата уведомления на каждый платёж.
CONVENTIONS.md не блокирует. Это заставит записывать правила.ТИП определён: факт, вкус или ценности — и способ решения соответствует
ФАКТЫ проверяются измерением, а не аргументацией
ВКУС решает автор; это объявлено заранее
РЕШЕНИЕ известно, кто принимает, для каждого типа вопроса
ОСТАНОВКА эскалация после 3–4 сообщений без сдвига
РАЗДЕЛЕНИЕ баг решается в PR, архитектура — тикетом
ФИКСАЦИЯ итог созвона и решение арбитра записаны в PR или ADR
АРБИТР эскалация ведёт к роли, есть резерв на время отпуска
ПРАВИЛА «так принято» подкреплено записанной договорённостью
ПОВТОР спор, случившийся дважды, превращается в общую договорённость
ТИМЛИД вмешивается по признакам, высказывается последним, обосновывает критериемУсловия, при которых спор не превращается в конфликт, — Культура review. Личные формулировки в споре — Soft skills. Где фиксировать решения — Remote и асинхронность.
Ключевая мысль: затяжной спор — почти всегда признак того, что в команде не решено, кто решает. Пока этого ответа нет, побеждает не правота, а упорство.