28 июл. 2025 г.·6 мин чтения

Spec‑first подход к программированию с ассистентами — меньшие и чище диффы

Подход spec-first к программированию с ассистентами начинается с примеров и сценариев сбоев, уменьшает шум в диффах и делает код‑ревью короче и менее персональными.

Spec‑first подход к программированию с ассистентами — меньшие и чище диффы

Почему код, сгенерированный ассистентом, часто вызывает споры при ревью

Большинство конфликтов на ревью начинаются ещё до того, как кто‑то написал код. Расплывчатый запрос даёт ассистенту слишком много свободы, и он делает больше, чем вы просили.

Если кто‑то пишет «очистите процесс регистрации и добавьте валидацию», ассистенту приходится догадываться, что значит «очистите». Он может переименовать файлы, переместить логику, переписать тесты, изменить текст ошибок и заменить шаблоны, которые изначально не были в задаче.

Автор видит работающую фичу. Ревьюер видит дифф, затрагивающий восемь мест для изменения, которое должно было затронуть два. Этот разрыв быстро создаёт напряжение.

Ассистенты хорошо заполняют пропуски. В командной разработке это часто приводит к проблемам. Когда запрос не ставит границ, инструмент придумывает их. Когда запрос пропускает командные правила, инструмент опирается на свои дефолтные выборы.

Тогда ревью перестаёт быть про поведение. Люди спорят о нейминге, структуре папок, вспомогательных функциях и о том, не переусердствовал ли ассистент с исправлением. Эти споры затягиваются, потому что никто не оценивает одно и то же целевое изменение.

Простой пример делает это очевидным. Задача — отклонять неверные email в форме регистрации. Один ревьюер ожидает, что форма сначала обрежет пробелы. Другой ожидает, что текст ошибки останется прежним. Третий волнуется о том, чтобы контракт API не изменился. Если ничего из этого нет в запросе, ассистент выберет одну версию и продолжит.

Отсутствие случаев отказа усугубляет дифф после мержа. Путь «всё ок» выглядит нормально, поэтому изменение принимают или почти принимают. Потом кто‑то спрашивает, что произойдёт при дубликате аккаунта, пустом поле, заполненном пробелами, или при таймауте сервера. Появляются дополнительные коммиты, переработки в ревью и запутанная история ради маленькой фичи.

Поэтому подход «spec‑first» обычно ведёт к более спокойным ревью. Когда поведение ясно до начала кода, ассистент делает меньше догадок, дифф остаётся меньше, и ревьюеры оценивают изменение по тому, что увидят пользователи.

Как выглядит старт со спецификацией

Хорошее начало не начинается с кода. Оно начинается с того пользовательского действия, которое вы хотите поддержать. Напишите это действие одним простым предложением, например: «Когда клиент меняет адрес доставки во время оформления заказа, заказ должен использовать новый адрес перед оплатой.» Это даёт ассистенту цель, которую люди могут оценить, не читая ни строчки кода.

Потом опишите результат простыми словами. Сначала пропустите внутренние детали. Скажите, что видит пользователь, что сохраняется и что должно произойти, если действие провалится. Если ревьюер может прочитать заметку за 30 секунд и представить поведение — этого достаточно.

Короткая заметка часто лучше длинного брифа. Покройте нормальный путь, пару способов, как фича может сломаться, и любое правило, которое ассистент не должен трогать. Последняя часть важнее, чем кажется. Многие споры из‑за генерации начинаются, когда сгенерированный код меняет логирование, валидацию, нейминг или несвязанные экраны, решая основную задачу.

Полезная спецификация может быть очень маленькой. Она должна назвать действие пользователя, ожидаемый результат простыми словами, один нормальный пример, два‑три случая отказа и короткую строку «не менять».

Представьте форму возврата денег. Нормальный пример может описывать, что агент поддержки вводит сумму меньше исходного списания, нажимает отправить и видит, что возврат зафиксирован. Случай отказа может сказать, что форма должна отклонять отрицательные суммы. Другой — что повторный клик не должен создавать два возврата, пока первый запрос ещё выполняется.

Также укажите, что остаётся неизменным. Возможно, текущая форма ответа API должна сохраниться. Возможно, формат аудита нельзя менять, потому что его читает другой инструмент. Одна строка такого рода может сократить дифф вдвое.

Держите заметку короткой. Если она превратится в страницу фоновой информации, ассистент захватит слишком много деталей и начнёт догадываться о остальном. Компактная спецификация легче просмотреть, легче оспорить и гораздо проще сопоставить с финальным диффом.

Пишите примеры до того, как просить код

Когда вы просите код слишком рано, ассистент заполняет пробелы догадками. Так небольшое изменение превращается в широкий дифф. Несколько конкретных примеров обычно быстро это останавливают.

Используйте реальные входные данные и реальные ожидаемые выходы. «Валидный email» или «неверное значение» — слишком расплывчато. Напишите точный текст, который вводит пользователь, точный результат, который вы хотите, и точную ошибку, которую вы допускаете. На практике примеры поведения для генерации кода работают эффективнее длинных инструкций.

Начните с одного нормального сценария от начала до конца. Назовите точное место для изменения: файл, функцию или экран. Это важно. Если вы не укажете цель, ассистент может отредактировать вспомогательные функции, общие типы и UI‑тексты, которые вы не хотели трогать.

File: src/auth/signup.ts
Function: createAccount(form)

Normal case:
- Input: name = "Mia Chen", email = "[email protected]", password = "correct horse battery staple"
- Expected result: account is created, welcome screen opens, confirmation email is queued

Odd but valid case:
- Input: name = "Jean-Luc Picard", email = "[email protected]"
- Expected result: account is created, hyphen stays in the name, plus-address is accepted

Pass when:
- Two tests cover both cases
- Existing signup and login tests still pass
- Changes stay inside signup files and related tests

Этот «странный, но валидный» случай важен. Он ловит ошибки, которые ассистенты часто делают: слишком жёсткую валидацию, нежелательное обрезание или логику очистки, которая меняет данные пользователя.

Будьте прямолинейны в том, что значит «пройти». Скажите, нужны ли unit‑тесты, один UI‑тест или оба. Скажите, что тоже должно остаться неизменным. Например, вы можете потребовать, чтобы старый поток логина работал как прежде и текст ошибки совпадал с текущей копией.

Это займёт несколько лишних минут, но сэкономит время на ревью позже. Ревьюеры смогут сравнивать поведение с примерами вместо того, чтобы спорить о стиле, догадках или коде, который ассистент изменил без явной причины.

Добавляйте случаи отказа заранее

Ассистенты обычно угадывают счастливый путь. Отсюда и начинаются грязные диффы. Если вы хотите более спокойные ревью, скажите модели, что код должен отклонять до того, как она напишет хоть одну ветку.

Сценарии отказа делают больше, чем предотвращают баги. Они сужают форму кода. Вы получаете меньше неожиданных проверок, меньше скрытых предположений и меньше споров о том, что должно происходить в краевых случаях.

Для каждого плохого входа опишите точный результат. Не ограничивайтесь «обрабатывать ошибки аккуратно». Скажите, какая ошибка появляется, какой код статуса возвращается, выполняет ли приложение повторную попытку и сохраняет ли система что‑либо вообще.

Короткого списка бывает достаточно:

  • Пустые данные должны падать с понятным сообщением, а не с общей серверной ошибкой.
  • Отсутствие обязательных полей должно останавливать поток до любой записи.
  • Дубликаты записей должны подчиняться явному правилу, например «использовать существующую запись» или «отклонять с ошибкой дубликата».
  • Неправильные форматы должны указывать, что именно не так, а не просто «неверный ввод».
  • Одна известная прошлоя ошибка должна появиться как тест‑кейс, описанный точно так, как она случилась.

Последний пункт важнее, чем ожидают. Если баг уже однажды обжёг команду, положите его в запрос. Например: старая версия создавала два счета, когда пользователь дважды нажал «submit», и новый код никогда не должен удваивать списание, отправку или создавать частичные данные.

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

Мелкие детали экономят время на ревью. Если email отсутствует, возвращайте 400 с сообщением «email is required.» Если приходит дублирующий внешний ID, сохраняйте первую запись и логируйте вторую попытку. Когда вы явно пропишете эти края, у ассистента останется меньше пространства для импровизаций, и первый черновик гораздо чаще будет соответствовать правилам команды.

Используйте простой рабочий процесс

Упорядочить процесс ревью
Bring in a Fractional CTO to reduce back and forth on AI written changes.

Начните с короткой заметки, которая говорит, что фича должна делать для пользователя. Держите её простой. Если человек вне инженерии не может её понять, заметка всё ещё слишком расплывчата.

Простая рутина работает лучше, чем огромный промпт с фоном:

  1. Напишите 4–6 строк ожидаемого поведения простым языком. Сфокусируйтесь на том, что пользователь делает и что система возвращает.
  2. Добавьте 3–5 примеров. Используйте реальные входные данные и ожидаемые выходы, не абстрактные правила.
  3. Добавьте случаи отказа до запроса коду. Включите плохие входы, отсутствующие данные, ошибки разрешений или краевые случаи, которые обычно порождают споры на ревью.
  4. Попросите минимальное изменение кода, удовлетворяющее только этим случаям. Скажите, что хотите минимальный дифф и никакой нерелевантной очистки.
  5. Сверьтесь с диффом и заметкой. Если код соответствует спецификации, не отклоняйте его только потому, что написали бы иначе.

Это быстро меняет тон ревью. Люди перестают спорить о стиле и начинают проверять, правильно ли работает поведение.

Небольшой пример показывает шаблон. Допустим, нужно запретить disposable‑domаины в signup. Ваша заметка может сказать: принимать корпоративные email, отклонять известные disposable‑домены, показывать понятную ошибку и сохранять введённое имя, чтобы пользователю не приходилось вводить его заново. Добавьте примеры вроде «[email protected]» проходит и «[email protected]» не проходит. Добавьте один случай отказа для таймаута неизвестного сервиса доменов. Теперь у ассистента есть ограждение для задачи.

После попадания кода запустите тесты и сравните результат с примерами. Если тест падает потому, что спецификация была расплывчатой, сначала исправьте спецификацию. Потом попросите следующее изменение. Команды, которые делают это хорошо, держат изменения маленькими, ревью спокойными, а доработки — редкими.

Простой пример на форме регистрации

Форма регистрации — хорошее место, чтобы протестировать этот метод: ожидаемое поведение легко описать. Маленькие правила важны, и мелкие ошибки быстро раздражают пользователей.

Предположим, пользователь вводит [email protected] с пробелами в начале и конце. Форма должна обрезать эти пробелы до валидации или сохранения. Если вы пропустите этот нюанс в запросе, ассистент может оставить баг или добавить рефактор значительно больший, чем вы просили.

Добавьте ещё одно правило. Если email уже существует, форма должна показать одно понятное сообщение. Одно сообщение в одном месте — этого достаточно. Не хочется, чтобы всплывашка, inline‑предупреждение и отключённая кнопка срабатывали одновременно.

Поле имени требует той же ясности. Если имя пустое, форма блокирует отправку и объясняет пользователю причину. Это звучит очевидно, но расплывчатые запросы часто приводят к смешанному поведению, когда кнопка работает в одном состоянии и молча терпит ошибку в другом.

Спецификация также нуждается в границе: менять только валидацию, а не макет страницы. Одна строка такого рода экономит много времени на ревью. Без неё ассистент может переставлять поля, переименовывать метки или чистить структуру компонента, когда этого не требовалось.

Чистый промпт для этой формы короткий: обрезать пробелы в поле email, дубликаты email показывают одно сообщение, пустые имена блокируют отправку, макет не менять. Этого достаточно, чтобы направить код, не приглашая лишних правок.

Когда ревьюер открывает дифф, он может сверить каждый пример со всеми изменениями. Обрезал ли код email до валидации? Показал ли код одно понятное сообщение при дубликате? Блокируется ли отправка при пустом имени? Оставил ли ассистент макет без изменений?

Такой стиль ревью меняет тон всей дискуссии. Люди перестают спорить о вкусе и начинают проверять поведение. Комментарии становятся точнее, потому что цель ясна.

Ошибки, которые раздувают диффы

Меньшие диффы — начало здесь
Get practical CTO help for tighter prompts, examples, and failure cases.

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

Эта догадочность быстро проявляется в ревью. Небольшой фикс превращается в переименованные хелперы, перемещённые файлы, изменённые правила валидации и споры о стиле, не имеющие отношения к исходной проблеме.

Пара привычек повторяется чаще всего.

Просить переписать вместо узкого изменения — первая. Если вы говорите «улучшите этот поток» или «очистите логику формы», ассистент тронет больше кода, чем вы хотели. Просите минимальный патч, меняющий одно поведение.

Ещё одна ошибка — смешивать свежие идеи с текущим фикс‑запросом. Команды часто подсовывают рефактор, обновление копирайта и новый краевой случай, делая один баг правкой и новую функциональность одновременно. Это усложняет понимание, что сломалось и почему.

Пропуск случаев отказа — ещё одна распространённая проблема. Они очевидны вам, но не ассистенту. Если вы не скажете, что должно происходить при плохом вводе, таймаутах, дубликатах или пустых состояниях, инструмент придумает поведение.

Неозвученное старое поведение даёт тот же эффект. Если вы не укажете, что должно остаться прежним, ассистент может упростить части, на которые полагаются пользователи. Ревьюеры тогда спорят, баг это или чистка.

И, конечно, нейминг. Нейминг важен, но он не должен возглавлять изменение. Если логика всё ещё не проходит базовые примеры, длинные дискуссии о названиях функций только откладывают реальный фикс.

Небольшой пример на signup показывает разницу. Если вы просите ассистента «улучшить валидацию регистрации», вы можете получить новый текст ошибок, рефактор валидаторов, изменённый поток отправки и дополнительные аналитические события. Если вы просите «блокировать disposable‑домены, оставить все остальные правила и сообщения без изменений и вернуть прежний формат состояния ошибки», дифф остаётся гораздо меньше.

Именно здесь подход spec‑first с ассистентами помогает. Короткая заметка о поведении, пара примеров и небольшой набор случаев отказа сокращают большинство споров ещё до их появления.

Небольшие диффы — это не про боязливость. Они проясняют намерение. Когда ревьюеры видят одно изменение поведения и всё остальное на месте, они принимают решение быстрее и спорят меньше.

Быстрая проверка перед мерджем

Поддержка команд стартапов
Oleg helps founders and engineers keep AI coding practical day to day.

Перед мерджем спецификация должна служить чек‑листом. Если один пример поведения не имеет соответствующего теста, правила валидации или изменения кода, работа ещё не завершена.

Сверяйте дифф и спецификацию бок‑о‑бок.

Каждый пример в спецификации должен соответствовать чему‑то конкретному: тесту, условию в коде или небольшому UI‑изменению. Если в спецификации написано «обрезать пробелы в поле email», ревьюер должен найти это поведение без догадок.

Дифф должен касаться только нужных файлов. Если ассистент тронул хелперы, переименовал переменные по всему приложению или отформатировал несвязанные файлы, попросите более узкий проход. Лишние изменения замедляют простые ревью.

Пути ошибок должны быть легко проверяемы. Проверьте неверный ввод, дубликаты, таймауты и проблемы с правами. Для каждого ожидается определённый результат: понятное сообщение, код статуса или отсутствие записи в базе.

Спецификация должна также назвать, что остаётся прежним. Если изменение только для валидации регистрации, скажите, что логин, сброс пароля и шаблоны писем не менять. Эта одна строка предотвращает непреднамеренное дрейфование.

Ревьюер должен иметь возможность быстро сказать да или нет. Если требуется долгий звонок, чтобы понять намерение, спецификация слишком расплывчата или дифф слишком широк.

Небольшой пример на signup иллюстрирует точку. Если спецификация лишь добавляет «отклонять disposable‑домены», мердж, вероятно, будет содержать одно правило валидации, один‑два теста и, возможно, короткое обновление текста ошибки. Он не должен переписывать всю форму или переносить код в новый паттерн просто потому, что ассистент захотел это сделать.

Последняя проверка простая: доказывает ли патч запрошенное поведение и только это поведение? Если ответ ясен — мерж готов.

Что делать дальше в команде

Выберите один небольшой тикет на этой неделе и проведите его как эксперимент. Не начинайте с крупного рефактора или рискованной фичи. Простая правка формы, правило API или одна валидация достаточно, чтобы понять, даёт ли подход меньшие диффы и спокойные ревью.

Целенаправленно держите эксперимент скучным. Один разработчик пишет ожидаемое поведение первым, включая пару примеров и способы отказа. Потом ассистент получает эту спецификацию вместо расплывчатого запроса «постройте логику регистрации».

Единый шаблон помогает больше, чем длинные советы. Сохраните один короткий документ, который каждый сможет копировать. Включите действие пользователя или ввод, 2–4 примера поведения, 2–4 случая отказа, что вне области, и точную формулировку запроса ассистенту.

Держите этот шаблон там, где команда уже работает. Если его трудно найти, люди перестанут им пользоваться через неделю.

Измеряйте изменения простыми числами. Сравните время ревью до и после. Посчитайте, сколько комментариев в ревью требуют изменений поведения вместо небольших исправлений кода. Если это число падает — спецификация работает.

Короткий ретро после тикета достаточно. Спросите, что стало понятнее, где ассистент всё ещё догадывался и какой случай отказа вы пропустили. Потом обновите шаблон раз. Не переписывайте процесс каждый день.

Если эксперимент срабатывает, расширяйте медленно. Попробуйте на пяти похожих тикетах, прежде чем вводить это как правило команды. Так вы соберёте достаточно примеров, чтобы выработать привычки, не превратив процесс в рутину.

Иногда команде нужна помощь, потому что проблема не в промпте, а в инженерном процессе вокруг промпта. Если ревью продолжают превращаться в споры о сфере, архитектуре или пропущенных краевых случаях, опытный Fractional CTO может уплотнить рабочий процесс и сделать его практичным.

Это также то, о чём говорит Oleg Sotnikov на oleg.is. Его консультации со стартапами и малыми командами фокусируются на том, чтобы делать разработку с ИИ пригодной для повседневной инженерии: ясные спецификации, меньшие диффы и меньше переписок в ревью.