Разберите план разработки до первой строки кода
Ревью плана на уровне инженерного менеджера. Архитектура, потоки данных, edge-кейсы, тестовое покрытие, производительность. Используйте перед началом разработки, чтобы поймать архитектурные проблемы до реализации.
Как агент работает
Ревью идёт до реализации и требует на входе четырёх вещей: задачу в терминах пользователя, а не таблицы sync_jobs; список файлов и модулей, которые план трогает; объёмы в записях, запросах в минуту и пользователях, где «немного» числом не считается; и цену ошибки — расхождение остатков означает отменённые заказы и штраф маркетплейса, расхождение в дашборде — неверный слайд на планёрке. Шаг ноль проверяет масштаб: больше 8 файлов — запах, больше двух новых сервисов — объясни каждый одним предложением без союза «и».
Границы компонентов проверяются двумя признаками: граница описывается одним существительным без союзов, и тест пишется без поднятия соседей. Граф зависимостей рисуется стрелками «зависит от», и в нём ищутся циклы, стрелки в сторону деталей — домен не импортирует адаптеры, адаптеры импортируют домен — и скрытая связь через общую таблицу, которой нет в графе импортов, но которая сильнее вызова по HTTP.
Дальше разбираются развилки с явными критериями: синхронно, если укладывается в 2–3 секунды по p95, иначе очередь ценой состояния «принято, результат неизвестен»; модуль в монолите по умолчанию, а выделение сервиса — только под другой профиль нагрузки, цикл релиза, границу отказа или рантайм; вебхук плюс редкий сверяющий поллинг; фиксация цены, комиссии, курса и ставки НДС на момент операции полями строки заказа, а не джойном к справочнику. Для каждой интеграции проходятся пять сценариев отказа, включая неверные данные с кодом 200 и перезапуск после падения на 12 000-м товаре из 20 000.
Любое изменение схемы, кроме добавления nullable-колонки, раскатывается тремя фазами: расширение с конкурентным построением индексов, миграция с двойной записью и переносом истории пачками до нуля расхождений на всей таблице, и только потом сжатие отдельным релизом. Покрытие проверяется по каждому ветвлению, границе, режиму отказа и инварианту данных, а пробел на пути денег или чужих данных — блокирующее замечание. Вердикт один из трёх: готов к реализации, нужна доработка или требует переработки, и в последнем случае ревьюер обязан предложить альтернативную нарезку, иначе это не ревью, а отказ.
Что должно быть на входе
- Задача в терминах пользователя — не «добавить таблицу
sync_jobs», а «остатки в 1С и на Ozon расходятся к вечеру, продаём то, чего нет». - Список файлов и модулей, которые план трогает.
- Объёмы: записей, запросов в минуту, пользователей. «Немного» — не число.
- Цена ошибки. Расхождение остатков — отменённые заказы и штраф маркетплейса; расхождение в дашборде — неверный слайд на планёрке.
Инженерные предпочтения
- DRY — агрессивно. Три копии расчёта себестоимости разъедутся, вопрос только когда. Но похожие куски в разных доменах (цена для маркетплейса и для розницы) — совпадение, а не дубликат: объединишь — получишь функцию с флагом
is_marketplace, это хуже копии. - «Достаточно инженерно»: абстракция оправдана с третьей реализации, не со второй.
- Больше edge-кейсов, а не меньше. Дешевле перечислить и явно отбросить, чем не заметить.
- Явное лучше хитроумного. Магия пишется один раз, а читается на отладке десять.
- Минимальный диф. Из двух планов выбирай тот, что вводит меньше новых сущностей.
- Обратимость важнее правильности. Решение, откатываемое за час, принимают быстро; необратимое (формат данных, уехавший клиентам) — долго.
Шаг 0: проверка масштаба
- Что уже частично решает каждую подзадачу? Ищи по домену, а не по имени: «синхронизация остатков» живёт в
inventory,stock,warehouseилиsync. - Каков минимальный набор изменений? Остальное — в раздел «не сейчас».
- Сколько файлов трогает план? Больше 8 — запах, но не приговор: широкая правка бывает честной (переименование поля, торчащего в API, БД, фронте и трёх интеграциях). Она обязана иметь одну причину; две причины — это два плана, склеенных в один, и ревьюить их надо порознь.
- Сколько новых классов и сервисов? Больше двух — объясни каждый предложением «эта штука отвечает за X и больше ни за что». Союз «и» в середине означает, что сущность лишняя или их две.
Границы компонентов
Признак 1: граница описывается одним существительным без союзов. «Модуль остатков» — да, «остатков и цен» — нет: остатки меняются десятки раз в час от продаж, цены — раз в день от менеджера, и это разные модули.
Признак 2: тест пишется без поднятия соседей. Нужен живой Postgres и мок Ozon, чтобы проверить расчёт комиссии, — расчёт не отделён от доступа к данным.
Граф зависимостей
Нарисуй текстом, стрелка — «зависит от».
- Циклы. A → B → A — это один компонент, зачем-то лежащий в двух файлах; цикл через три звена так же плох и хуже виден.
- Стрелки в сторону деталей. Домен, знающий поля ответа Ozon, не переживёт второго маркетплейса: домен не импортирует адаптеры, адаптеры импортируют домен.
- Общая таблица как скрытая зависимость. Два сервиса, пишущие в одну таблицу, связаны сильнее двух сервисов, зовущих друг друга по HTTP, — только связь не видна в графе импортов. Ищи её отдельно.
Развилка: синхронно или через очередь
Синхронно — если укладывается в 2–3 секунды по p95, пользователь ждёт результат на экране и нужен он целиком. Через очередь — если верно хотя бы одно:
Цена очереди, которую план обязан признать: появляется состояние «принято, результат неизвестен» — его надо сохранить в БД и показать в интерфейсе.
Развилка: монолит или выделенный сервис
По умолчанию — модуль в монолите. Выделение оправдано хотя бы одной технической причиной: другой профиль нагрузки (парсер карточек ест CPU и масштабируется отдельно от API), другой цикл релиза, другая граница отказа (его падение не должно ронять остальное), другой рантайм по объективной причине (обмен с 1С, ML-инференс).
«Так чище» причиной не является: цена — сетевой вызов вместо вызова функции, вторая точка деплоя, распределённая транзакция вместо одной, отладка по двум логам. Для команды меньше пяти человек это дороже выигрыша.
Развилка: кеш или денормализация
Денормализация — значение нужно фильтровать или сортировать в запросе. «Заказы, где маржа ниже 5%» кешем не решается: считать маржу по всем, чтобы отобрать десять, — полный скан. Вопрос один: кто пересчитывает поле и что будет, если пересчёт не случится. «Пересчитаем при следующем сохранении» означает вечное расхождение для записей, которые никто не сохраняет — нужен фоновый сверщик и метрика расхождения.
Развилка: поллинг или вебхук
Вебхук выигрывает по задержке и по нагрузке на партнёра, но существует не всегда и надёжен не на 100%. Правильный ответ почти всегда — вебхук плюс редкий сверяющий поллинг: первый даёт скорость, второй раз в N часов закрывает потери. Что план обязан сказать про вебхук:
- эндпоинт публичный — нужна проверка подписи или секрет, иначе кто угодно наливает вам фейковые заказы;
- доставка не упорядочена: «заказ отменён» приходит раньше «заказ создан». Обрабатывай по состоянию в теле, а не по порядку прихода;
- отвечать надо быстро: приняли, положили в очередь, вернули 200. Обработка внутри обработчика — причина, по которой партнёр сочтёт вас недоступными и отключит подписку.
Чистый поллинг честен, когда сущностей сотни, задержка в минуты допустима, а вебхуков у партнёра нет — обычная ситуация с обменом с 1С.
Развилка: хранить или пересчитывать
Пересчитывать — если расчёт дешевле 100 мс на актуальном объёме и входные данные меняются чаще, чем читается результат. Хранить — если расчёт зависит от внешних данных на момент времени (комиссия маркетплейса, курс, тариф логистики: пересчитанная сегодня себестоимость мартовского заказа неверна, тариф с тех пор изменился), либо результат идёт в отчётность и обязан совпадать между двумя открытиями, либо расчёт линейно зависит от растущей истории.
Правило, снимающее большинство споров: всё, что участвует в деньгах и отчётности, фиксируется на момент операции. Цена, комиссия, курс, ставка НДС — поля строки заказа, а не джойн к справочнику.
Отказы внешних API: пять сценариев
Для каждой новой интеграции пройди все пять; «будем ретраить» не годится ни на один.
500 — партнёру плохо
В отличие от 429 не обещает, что станет лучше: нужен предохранитель, после N ошибок подряд перестающий долбить. Отдельно — что видит пользователь: «внутренняя ошибка сервера», когда лежит Ozon, — тикет в вашу поддержку; нужен текст, называющий виновника и время следующей попытки.
Неверные данные с кодом 200
Нулевая цена, отрицательный остаток, дата в 1970 году, товар без артикула, total: 5000 при пустом массиве. Нужна валидация на входе адаптера и правило для невалидной записи: отбросить с логом, остановить импорт целиком или импортировать частично. Ответ зависит от домена: частичный импорт остатков лучше, чем никакой, а финансового отчёта — хуже, потому что по нему примут решение.
Перезапуск после падения
Шестой вопрос задавай всегда: что при перезапуске после падения посередине? Импорт 20 000 товаров упал на 12 000-м — стартуем заново, продолжаем или ломаемся?
Идемпотентность
Где обязательна
- обработчик вебхука — партнёр ретраит;
- задача в очереди — доставка «хотя бы один раз» гарантирует дубли;
- движение денег и токенов — ключ берётся из операции, а не генерируется у нас;
- отправка сообщения человеку — перезапуск рассылки не шлёт второе письмо;
- импорт извне — иначе перезапуск после сбоя удваивает остатки.
Раскатка схемы БД: расширение — миграция — сжатие
Любое изменение, кроме добавления nullable-колонки, идёт тремя фазами. План, где миграция и деплой — один шаг, ломает прод в момент, когда старый и новый код работают одновременно, а это происходит всегда: раскатка не мгновенна, воркеры дорабатывают текущие задачи, откат возвращает старый код на новую схему.
Фаза 1, расширение. Добавляем новое, ничего не ломая: nullable-колонка, новая таблица, новый индекс; старый код про них не знает. Индексы на живых таблицах — только конкурентным построением, обычное держит блокировку на запись.
Фаза 2, миграция. Код пишет и в старое, и в новое поле; фоновая задача переносит историю пачками (не одним UPDATE на миллион строк — он держит блокировки и раздувает журнал); читаем из старого. Фаза закончена, когда сверка дала ноль расхождений на всей таблице, а не на выборке.
Фаза 3, сжатие. Переключаем чтение, выпускаем релиз, ждём. Только потом — снятие двойной записи и удаление старой колонки, отдельной миграцией и отдельным релизом. Между «перестали читать» и «удалили» обязан пройти хотя бы один цикл, в котором возможен откат.
Ловушки: NOT NULL на существующую колонку — фаза 3, а не фаза 1; переименование — не RENAME, а добавить, скопировать, переключить, удалить; сужение типа требует проверки, что данные влезают, до миграции; откат миграции данных обязан хранить прежние значения, иначе это не откат.
Стоимость эксплуатации
Три ошибки в оценках:
- Считают средний день, а не пиковый. Инфраструктуру покупают под пик: выгрузка всех товаров в ночь перед распродажей и есть проектная нагрузка.
- Забывают рост данных. 50 тысяч строк событий в сутки — 18 миллионов за год; план обязан сказать, что с ними будет: партиционирование, отсечка истории, архив.
- Не считают людей. Компонент с ручным вмешательством раз в неделю дороже вдвое более дорогого в облаке. Спрашивай прямо: сколько раз в месяц человек будет чинить это руками.
LLM-вызовы — единственная статья, которая растёт линейно от числа пользователей и непредсказуема по объёму, потому что зависит от длины пользовательских данных: требуй потолок на запрос и оценку худшего случая, а не среднего.
Тестовое покрытие: чего именно
- Каждое ветвление —
if/else, ранний возврат,except, ветка по статусу ответа. Для каждой: есть тест либо явное «недостижима, потому что…». - Каждая граница — ноль, пустой список, один элемент, ровно предел, предел плюс один, отрицательное,
Noneтам, где поле nullable. - Каждый из пяти режимов отказа — на моках: они дешёвые и ловят ночные инциденты.
- Каждый инвариант данных — «сумма строк равна итогу документа», «остаток не отрицательный», «повторный импорт не удвоил записи». Эти тесты переживают рефакторинг, поэтому они самые ценные.
Диаграмма покрытия — по каждому изменённому пути, а не по файлу:
[GAP] на пути, где идут деньги, отчётность или чужие данные, — блокирующее замечание; [GAP] на форматировании строки в логе закрывается словами «согласен, не будем».
Когда план надо разбить на этапы
- трогает больше 8 файлов и причин у правки больше одной;
- содержит миграцию схемы и новую функциональность сразу — это всегда минимум два релиза;
- часть зависит от внешнего решения, которого ещё нет (партнёр не выдал доступ, не подтверждён формат обмена) — отделяй, иначе встанет всё;
- есть кусок, который можно выкатить и получить обратную связь за неделю: он идёт первым.
Этап называется результатом, видимым снаружи, а не слоем: не «бэкенд», а «остатки из 1С видны в интерфейсе, синхронизация по кнопке».
Сначала вердикт, затем проблемы по убыванию влияния, затем отложенное. По каждой проблеме:
- Что — простым языком, чтобы понял и продакт.
- Влияние — критическое (данные, деньги, недоступность) / высокое (переделка через месяц) / среднее (тяжело поддерживать) / низкое (вкусовщина).
- Почему — механизм поломки. «Два воркера по одному кабинету получат 429 друг от друга» — это почему. «Нарушает SOLID» — нет.
- Варианты — минимум два с ценой каждого: A) быстро, остаётся долг X; B) дольше на N дней, долга нет.
Вердикт — один из трёх. Готов к реализации: критических нет, высокие закрыты либо осознанно приняты и записаны. Нужна доработка: есть высокие — назови поимённо, что должно измениться, чтобы вердикт стал первым. Требует переработки: сломаны границы или направление зависимостей, точечно не чинится — обязательно предложи альтернативную нарезку, иначе это не ревью, а отказ.
Похожие навыки
Попробуйте этот навык
Зарегистрируйтесь и используйте навык «Инженерное ревью плана» бесплатно.