17 июл. 2024 г.·7 мин чтения

Архитектурные границы для промпт-ориентированных команд, которые держатся долго

Узнайте, как архитектурные границы для промпт-ориентированных команд помогают быстрым ИИ-разработкам не превращать короткие фиксы в запутанные системы — с шагами и простыми проверками.

Архитектурные границы для промпт-ориентированных команд, которые держатся долго

Почему быстро написанный через промпты код превращается в путаницу

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

Это кажется эффективным в первый день. Промпт вроде «добавь статус аккаунта на страницу клиента» может за один проход изменить базу данных, ответ API, логику страницы и даже правила биллинга. Демо работает, и никто не останавливается, чтобы спросить, должны ли эти части вообще знать друг о друге.

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

Проблема становится хуже еще и потому, что быстрые исправления приживаются. Один разработчик чинит крайний случай на экране. Другой промпт повторяет правило в endpoint. Через неделю кто-то обновляет отчет и снова вшивает ту же логику. Никто не записывает контракт между этими частями, и тогда обходной путь становится самой системой.

Позже мелкие правки начинают ломать вещи, которые кажутся не связанными. Переименование одного поля ломает отчет, мобильный экран и фоновую задачу. Изменение правила скидки влияет на счета, которые вроде бы никто не трогал. Команды часто называют это случайной нестабильностью. Обычно она появляется из-за тесной связанности, созданной во время быстрой разработки.

Небольшая SaaS-команда может столкнуться с этим удивительно рано. Они просят ИИ добавить обработку сценария «пробный период истек». Модель обновляет таблицу пользователей, поток входа, страницу администратора, текст письма и проверки доступа одновременно. Это быстро уходит в продакшен. Потом поддержка просит добавить двухдневный льготный период, и теперь команде приходится искать половину приложения, чтобы изменить одну идею.

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

Где стоит провести границы в первую очередь

Первая граница должна проходить вокруг той части продукта, которая меняется чаще всего. Команды часто делят код на frontend, backend или utils, потому что это выглядит аккуратно. Такой разрез обычно быстро ломается. Изменения не следуют за папками. Они следуют за действиями пользователей: регистрация, оплата, отмена, приглашение, экспорт.

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

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

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

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

Это особенно важно для небольшой SaaS-команды. Один промпт за полдня выпускает новый шаг онбординга, а другой в тот же день меняет логику ценообразования. Если signup знает внутренности billing, эти изменения сталкиваются. Если signup вызывает только небольшой billing-интерфейс, оба изменения могут двигаться дальше, а правило останется в одном месте.

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

Что нужно зафиксировать заранее

Фиксируйте контракт, а не код.

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

Начните с простого языка. Запишите, что входит, что выходит и что означает каждое поле. «Customer ID — это строка», «trial_end может быть пустым» и «status может быть только active, paused или canceled» достаточно понятно и людям, и моделям. Размытые названия вроде «информация о пользователе» или «данные аккаунта» провоцируют догадки, а код, созданный через промпты, заполняет такие пробелы каждый раз по-разному.

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

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

Кто-то также должен окончательно утверждать изменения контракта. Один человек или одна команда должны проверять изменения названий общих полей, типов и поведения ошибок. Иначе небольшая правка промпта может изменить форму ответа и сломать трех потребителей, прежде чем это кто-то заметит.

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

Как рано зафиксировать интерфейс

Начните с одного важного сценария. Не пытайтесь зафиксировать весь продукт сразу. Возьмите путь, с которым команда работает каждую неделю, например «trial signup создает аккаунт» или «support ticket переходит из open в closed». Одна хорошо сделанная граница лучше пяти недоделанных.

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

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

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

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

Вам также нужен один тест, который будет падать, если контракт изменится. Сделайте его простым. Если ваш API возвращает status, plan и expires_at, тест должен падать, когда кто-то заменит expires_at на expiryDate, потому что промпт решил, что так красивее. Скорость — это нормально. Тихий дрейф — нет.

Небольшая SaaS-команда может сделать это за один день. Она фиксирует payload billing-webhook, закрепляет разрешенные состояния событий и добавляет один contract test в CI. После этого промпты все еще могут генерировать обработчики, логи, повторные попытки и админские экраны. Просто они не могут по прихоти переписать границу.

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

Как сохранять скорость и не ломать контракт

Зафиксируйте один интерфейс первым
Превратите один проблемный стык в понятный контракт, который команда сможет поддерживать.

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

Одна привычка помогает сильнее, чем ожидает большинство команд: держите заметки по интерфейсу рядом с кодом, а не в отдельном документе, который никто не открывает. Короткий файл контракта с именами полей, кодами статусов и одним-двумя примерами payload экономит время, потому что модель, разработчик и ревьюер работают из одного источника.

Примеры запросов и ответов должны попадать и в промпты, и в code review. Модели работают лучше, когда могут копировать понятный шаблон. Ревьюерам тоже проще, потому что они сравнивают результат с конкретным примером, а не пытаются угадать, что имел в виду промпт.

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

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

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

Обычно работает короткий чек-лист:

  • Храните заметки по контракту и примеры в репозитории рядом с кодом, который их использует.
  • Вставляйте примеры запросов и ответов в промпты, тесты и ревью.
  • Переписывайте внутренности свободно, но изменения на границе планируйте отдельно.
  • Используйте адаптеры, когда клиенты и сервисы не могут меняться одновременно.
  • Записывайте каждое изменение контракта в одном общем месте.

Это звучит строго, но на самом деле защищает скорость. Промпт-ориентированное ПО движется быстрее всего тогда, когда команда может свободно менять внутренности и доверять краям, зная, что они останутся стабильными.

Простой пример из жизни небольшой SaaS-команды

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

Перед стартом они приняли одно умное решение. В первый день они зафиксировали стык между signup и billing. Signup мог собирать данные, проверять их и отправлять в billing один чистый payload. Billing принимал только этот payload и возвращал только несколько понятных состояний, например «trial started» или «payment needed».

Контракт остался маленьким: идентификатор пользователя, выбранный тариф, страна для налоговых правил и дата начала пробного периода. Все остальное оставалось за пределами billing. Шаг с email не вызывал код оплаты. Админская панель не лезла внутрь checkout. Если позже команде понадобились бы дополнительные поля профиля, signup мог бы хранить их у себя, не меняя то, как работает billing.

Это решение окупилось через два дня. Сгенерированный ИИ email-шаг выглядел нормально в тестах, но реальные пользователи проигнорировали первое письмо. Команда переписала логику писем, изменила тайминг и добавила напоминание для незавершенной настройки. Они сделали все это, не трогая платежи, потому что email слушал событие signup, а не сидел внутри billing-потока.

Та же граница помогла с тестированием. Команде не пришлось перепроверять каждый экран после каждого изменения. Они проверяли только две вещи: продолжает ли signup отправлять согласованные поля и продолжает ли billing отвечать теми же состояниями. Это сократило поверхность тестирования до уровня, с которым небольшая команда могла справиться в загруженную неделю.

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

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

Ошибки, которые создают постоянную связанность

Сделайте AI-workflows безопаснее
Используйте скорость ИИ-программирования, не превращая приложение в один большой скрипт.

Команды редко планируют тесную связанность. Обычно они создают ее быстрыми исправлениями, а код, созданный через промпты, только облегчает это.

Одна частая ошибка — дать инструментам для промптов право читать и менять любой файл в репозитории. Когда модель может трогать все, она часто пересекает линии, которые человек бы остановил и пересмотрел. Небольшое изменение billing в итоге редактирует auth, общие типы, шаблоны писем и фоновые задачи просто потому, что это самый короткий путь к рабочему результату.

Другая ошибка — передавать сырые таблицы базы данных через границы модулей или сервисов. Это кажется быстрым, потому что никто не пишет код для преобразования данных. Но потом детали хранения просачиваются в остальную часть продукта. Если одна команда разделит таблицу, переименует колонку или изменит nullable-поле, все остальные унаследуют это изменение, хотят они этого или нет.

Переименования полей создают более медленную версию той же проблемы. Кто-то меняет customer_name на full_name в одном месте и надеется, что остальное продолжит работать. Инструменты ИИ часто находят очевидные ссылки, но могут пропустить экспорт, фильтры, отчеты, старые endpoint'ы и скрипты, о которых никто не вспоминает, пока они не сломаются.

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

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

Предупреждающие признаки обычно хорошо видны, если знать, куда смотреть. Промпты начинают просить «обновить каждый файл, который это использует». Команды передают полные ORM-модели вместо небольших объектов контракта. Старые скрипты появляются в CI или в плановых задачах. Две команды меняют одну схему без назначенного ревьюера.

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

Быстрые проверки перед запуском

Сократите поломки между слоями
Держите бизнес-правила в одном месте и остановите исправления по всему репозиторию.

Быстрый код ощущается отлично ровно до того момента, когда небольшое изменение расползается сразу по трем частям продукта. Двухминутная проверка на границе успевает поймать многое из этого.

Перед запуском задайте несколько прямых вопросов. Может ли одна сторона измениться сама по себе? Если UI меняет подпись, а backend делает внутренний рефакторинг, другая сторона должна остаться нетронутой, если только сам контракт не изменился. Есть ли один владелец контракта? Один человек или одна команда должны утверждать изменения схемы, состояния и правил версий. Если владение размыто, граница будет дрейфовать.

Ваши тесты тоже должны падать при нарушении контракта. Переименованные поля, удаленные поля, новые значения enum и отсутствующие состояния, такие как empty, loading, denied или archived, должны вызывать понятный сбой. И попросите нового коллегу объяснить границу простыми словами. Если ему нужно десять минут, чтобы проследить файлы, промпты и побочные эффекты, линия слишком размыта.

Есть еще одна проверка, которую легко пропустить: сравните область промпта с diff. Если вы попросили изменить один модуль, а модель отредактировала общие helper'ы, код auth или общие типы, остановитесь и проверьте это вручную.

Небольшая SaaS-команда может почувствовать это сразу. Допустим, поддержка добавляет новое состояние тикета под названием waiting_on_customer. Если из-за этого на первый же день приходится править отчетность, billing и мобильные уведомления, значит команда не просто добавила состояние. Она вскрыла скрытую связанность.

Это не значит, что каждому контракту нужен тяжелый процесс. Короткий файл схемы, несколько contract tests и назначенный владелец часто оказываются достаточными. Это как раз та практичная дисциплина, о которой говорит Oleg Sotnikov в своей консультационной работе по AI-first: она делает скорость полезной, а не дорогой.

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

Следующие шаги для команд, которым нужна более строгая структура

Если команда постоянно чинит одну и ту же путаницу, не начинайте с полной перестройки процессов. Начните с одного workflow, который уже болит. Возьмите что-то конкретное, например onboarding клиентов, обновления billing или исправления багов, найденных службой поддержки, и разберите все стыки, которые происходят сегодня.

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

До начала следующего спринта зафиксируйте в этом workflow один интерфейс. Пусть он будет маленьким. Определите вход, выход, владельца и то, что происходит, если данные отсутствуют или ошибочны. Поместите этот контракт в одно место, где команда его увидит, а потом подкрепите его тестами, чтобы никто не изменил его случайно.

Хорошо работает простой ритм. Разберите один проблемный workflow от начала до конца. Зафиксируйте следующий интерфейс, который команда будет трогать. Назначьте одного владельца этой границы. Рассматривайте изменения на границе в начале спринта, а не позволяйте им проскакивать в середине недели.

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

Иногда самый быстрый вариант — внешняя помощь. Oleg Sotnikov работает со стартапами в роли Fractional CTO и помогает командам выстраивать практичные правила для промптов, понятное владение и границы системы, которые держатся, пока код движется быстро. Если хотите ближе посмотреть на такой подход, oleg.is — хорошее место для старта.

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