
Полная версия
Как думать, когда код пишет ИИ
Вот где происходит тектонический сдвиг. 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-токены. Она не добавила блокировку после неудачных попыток. Она сделала статистически среднее, а не нужное вам.












