Ремесло в эпоху сгенерированного кода
В этом году каждой студии задают один и тот же вопрос: если код может написать модель, за что именно вы берёте деньги? Наш ответ — в виде того, что отказывается принимать наша собственная сборка.
На этой странице
Писать код перестало быть дорогой частью. Читать его дешевле не стало, и эта единственная асимметрия — весь сюжет года.
Модель выдаст четыреста строк правдоподобного TypeScript, пока вы дописываете предложение. Ваша способность удержать четыреста строк в голове и спросить что должно быть верно, чтобы это оказалось неправильным, ровно такая же, какой была в 2019-м. Узкое место переехало в ту единственную часть процесса, которая не ускоряется. Любой интересный мне спор об ИИ в инженерии начинается отсюда; скучные тайно сводятся к тому, был ли набор текста хоть когда-нибудь сутью дела.
У сгенерированного кода к тому же особый и неприятный способ ломаться: он ломается правдоподобно. Написанные руками ошибки обычно и выглядят как ошибки — неудачно названная переменная, ветка, которая очевидно никогда не исполняется, комментарий, спорящий со строкой под ним. Ошибки модели приходят одетыми как надо. Имена хорошие, структура привычная, комментарии согласны с кодом, а второй ответ DNS никто не проверяет.
Поэтому мы перестали спрашивать, кто написал строку, и начали спрашивать, что в сборке возразит, если строка окажется неверной.
Проверки, которые бегут без меня
Orrery рассчитывает орбиты спутников в браузере по наборам элементов, которые публикует CelesTrak. В разработке, до того как отрисуется хотя бы один пиксель, запускается самопроверка. Дайте ей набор элементов класса МКС — среднее движение 15.495 оборота в сутки — и она обязана вернуть период 92.933 минуты и скорость 7.660 км/с. Дайте геостационарный набор — и долгота должна уйти на 0.010° за шесть часов. Эти числа не наши. Они из физики и были верны задолго до того, как появился этот файл.
Ценность такой проверки целиком в том, что она предотвращает: карту спутников, которая неверна везде и очевидно неверна нигде. Орбитальная ошибка о себе не сообщает. Каждая точка по-прежнему движется плавно, планета вращается, и ничто на экране не скажет, что решатель Кеплера потерял итерацию в чьей-то уборке кода. А ассерты скажут.
Тот же инстинкт в игровом масштабе. Slipstream — раннер по полосам, ряды препятствий в котором приходят из генерируемых паттернов, и паттерн, не оставляющий ни одной достижимой полосы на скорости, — это не сложный уровень, это сломанный уровень. Такой дефект прекрасно переживает плейтест: тестировщик, который погиб, решает, что просто плохо играл. Поэтому паттерны валидируются при загрузке модуля: вперёд прогоняется, какие полосы аппарат физически успевает достать на худшей скорости, 32 единицы в секунду, и утверждается, что в каждом ряду хотя бы одна из них остаётся проходимой. Комментарий в этом файле — вся философия в девяти словах: честно для валидатора значит честно для игрока.
| Что запускается | Что утверждает | Почему человек это не закроет |
|---|---|---|
| Самопроверка расчёта в Orrery | Орбита класса МКС → период 92.933 мин, 7.660 км/с; дрейф геостационара 0.010° за 6 ч | Орбитальную ошибку не видно на глаз — карта выглядит правильной, будучи неверной |
| Валидатор паттернов Slipstream | В каждом ряду препятствий остаётся достижимая полоса на худшей скорости | Плейтест может не найти нечестный ряд; доказать, что таких нет, он не способен |
| Схема, границы и санитайзер Blueprint | 3–14 узлов, ровно 3 фазы, никаких висящих связей, никаких неизвестных id опций | Вывод модели читается гладко даже тогда, когда он структурно невозможен |
Три проверки, вшитые в те пути кода, которые разработчик и так проходит. Ни одной из них не важно, кто написал проверяемый код.
Это последнее свойство важнее самих проверок. Верификация, которую нужно не забыть запустить, — это верификация, которую вы рано или поздно пропустите, обычно в день, когда движетесь быстрее всего, то есть теперь почти каждый день.
Модель предлагает, движок решает
Blueprint — место, где на этот вопрос пришлось отвечать коммерчески, а не философски. Посетитель описывает продукт, Claude набрасывает архитектуру, страница её рисует и прикладывает цену. Очевидная катастрофа — языковая модель, придумавшая число, которое незнакомый человек затем вполне резонно примет за нашу цену.
Поэтому в этом коде есть правило, записанное предложением, а не флагом конфигурации: модель никогда ничего не оценивает — она только предлагает выбор, да и он сначала проходит очистку.
На практике модель выбирает из закрытого каталога: платформы, функции, ИИ-возможности, уровень дизайна, число интеграций. Неизвестные идентификаторы отбрасываются на входе. Обязательные поля с единственным выбором, вернувшиеся пустыми, заполняются значениями по умолчанию. Дальше арифметика происходит там, куда модель не дотягивается: базовые юниты суммируются по выбранным пунктам, умножаются на сложность продукта, уровень QA и объём запуска, переводятся через диапазон ставки за юнит; сроки — платформы идут параллельно, остаток по пятнадцать юнитов в неделю, плюс двухнедельный буфер. Это тот же детерминированный движок, что стоит за нашей страницей цен, и читает он те же правила. Список enum в выходной схеме, каталог опций в промпте и allowlist санитайзера выведены из одного и того же объекта правил, поэтому разъехаться незаметно они не могут.
Модели позволено фантазировать об архитектуре. Ей не позволено приближаться к числу, по которому клиент может действовать.
Два решения помельче в том же духе. Идея посетителя оборачивается в теги и трактуется как данные, а не как инструкции, потому что текстовое поле на публичной странице — недоверенный ввод, как бы разговорчиво оно ни выглядело. А когда чертёж превращается в запрос предложения, оценка пересчитывается на сервере из очищенных выборов; числа, пришедшие из браузера, игнорируются начисто. Цена клиента не должна быть значением, прошедшим через место, которое может отредактировать посторонний.
Знать, от чего вы не защитились
Сервер, который ходит по адресам, набранным незнакомцами, — это заряженное оружие, направленное в вашу же сеть, и Reliquary именно таков: вставьте ссылку, наш сервер запросит страницу, разберёт её и вернёт экспонат. Адреса, которые стоит атаковать, живут не в публичном интернете. 169.254.169.254 — там, где облачные провайдеры держат метаданные инстанса; приватные диапазоны — там, где живёт вся остальная ваша инфраструктура. Про эту сборку мы писали в июне; защита — самая неэффектная её часть и та, которую я стал бы отстаивать первой.
Только http и https, только порты 80 и 443, никаких учётных данных, протащенных в поле userinfo. Денилист на приватные, зарезервированные, link-local и carrier-grade-NAT диапазоны. DNS резолвится со всеми возвращёнными адресами, и каждый обязан быть публичным, потому что проверка только первого ответа — классический промах в миллиметре. Префиксы IPv6 классифицируются раньше хвоста в точечной записи, именно в таком порядке: обратный порядок пропустил бы mapped-адрес по публично выглядящему хвосту.
А дальше часть, которой я горжусь больше. Комментарий в том же файле называет атаку, которую мы не закрыли: DNS rebinding. Между моментом, когда мы проверили имя хоста, и моментом, когда соединяется сокет, ответ может смениться на приватный. Закрыть это как следует — значит прибить соединение к уже проверенному адресу, а значит завести собственный HTTP-диспетчер и новую поверхность зависимостей, и мы рассудили, что для функции, которая ходит за аукционными объявлениями, оно того не стоит. Возможно, суждение неверное. Но оно хотя бы записано, так что следующий читатель файла унаследует рассуждение, а не впечатление, что всё предусмотрено.
Вот эту часть за вас не сделает ни один генератор. Модель охотно напишет валидатор URL и, возможно, напишет хороший. Чего она не может — решить, какой остаточный риск ваш бизнес готов на себя взять, и сказать об этом вслух там, где с этим можно поспорить. Суждение — не способность выдать защищённую версию. Это знание формы того, что осталось незащищённым, осознанный выбор в её пользу и собственная подпись под этим выбором.
Ещё две вещи остались здесь упрямо человеческими. Первая — инвариант, то самое предложение, которое системе не разрешено нарушать: инварианты кодируют то, что вам важно, а ничто в обучающем корпусе не знает, что важно вам. Вторая — решение, чего не строить. Reliquary держит каждый экспонат в локальном хранилище вашего браузера и ровно ничего — на наших серверах. Это стоило настоящих функций: ни шеринга, ни синхронизации между ноутбуком и телефоном, жёсткий потолок в шестьдесят экспонатов и полное отсутствие представления о том, что люди с этой штукой делают. Ни одна система, обученная на последнем десятилетии разработки, не предложила бы отказаться от данных. Это было решение вкуса, а вкус — ровно то, ради защиты чего написан «Стандарт, а не настроение».
Что подводит меня к слову, которое в этом году употребляют все. Слоп — не свойство ИИ; это свойство ревью. Код, о котором никто не может сказать что должно быть верно, чтобы это оказалось неправильным, — слоп, пришёл он от модели, от подрядчика или от меня в час ночи. Наша индустрия выпускала его океанами задолго до того, как хоть что-то научилось дописывать функции. Модели этот способ отказа не изобрели. Они снизили стоимость его производства, а это аргумент за то, чтобы относиться к ревью серьёзнее, а не легче.
Неудобная часть этой позиции в том, что она плохо масштабируется, и, по-моему, продавцы будущего это знают. Генерация почти бесплатна и дешевеет дальше. Верификация — это труд, он упрямо линеен, и именно за него мы на самом деле берём деньги. Когда клиент спрашивает, за что он платит инженеру в 2026 году, честный ответ: не за набор текста. За отказы. За число, которое обязано было прийти из калькулятора, за ряд, который обязан был остаться проходимым, за риск, который обязан был быть названным в комментарии, где с ним можно не согласиться.