Настройки чтения
Размер шрифта
Высота строк
Поля
На страницу:
3 из 4

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

Что он создаёт на практике. Первое — DSL общения для конкретного домена. Это формальный, но человекочитаемый язык описания сущностей и операций, который становится прослойкой между бизнес-требованием и генерацией кода. Например, DSL для бэкенда компании может выглядеть как набор декларативных конструкций: ENTITY Order, API POST /orders WITH AUTH, CONSTRAINT: order.total > 0. Модель обучается понимать этот DSL через системный промпт-легенду. Другие разработчики компании пишут на этом DSL — и получают предсказуемый, стандартизированный код.

Второе — системы автоматической верификации. Senior не доверяет сгенерированному коду. Он проектирует «контур проверки»: модель генерирует код, затем другой промпт (или другая модель) проверяет этот код на соответствие исходному DSL, ищет уязвимости и логические дыры, и только после этого код попадает к человеку на финальное ревью. Он строит пайплайн «Спецификация → Генерация → Авто-ревью → Человеческое утверждение».

Третье — оркестрация ансамблей моделей. Staff-уровень означает, что разработчик управляет не одной LLM, а роем специализированных агентов. Одна модель — эксперт по безопасности, другая — по перформансу, третья — по пользовательскому опыту. Они «спорят» друг с другом в автоматическом режиме, а человек-дирижёр модерирует этот спор и принимает финальное решение в сложных случаях.

Четвёртое — проектирование self-correcting циклов. Это высший пилотаж: промпт-программа, которая сама обнаруживает свои ошибки и исправляет их. Например: модель генерирует SQL-запрос; второй промпт просит её «представь, что ты — злобный DBA, найди уязвимость в этом запросе»; третий промпт даёт исходную задачу плюс найденную уязвимость и просит переписать запрос безопасно. И так по кругу, пока уязвимости не закончатся. Человек не участвует в итерациях — он спроектировал этот самоисправляющийся механизм.

2. Детальный разбор: что такое «промпт-программа» и чем она отличается от просто промпта.

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

Элементы промпт-программы. Ветвление: «Если модель ответила кодом, который не компилируется, — передай ошибку компилятора обратно в модель с инструкцией исправить». Это цикл «генерация — компиляция — исправление», который не требует участия человека. Циклы: «Генерируй тест-кейсы для функции, пока покрытие веток не достигнет 95%». Модель в цикле генерирует тесты, прогоняет их, видит непокрытые ветки и генерирует ещё. Утверждения (assertions): встроенные в промпт-программу проверки. «Если сгенерированный ответ содержит слово "предполагая" или "допустим", останови выполнение и запроси у разработчика уточнение — модель угадывает, а не следует спецификации». Память состояний: промпт-программа ведёт журнал принятых решений. Если на пятом шаге выясняется, что решение на втором шаге было неверным, программа автоматически откатывается и перезапускает генерацию с уточнённым контекстом.

3. Кто нанимает Staff Prompt Architects и зачем.

Мы дадим читателю отрезвляющий взгляд на рынок. Сегодня такие позиции редко называются «Prompt Architect» — это временный термин. Они скрываются за названиями: AI Systems Engineer, LLM Ops Lead, Head of AI Tooling, Principal Architect (AI-augmented systems). Но суть одна: компаниям нужны люди, которые превращают дорогую и непредсказуемую модель в стабильный, конвейерный инструмент производства кода. Им не нужен человек, который пишет промпты. Им нужен человек, который создаёт фабрику, где другие люди и агенты пишут промпты по его стандартам, а на выходе получается качественный продукт.

Итоговый вывод подраздела: Разница между Junior и Staff в новой парадигме — это разница между тем, кто использует микроскоп, чтобы лучше видеть, и тем, кто проектирует сам микроскоп. Junior спрашивает модель. Middle управляет диалогом с моделью. Senior проектирует систему, в которой модели работают согласованно и проверяемо. Если вы хотите быть незаменимым — переставайте писать промпты. Начинайте проектировать процессы, в которых промпты пишутся, исполняются и верифицируются автоматически. Ваш продукт — не текст запроса. Ваш продукт — архитектура взаимодействия с искусственным интеллектом.

2.3. Когнитивный разворот: мышление не циклами и условиями, а сущностями и контрактами

Традиционное обучение программированию — будь то университетский курс или буткемп — ставит во главу угла поток управления. Студента учат мыслить последовательностями: «сначала проверь условие, потом пройдись циклом, потом вызови функцию, потом обработай исключение». Весь интеллект разработчика направлен на то, чтобы выстроить правильную цепочку команд. Алгоритм — это маршрут. Код — это карта этого маршрута. И это работало, пока код был нашей единственной материализованной мыслью.

Новая реальность требует когнитивного разворота — возможно, самого трудного во всей книге, потому что он ломает многолетние нейронные связи. Мышление должно сместиться с потока управления на трансформации данных и инварианты. Это подход, известный как Design by Contract — проектирование по контракту, — но в эпоху LLM он из нишевой методологии становится основой выживания. Суть проста: вы перестаёте думать о том, как данные проходят через систему, и начинаете думать о том, какими они должны быть на входе, какими — на выходе, и что никогда не должно нарушаться в процессе. Код перестаёт быть вашим главным интеллектуальным продуктом. Он становится лишь одним из возможных доказательств соблюдения контракта, сгенерированным ИИ. И как любое доказательство теоремы, оно может быть длинным или коротким, элегантным или корявым — это неважно, если оно корректно.

Этот сдвиг можно сравнить с переходом от рисования карты вручную к описанию географии. Раньше вы были картографом, который прорисовывал каждую тропинку. Теперь вы — географ, который говорит: «Между точкой А и точкой Б есть река. Через реку всегда есть мост. Ни одна дорога не поднимается выше 500 метров». А ИИ-картограф по этому описанию рисует десять разных карт — и вы оцениваете их не по красоте линий, а по тому, соблюдены ли ваши географические ограничения.

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

Первое, с чего мы начинаем, — предусловия. Это утверждения, которые должны быть истинны до того, как операция выполнится. Мы не пишем код. Мы формулируем правила мира. Для операции «забронировать билет» предусловия выглядят так: пользователь аутентифицирован и идентифицирован; рейс существует в расписании; на рейсе есть как минимум одно свободное место; дата вылета находится в будущем относительно текущего момента; пассажир предоставил все обязательные данные — имя, документ, дату рождения; платёжный метод пользователя валиден и не просрочен. Обратите внимание: здесь нет ни слова о том, как это проверить. Нет запросов к базе данных. Нет API-вызовов. Есть только утверждения о реальности, которые должны быть истинны на входе в операцию.

Второе — постусловия. Это утверждения, которые гарантированно истинны после успешного выполнения операции. Для того же «забронировать билет»: создана запись о бронировании с уникальным идентификатором; количество свободных мест на рейсе уменьшилось ровно на количество забронированных мест; билет привязан к конкретному пассажиру и не может быть случайно передан другому; сумма, списанная с платёжного метода, в точности равна стоимости билета на момент бронирования; пользователь получил подтверждение — email или push — с деталями рейса. Снова никакого кода. Только факты о состоянии мира после операции.

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

Теперь магия. Имея этот набор из предусловий, постусловий и инвариантов, мы идём к ИИ-модели и говорим: «Вот контракт системы бронирования авиабилетов. Реализуй его четырьмя разными способами».

Модель возвращает четыре архитектуры. Первая — классический монолит с реляционной базой: транзакции, пессимистические блокировки, проверка мест через SELECT FOR UPDATE. Вторая — event-driven архитектура с Kafka: события «БилетЗабронирован», «ПлатёжПодтверждён», асинхронные саги для отката при сбоях. Третья — минималистичное решение на ключ-значение хранилище с оптимистическими блокировками: каждое место — ключ, атомарная операция compare-and-swap. Четвёртая — акторная модель: каждый рейс — актор, который сериализует все операции над собой и гарантирует инварианты естественным образом.

Теперь мы, как Архитекторы Намерений, оцениваем эти четыре решения. Но оцениваем мы их не по стилю кода — не по тому, насколько «чистый» получился Java-код или насколько идиоматичен Elixir. Мы оцениваем их по единственному критерию: полнота покрытия контракта.

Первый вопрос, который мы задаём каждому решению: «Где в твоём коде проверяется инвариант "количество проданных билетов никогда не превышает вместимость самолёта"?» Монолит показывает транзакцию с SELECT FOR UPDATE — окей, это работает в пределах одного инстанса, но что если инстансов два? Event-driven решение показывает сагу — но что если между событием «ПлатёжПодтверждён» и «МестоЗабронировано» другой пользователь купил последнее место? Решение на compare-and-swap показывает атомарную операцию, которая упадёт при конфликте — это честно. Акторная модель показывает сериализацию — конфликт невозможен в принципе.

Второй вопрос: «Как ты гарантируешь, что один пассажир не купит два билета на один рейс?» Монолит показывает уникальный constraint в базе — надёжно, но только если нет распределённой транзакции с платёжным шлюзом. Event-driven показывает проверку в сервисе-саге — но между проверкой и записью есть окно. Актор показывает, что сама сущность Рейса проверяет список пассажиров — окей. Мы не говорим «этот код красивый» или «этот код уродливый». Мы говорим: «Вот этот код покрывает контракт полностью, а вот этот — создаёт окно уязвимости длиной в 50 миллисекунд, которое ты, разработчик, должен осознать и либо закрыть, либо принять как допустимый риск».

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

Это освобождает невообразимый объём когнитивной мощности. Вам больше не нужно держать в голове, как работает ORM или как настроить пул соединений. ИИ разберётся с реализацией. Ваша голова занята более важным: «А точно ли мы описали все инварианты? А что будет, если платёжный шлюз ответит через 30 секунд? А не упустили ли мы случай, когда пользователь меняет паспортные данные между бронью и вылетом?» Вы перестаёте быть контролёром потока команд и становитесь исследователем пространства возможных нарушений контракта.

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

2.4. Почему «грязный код» прототипа теперь ценнее «чистого кода» бездумного

Это глава-ниспровержение. Глава-атака на одну из самых оберегаемых догм современной разработки — догму Чистого Кода. Я не буду утверждать, что Роберт Мартин был не прав. Я скажу хуже: он был прав для своей эпохи, но его советы стали опасным анахронизмом в эпоху, когда код может быть переписан за секунды.

Десятилетиями нас учили: «Всегда оставляйте код чище, чем вы его нашли». «Думайте о том, кто будет читать ваш код через полгода». «Не смешивайте ответственности». «Выделяйте абстракции». «Следуйте принципам SOLID». Эти правила рождались из суровой реальности: стоимость изменения кода экспоненциально росла со временем. Грязный код был токсичным долгом, который душил проект. И каждый ответственный разработчик должен был думать о будущем читателе, потому что этим читателем, скорее всего, будет он сам через полгода, и он ничего не вспомнит.

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

Ценность в новой парадигме смещается в скорость проверки гипотезы. Главный вопрос больше не звучит как «Насколько легко этот код будет поддерживать через год?». Главный вопрос теперь: «Насколько быстро этот код докажет, что моя идея работает — или что она ошибочна?». Прототип из артефакта, который стыдно показывать, превращается в расходный материал для мышления. Он — не фундамент, а щуп, которым мы протыкаем неизвестность.

Я называю это концепцией Одноразовых Архитектурных Спайков. Термин «спайк» пришёл из экстремального программирования — это эксперимент для исследования неизвестного. Но раньше спайк был дорогим: вы тратили день на его написание, а потом ещё полдня на разбор последствий. Теперь спайк можно сгенерировать, протестировать гипотезу и выбросить за 15 минут. И это меняет всё.

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

Возьмём бизнес-гипотезу: «Наш интернет-магазин должен показывать пользователю персонализированную ленту товаров, отсортированную не по дате добавления, а по предсказанной вероятности покупки, которая вычисляется на основе его последних трёх просмотров». Это туманная, нечёткая задача. Мы не знаем, какой алгоритм сработает. Мы не знаем, какие данные реально коррелируют с покупкой. Мы не знаем, как это будет выглядеть в интерфейсе. У нас есть 10 минут.

Шаг первый. Мы пишем чудовищный промпт, намеренно нарушая все принципы чистоты: «Создай один единственный файл на Python. Смешай всё в кучу: прямые SQL-запросы к базе через сырые строки, логику вычисления рекомендаций, рендеринг HTML с инлайн-стилями, обработку HTTP-запроса через примитивный веб-сервер. Не используй никаких фреймворков, никакого ORM, никаких шаблонизаторов. Не разделяй на модули. Не пиши тесты. Не обрабатывай ошибки элегантно — просто упади, если что-то не так. Мне нужен монструозный, однофайловый прототип, который просто работает. Алгоритм рекомендации: тупо посчитай косинусное сходство между вектором последних трёх просмотренных товаров и всеми остальными товарами. Всё. Сделай это за один ответ».

Шаг второй. Мы получаем 300 строк ужасного, нечитаемого, нарушающего все мыслимые принципы кода. Мы запускаем его. Он работает — медленно, коряво, но работает. Мы открываем в браузере, кликаем. Видим персонализированную ленту. И через 30 секунд взаимодействия понимаем главное: гипотеза о косинусном сходстве даёт бессмысленные результаты. Рекомендации выглядят случайными. Мы только что спасли компанию от месяцев разработки ненужной фичи, а себя — от рефакторинга никому не нужного кода. Время, потраченное на прототип — 10 минут. Время, которое мы не потратили на написание чистого кода с репозиториями, сервисами, фабриками и тестами — примерно два дня.

Шаг третий. Мы выбрасываем этот файл. Полностью. Без сожаления. Он сделал своё дело — проверил гипотезу. Теперь, вооружённые знанием «косинусное сходство не работает, нужно пробовать коллаборативную фильтрацию», мы создаём второй грязный прототип. И так до тех пор, пока не нащупаем работающий подход. И только тогда, когда гипотеза подтверждена, мы говорим ИИ: «Теперь возьми этот грязный прототип и сделай из него чистый production-код. Раздели на модули. Добавь обработку ошибок. Напиши тесты. Используй нормальный фреймворк».

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

Критерий первый: Прототип отвечает ровно на один вопрос. Если вы ловите себя на мысли «заодно сделаем красивый интерфейс» или «и авторизацию прикрутим заодно» — вы уже не прототипируете, вы начали продакшен-разработку, просто под прикрытием. Хороший прототип отвечает на чёткий бинарный вопрос: «Работает ли подход А?» — да или нет. Всё остальное — шум.

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

Критерий третий: Прототип не попадает в репозиторий проекта. Никогда. Он жив

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

Глава 3. Искусство спецификации: промпт-инжиниринг как системная дисциплина

3.1. Анатомия идеального промпта: не просьба, а техзадание

Это первая глава практического блока, и её задача — перевести читателя из режима «общения с моделью» в режим «составления технической документации для исполнителя». Большинство разработчиков, впервые столкнувшись с LLM, совершают одну и ту же ошибку: они общаются с моделью как с равным коллегой, который «поймёт с полуслова». «Напиши авторизацию». «Добавь пагинацию». «Почини этот баг». Это не инженерия. Это гадание.

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

Я предлагаю адаптировать для кода известный в промпт-инжиниринге шаблон CO-STAR. В оригинале это аббревиатура для Context, Objective, Style, Tone, Audience, Response Format. Применительно к разработке мы трансформируем её в боевой инструмент спецификации.

Первый элемент — Контекст. Это самая объёмная и самая важная часть промпта. Вы должны описать не просто текущую задачу, а среду, в которой она существует. Какой это проект? Монолит или микросервисы? Какая версия языка? Какие фреймворки уже используются и почему? Какие соглашения о нейминге приняты в команде? Как устроена база данных? Есть ли существующий код, с которым новый должен взаимодействовать, и как именно? Если вы пишете «сделай авторизацию» без контекста, модель сделает вам стандартную JWT-авторизацию на Node.js, даже если ваш проект — на Go, с сессионной авторизацией через Redis, и в компании принято называть модули не «auth», а «identity». Контекст — это ваша защита от «статистически правильного, но для вас неправильного» решения.

Второй элемент — Цель. Это не расплывчатое «сделай фичу», а точное описание желаемого результата, желательно с критериями приёмки. «Реализовать API-эндпоинт для входа пользователя, который принимает email и пароль, возвращает access token в теле ответа и refresh token в http-only куке, проверяет пароль через bcrypt, блокирует учётную запись после 5 неверных попыток на 15 минут, логирует каждую попытку входа с указанием IP». Заметьте: это не инструкция «сделай цикл, потом проверь, потом заблокируй». Это описание того, что должно получиться на выходе. Модель сама построит поток управления.

Третий элемент — Стиль и тон. Применительно к коду это означает: архитектурный стиль и идиоматичность. Вы должны явно указать, как должен выглядеть результат. «Используй функциональный стиль без мутаций». «Придерживайся чистой архитектуры с разделением на сущности, use cases и инфраструктуру». «Пиши в объектно-ориентированном стиле с явным внедрением зависимостей». «Код должен быть идиоматичным для Go, используй стандартную библиотеку где возможно». Без этого указания модель смешает паттерны из разных экосистем, и вы получите Java-подобный Python или Python-подобный Rust.

Четвёртый элемент — Аудитория. Для кого этот код? Кто будет его читать и поддерживать? «Этот код будут читать джуниор-разработчики, поэтому избегай сложных абстракций и магии метапрограммирования — любой метод должен быть понятен после одного прочтения». Или наоборот: «Это высоконагруженный микросервис, который будут поддерживать опытные инженеры. Допустимы продвинутые паттерны, приоритет — производительность и минимальное потребление памяти». Аудитория определяет сложность допустимых решений.

Пятый элемент — Формат ответа. Это самая недооценённая часть промпта, и именно она отличает любителя от профессионала. Вы должны указать, в каком виде вы хотите получить результат. «Выведи три файла: auth_service.go, auth_handler.go, auth_service_test.go. Каждый файл начни с комментария, описывающего его назначение. Код должен компилироваться без ошибок». Или: «Сначала опиши свой подход в трёх абзацах. Затем представь диаграмму классов в виде ASCII-графики. И только потом выведи код». Управление форматом ответа — это управление вашим же временем на распаковку и понимание сгенерированного.

Шестой элемент, который я добавляю к CO-STAR лично, — Ограничения и негативные спецификации. Это то, чего модель НЕ должна делать. «Не используй внешние библиотеки, кроме стандартной». «Не генерируй main.go — это часть другого сервиса». «Не добавляй логирование, у нас свой слой логирования». «Если тебе не хватает информации — не додумывай, а укажи в ответе, каких именно данных не хватает и почему». Это страховка от самой частой болезни LLM — галлюцинаторного заполнения пробелов.

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

Плохой промпт. Разработчик пишет: «Напиши модуль авторизации». Всё. Модель возвращает стандартную JWT-авторизацию на Express.js с middleware, который проверяет заголовок Authorization, извлекает токен, верифицирует через секретный ключ, зашитый прямо в коде, и возвращает пользователя. Результат непригоден. Почему? Модель угадала стек (Node.js — самый популярный в её обучающей выборке для таких запросов). Она выбрала самую примитивную схему. Она зашила секрет в код. Она не учла refresh-токены. Она не добавила блокировку после неудачных попыток. Она сделала статистически среднее, а не нужное вам.

На страницу:
3 из 4