learn-se · Урок 5 · ~18 минут · синтез практикума

Принять результат: читать дифф и верить только запуску

Уроки 1–4 были про то, как отдать задачу агенту. Этот — про вторую половину работы инженера, который не пишет код: как принять результат. Ты не читаешь код построчно — и не надо. Надо уметь три вещи, и все три ты уже сделал руками на чек-инах.

Три навыка приёмки

1. Читать дифф как карту — «что и где меняется», а не построчно.
2. Верить только запуску — и отличать честное доказательство от красивого скриншота.
3. Видеть, как срезы компаундятся — раннее решение удешевляет позднее.

1 · Дифф — это карта, а не текст

Когда агент вернул изменение, ты не читаешь его как книгу. Ты смотришь на форму: сколько файлов, каких слоёв, где узко, где широко. Карта Среза 1 чек-инов:

ФайлЧто и гдеСигнал
migration check_ins.sqlновая таблица + правило доступаслой данных
hooks/checkIns.tsвся логика чтения/записислой логики
CheckInCard.tsxновый блок UIслой представления
HomeScreen.tsx+4 строкиточка подключения

Что карта сказала инженеру за 10 секунд, без чтения кода:

3 файла = 3 слоя. Форма вертикального среза видна прямо в структуре диффа. Данные + логика + UI — значит срез сквозной, а не «полработы».
Узкая граница = сигнал качества. Единственный тронутый чужой файл — +4 строки. Аккуратная фича почти не лезет в чужое.
+200 строк или 15 чужих файлов — красный флаг. Не «плохо» автоматически, а повод спросить: «почему так широко?» Широкий дифф = размытые границы.

i18n-строки, форматирование, импорты — это шум. Скользи мимо. Твой глаз ищет три вещи: где данные, где логика, где граница доступа. Остальное — фон.

Как узнать форму внутри файла данных

Даже не читая построчно, в файле логики всегда одна и та же форма: один хук читает, другой пишет. Нашёл эти два — понял файл на 80%. В checkIns.ts писатель — это optimistic-мутация из четырёх частей:

mutationFn  — настоящая запись на сервер (медленно)
onMutate    — показать СРАЗУ + сохранить previous (для отката)
onError     — откат к previous, если сервер отклонил
onSuccess   — сверить с правдой сервера (перечитать)

Теперь ты ловишь баг, не написав ни строки: нет previous → нечем откатывать; нет onError → «покажет, но не откатит»; нет onSuccess → останется фейковый id.

2 · Верь только запуску — и не любому «зелёному»

Это самый дорогой урок практикума, потому что он поймал реальный баг, который прошёл всё остальное.

Что запуск поймал, а типы — нет Срез 1 прошёл tsc. И всё равно молча ломался: браузерный Supabase-клиент не залогинен → правило доступа (RLS) отклоняло запись. Запись «получалась» на экране (optimistic) и исчезала при перечитывании. Типы не знают про auth, RLS и паттерн проекта. Зелёный tsc ≠ работает.

Но «запустил и посмотрел» — тоже ловушка, если смотреть на неправильное. Качество доказательства важнее самого факта запуска:

Скриншот, который лжёт Первый «успешный» снимок показывал кнопку «Отправка…». Это optimistic-состояние в полёте, а не подтверждённая запись. Optimistic-паттерн специально делает «выглядит готовым» и «сохранено» визуально неотличимыми — поэтому снимок формы ничего не доказывает.
Честное доказательство Не снимок формы, а отдельный read-back: POST → независимый GET клиента → GET тренера. Три чтения сошлись по одному id — вот теперь записано.
Негативный контроль Чужая связь тренер↔клиент → 404. Позитив показывает «работает для своих», негатив — «не работает для чужих». Приватность фото доказана тремя контролями: signed URL → 200, публичный URL → 400, чужой путь → 400. Требуй оба знака.
Сверка со вторым источником Дважды «выглядело как баг» оказывалось артефактом снимка (Срез 4: пикер показал дефолт, потому что снимок сделан до загрузки async-запроса). Правило: не доверяй одному снимку — сверь со вторым источником (API-значение против UI-значения). И «выглядит как баг» разводится сверкой, а не переписыванием кода.

Свернём в один чек-лист приёмки. Когда агент говорит «готово», ты спрашиваешь:

ВопросПлохой ответХороший ответ
Как проверено?«tsc прошёл»«запустил сценарий вживую»
Что на скриншоте?форма после кликанезависимое перечитывание
А негатив?«для чужого → 404/400»
Точно не артефакт?один снимоквторой источник сошёлся

3 · Срезы компаундятся — раннее решение платит вниз по течению

Последний навык — стратегический. Ты режешь фичу на срезы (урок про vertical slice) не только чтобы видеть прогресс. Правильное свойство, вложенное рано, делает поздний срез тривиальным.

Офлайн (3c) ← idempotent (3a) Офлайн-очередь повторяет отправку, когда сеть вернулась. Обычно повтор рискует дублем — нужен дедуп. Но чек-ины уже были idempotent (unique-индекс + upsert из Среза 3a), поэтому повтор безопасен by construction. Дедуп в очереди не понадобился — ни строки лишнего кода.

Это eventual consistency в чистом виде: офлайн-запись → телефон и сервер разошлись → сеть вернулась → сошлись. И она была почти бесплатной, потому что инвариант «один чек-ин в неделю» уже держался в БД.

Правило: хорошие инварианты платят вниз по течению. Решение в раннем срезе (idempotent) сделало поздний срез (офлайн) почти даровым. Вкладывайся в правильные свойства рано — они окупаются в срезах, которых ты ещё не видишь.

Обратная сторона медали — из урока 4: «инфра уже есть» ≠ бесплатно. Пуши переиспользовали боевой sendPushToUser, но фича всё равно притащила свои edge cases (когда стрелять, кому не слать, не задваивать). Компаундинг удешевляет — но не обнуляет.

Проверь себя

1. Агент прислал дифф: 1 новая миграция, 1 хук, 1 компонент, +5 строк в роутере. О чём говорит эта форма?

Данные + логика + UI = срез идёт сквозь все слои. +5 строк в чужом файле = аккуратная граница. Форму диффа читаешь до кода, и она уже многое говорит.

2. «Всё работает — tsc зелёный и на скриншоте форма отправилась». Где дыра в доказательстве?

Ровно баг Среза 1. Типы не ловят RLS; optimistic делает «выглядит сохранённым» и «сохранено» неотличимыми. Честное доказательство — независимый read-back.

3. UI показал не то значение, что ты ставил. Первый ход инженера?

Срез 4: пикер показывал дефолт, потому что снимок сделан до загрузки async-данных. «Выглядит как баг» разводится сверкой со вторым источником, а не переписыванием.

4. Почему офлайн-срез (3c) обошёлся почти без кода?

Компаундинг инвариантов: unique+upsert из 3a убрал потребность в дедупе очереди. Правильное свойство, вложенное рано, удешевляет поздний срез.

Упражнение — прими результат сам

В следующий раз, когда я верну тебе готовый срез, прогони приёмку вслух, тремя ходами:

  1. Карта. Скажи мне форму диффа своими словами: сколько слоёв, где узко/широко, что шум. Не читая кода — по списку файлов.
  2. Доказательство. Потребуй у меня read-back и негативный контроль, не снимок формы. Спроси: «а как это ломается для чужого?»
  3. Компаундинг. Спроси: «какое свойство этого среза удешевит следующий?» — и держи его в уме, назначая следующий срез.

Бонус — глаз приёмщика. Ниже отчёт агента о «готовом» срезе. Найди слабое место доказательства до того, как раскроешь.

Отчёт агента (фрагмент)

— Добавил лимит «не больше 4 фото на чек-ин».
— Проверил: загрузил 3 фото, на скриншоте видно три превью, форма приняла. Работает ✅.
— tsc и линтер зелёные.

Показать слабое место
Разбор

Проверен только позитив (3 фото прошли), а спека — про границу «не больше 4». Нет негативного контроля: что при попытке загрузить 5-е? Должно отклониться — а это не проверено. «Загрузил 3, увидел 3» доказывает, что загрузка работает, но ничего не говорит о самом лимите. Правильная приёмка: 4 → ок, 5 → отклонено. Позитив без негатива не покрывает правило.

Что уносишь из практикума

Ты построил живую фичу от пустой таблицы до задеплоенного продукта с пушами, не написав кода — управляя срезами, читая карты диффов и требуя честных доказательств. Три навыка приёмки — зеркало трёх навыков постановки из уроков 1–4. Инженер без клавиатуры живёт на этих двух половинах: точно отдать и строго принять.

Источники: Shore — верификация как исполняемая спека · Fowler — Self-Testing Code · живой кейс: чек-ины setlog