Достаточно подробная спецификация — это код. Haskell.. Haskell. ruvds_перевод.. Haskell. ruvds_перевод. агентное программирование.. Haskell. ruvds_перевод. агентное программирование. Блог компании RUVDS.com.. Haskell. ruvds_перевод. агентное программирование. Блог компании RUVDS.com. будущее ии.. Haskell. ruvds_перевод. агентное программирование. Блог компании RUVDS.com. будущее ии. искусственный интеллект.. Haskell. ruvds_перевод. агентное программирование. Блог компании RUVDS.com. будущее ии. искусственный интеллект. Ненормальное программирование.. Haskell. ruvds_перевод. агентное программирование. Блог компании RUVDS.com. будущее ии. искусственный интеллект. Ненормальное программирование. Программирование.
Достаточно подробная спецификация — это код - 1

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

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

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

Все встреченные мной аргументы сторонников этой идеи обычно опираются на два распространённых заблуждения:

  • Заблуждение 1: техническая документация проще кода, который по ней создаётся.

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

  • Заблуждение 2: написание документации должно быть более вдумчивым, чем написание кода.

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

И ниже я на конкретных примерах распишу, почему считаю эти заявления заблуждениями.

Завуалированный код

Начну с проекта Symphony компании OpenAI, который преподносится как пример проекта, сгенерированного на основе технической документации.

Symphony — это оркестратор агентов, который по заявлениям его разработчиков был сам сгенерирован из «специцикации» (SPEC.md). Я специально взяла «спецификацию» в кавычки, так как представленный файл больше походит на псевдокод в Markdown. Если заглянуть в этот документ поглубже, то мы увидим, что он содержит, например, текстовое описание схемы базы данных:

4.1.6 Live-cессия (метаданные сеанса работы с агентом)

Состояние, отслеживаемое, когда запущен подпроцесс ИИ-агента.

Поля:

  • session_id (строка, <thread_id>-<turn_id>)

  • thread_id (строка)

  • turn_id (строка)

  • codex_app_server_pid (строка или null)

  • last_codex_event (строка/перечисление или null)

  • last_codex_timestamp (временная метка или null)

  • last_codex_message (краткое содержание)

  • codex_input_tokens (целое число)

  • codex_output_tokens (целое число)

  • codex_total_tokens (целое число)

  • last_reported_input_tokens (целое число)

  • last_reported_output_tokens (целое число)

  • last_reported_total_tokens (целое число)

  • turn_count (целое число)

    • Количество запусков агента за время работы текущего воркера.

…Или текстовое описание кода:

8.3 Управление многопоточностью

Глобальный лимит:

  • available_slots = max(max_concurrent_agents - running_count, 0)

Лимит на состояние:

  • max_concurrent_agents_by_state[state] если есть (ключ состояния нормализован)

  • в остальных случаях использовать глобальный лимит

Среда выполнения подсчитывает задачи по их текущему отслеживаемому состоянию в словаре running.

8.4 Повторные попытки и увеличение задержки

Создание записи о повторной попытке:

  • Сбросить существующий таймер повтора для той же задачи.

  • Сохранить попытку, идентификатор, ошибкуdue_at_ms и дескриптор нового таймера.

Формула использования задержки:

  • Обычные повторные попытки после корректного завершения воркера выполняются с фиксированной задержкой в 1 000 мс.

  • Повторные попытки, вызванные сбоем, выполняются с задержкой, вычисляемой по формуле min(10000 * 2^(attempt - 1), agent.max_retry_backoff_ms).

  • Максимальная задержка повтора ограничивается установленным значением (по умолчанию 300000 / 5 м).

Обработка поведения повторов:

  1. Получение активных кандидатов-задач (не всех задач).

  2. Поиск конкретной задачи по issue_id.

  3. Если не найдена, освободить её.

  4. Если найдена и всё ещё среди кандидатов:

    • При наличии доступных слотов запустить.

    • В противном случае вернуть в очередь с сообщением об отсутствии свободных слотов оркестратора.

  5. Если найдена, но уже неактуальна, освободить.

… Или разделы, добавленные конкретно для тонкого контроля генерации кода моделью:

6.4 Список полей конфигурации (памятка)

Этот раздел намеренно расширен, чтобы агент мог быстро реализовать слой конфигурации.

  • tracker.kind: строка, обязательная, сейчасlinear.

  • tracker.endpoint: строка, по умолчанию https://api.linear.app/graphql, когда tracker.kind=linear

… Или непосредственно код1:

16. Образцы алгоритмов (безразличные к языку)

16.1 Запуск сервиса

function start_service():
  configure_logging()
  start_observability_outputs()
  start_workflow_watch(on_change=reload_and_reapply_workflow)

  state = {
    poll_interval_ms: get_config_poll_interval_ms(),
    max_concurrent_agents: get_config_max_concurrent_agents(),
    running: {},
    claimed: set(),
    retry_attempts: {},
    completed: set(),
    codex_totals: {input_tokens: 0, output_tokens: 0, total_tokens: 0, seconds_running: 0},
    codex_rate_limits: null
  }

  validation = validate_dispatch_config()
  if validation is not ok:
    log_validation_error(validation)
    fail_startup(validation)

  startup_terminal_workspace_cleanup()
  schedule_tick(delay_ms=0)

  event_loop(state)

Мне кажется, несправедливо позиционировать такой подход как альтернативу коду, когда по факту технический документ уже читается как код (а местами им и является).

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

Этот пример я привела, так как, на мой взгляд, Symphony хорошо отражает первое заблуждение:

Технические документы проще кода, который по ним создаётся

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

Дейкстра писал, почему это неизбежно:

Теперь мы знаем, что выбор интерфейса — это не просто распределение (фиксированного объёма) труда, поскольку сюда необходимо прибавить работу, связанную с взаимодействием через этот интерфейс. Мы также поняли — и это развеяло наши иллюзии — что изменение интерфейса может легко увеличить объём работы с обеих сторон (и порой намного). Отсюда и предпочтение так называемых «узких интерфейсов». Поэтому перевод коммуникации между машиной и человеком на родной язык последнего мало того что приведёт к значительному увеличению нагрузки на первую, но и вовсе не гарантирует, что жизнь человека станет проще.

Если оглянуться на историю математики, то мы увидим обоснования для такого сомнения. Греческая математика зашла в тупик, потому что оставалась в рамках устного и начертательного подхода. Мусульманская «алгебра» после робкой попытки перейти к символике загнулась, вернувшись к риторическому стилю. А современный цивилизованный мир смог развиться — к худшему или лучшему — только когда Западная Европа освободилась от оков средневековой схоластики (тщетной попытки достичь точности с помощью слов.) Всё благодаря тщательным, или как минимум сознательно выстроенным системам символов, которыми мы обязаны таким фигурам, как Виета, Декарт, Лейбниц и позднее Буль.

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

Нестабильность

К тому же, генерация кода на основе спецификаций не гарантирует его надёжной работы! Я даже попробовала использовать подход, предложенный в README Symphony:

Попросите ИИ-агента создать Symphony на любом языке программирования:

Реализуй Symphony в соответствии со следующим техническим документом: https://github.com/openai/symphony/blob/main/SPEC.md

Я попросила Claude Code разработать Symphony на языке Haskell2, и ничего толкового не вышло. Результат можете посмотреть в моём репозитории Gabriella439/symphony-haskell repository.

В коде не только присутствовало множество багов (которые я потом просила Claude исправить, и все эти исправления есть в истории коммитов), но даже когда что-то «работало» (то есть не было сообщений об ошибках), агент просто молча, без какого-либо прогресса, зависал над следующим тикетом Linear:

Создай новый пустой репозиторий

Не нужно создавать проект GitHub. Просто создай пустой репозиторий git.

Иными словами, «тщетная попытка обеспечить словесную точность» (говоря словами Дейкстры) всё равно не приводит к надёжной генерации рабочей реализации.3

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

Разработчики Symphony могли бы попробовать исправить эту нестабильность, расширив спецификацию, но она и без того длинная — примерно 1/6 от размера реализации на Elixir. Если продолжить её расширять, то получится как в истории Борхеса «О точности в науке»:

…В той империи искусство картографии достигло такой степени совершенства, что карта одной провинции получилась размером с целый город, а карта всей империи — размером с провинцию. Но и такие карты невообразимых масштабов показались картографам недостаточными. Тогда их гильдия составила карту всей империи, размером с саму империю и повторявшую её один-в-один. Будущие поколения картографов, которые не разделяли того же рвения к совершенству картографии, сочли столь огромную карту никчёмной и беспощадно бросили её на растерзание палящему солнцу и суровым зимам. Даже по сей день в пустынях Западных земель можно найти обрывки этой Карты, населённые животными и бездомными. Никакого другого наследия великой науки географии в той империи не осталось.

Слоп

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

Тогда почему же я называю следующее утверждение заблуждением:

Создание спецификации должно быть более вдумчивым, чем написание кода

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

На деле спецификация Symphony читается как сгенерированный ИИ слоп. Особо ярким примером этого является Раздел 10.5. Вот его фрагмент:

Контракт расширения linear_graphql:

  • Назначение: выполнить сырой GraphQL-запрос к Linear или мутацию, используя настроенную в Symphony авторизацию трекера для текущей сессии.

  • Доступность: имеет смысл, только когда tracker.kind == "linear", и настроена валидная аутентификация Linear.

  • Предпочтительная форма ввода:

  • {

  •   “query”: “single GraphQL query or mutation document”,

  •   “variables”: {

  •    “optional”: “graphql variables object”

  •   }

}

  • запрос должен быть непустой строкой.

  • запрос должен содержать ровно одну операцию GraphQL.

  • переменные необязательны, и при их наличии должны являться объектом JSON.

  • Реализации могут дополнительно принимать сырую строку GraphQL-запроса в качестве сокращённого ввода.

  • Выполнять по одной операции GraphQL за вызов инструментов.

  • Если переданный документ содержит несколько операций, отказать в вызове инструмента по причине недопустимого ввода.

  • Выбор operationName намеренно вынесен за рамки этого расширения.

  • Повторно использовать установленную конечную точку Linear и параметры аутентификации из активной конфигурации рабочего потока/среды выполнения Symphony; не требовать от агента считывания сырых токенов с диска.

  • Семантика результата инструмента:

    • успешный транспорт + отсутствие ошибок GraphQL верхнего уровня -> success=true

    • присутствуют ошибки GraphQL верхнего уровня -> success=false с сохранением тела ответа GraphQL для отладки

    • недопустимый ввод, нет авторизации или сбой при транспорте -> success=false с содержанием ошибки

  • Вернуть ответ GraphQL или содержание ошибки в виде структурированного вывода инструмента, который модель сможет проанализировать в рамках сессии.

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

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

Заключение

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

Если говорить в более общем ключе, то здесь применяется известный принцип «мусор на входе — мусор на выходе» (garbage in, garbage out, GIGO). Просто нет такой реальности, где вы можете скормить агенту документ, в котором не хватает деталей и ясности, и ожидать от него качественного восполнения этих пробелов. Агенты не умеют читать наши мысли, и даже если научатся, этого будет недостаточно, когда в самой голове неразбериха.

Сноски

  • Я специально не стала форматировать этот фрагмент кода, чтобы сохранить Markdown-формат в стиле GitHub, в каком этот код приводится в оригинальном документе. Все подобные фрагменты оформляются как прямой текст (один из многих признаков, что они сгенерированы ИИ). По факту это является примером того, что модель воспринимает документ буквально, не улавливая суть изложенного запроса. Уверена, было как-то так: человек попросил ИИ преобразовать изначальный набросок проекта в текстовую спецификацию, и ИИ решил, что форматирование фрагментов кода в таком виде делает их более текстовыми. 

  • Мне многие пишут «результат был бы качественней, если бы ты попросила сгенерировать его на более популярном языке, а не Haskell». На что я отвечаю: «Если у агента сложности с генерацией кода на Haskell, это говорит о его неспособности уверенно обобщаться на данные, выходящие за рамки обучающей выборки». 

  • Если вам кажется, что я что-то сделала не так, можете повторить эксперимент сами. 

Автор: Bright_Translate

Источник

Rambler's Top100