Почему с ИИ основы разработки стали важнее

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

Содержание

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

В докладе «Software Fundamentals Matter More Than Ever» на канале AI Engineer он разбирает эту проблему через знакомые инженерные практики. Общая концепция продукта, единый язык предметной области, короткие циклы проверки и продуманные границы модулей помогают и человеку, и агенту удерживать систему понятной. Покок показывает связь между этими практиками: удачная архитектура облегчает тестирование, тесты дают агенту обратную связь, а ясные интерфейсы позволяют делегировать реализацию, не теряя контроля над устройством приложения.

Цена кода определяется тем, насколько легко его менять

Покок начинает с подхода specs-to-code. В его изложении разработчик описывает приложение в спецификации, ИИ превращает её в код, а при обнаружении проблемы человек снова меняет спецификацию и запускает генерацию. Сам код при этом можно почти не рассматривать: предполагается, что нужное поведение удастся получить через документ.

На практике Покок всё же заглядывал в реализацию. По его наблюдению, после последовательных запусков она становилась хуже. Он пытался понять, как объяснить модели, что такое хорошая кодовая база, и вернулся к книге Джона Остерхоута (John Ousterhout) A Philosophy of Software Design.

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

Мэтт Покок рядом со слайдом с определением сложности программной системы и подписью John Ousterhout.
Определение Джона Остерхоута: сложность связана с устройством системы и мешает понимать и изменять её. Покок использует изменяемость как критерий качества кодовой базы. ↗ 2:49

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

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

До плана нужно добиться общего понимания

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

Покок обращается к понятию design concept из книги Фредерика Брукса The Design of Design. Речь об общем представлении того, что создаётся, которое складывается между участниками проектирования. Его нельзя свести к самому факту наличия Markdown-файла: план может существовать, а понимание задачи у человека и агента всё ещё будет разным.

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

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

Слайд с командой /grill-me и английской инструкцией об интервью до общего понимания плана.
Инструкция /grill-me предлагает подробно расспрашивать человека о плане до общего понимания, проходя ветви дерева проектных решений и последовательно разрешая зависимости между ними. ↗ 6:29

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

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

Единый язык связывает разговор и реализацию

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

В предметно-ориентированном проектировании, Domain-Driven Design, этому противостоит ubiquitous language — единый язык. Общая модель предметной области должна проявляться и в разговорах, и в названиях и понятиях кода. Важно не просто собрать определения, а пользоваться ими последовательно: при обсуждении требований, проектировании и реализации.

Слайд с определением ubiquitous language, связывающим общую модель предметной области с разговорами и кодом; внизу указаны Eric Evans и Domain-Driven Design.
На слайде приведена мысль Эрика Эванса: разговоры разработчиков и выраженные в коде понятия должны опираться на одну модель предметной области. ↗ 8:40

У Покока практическая форма этой договорённости — Markdown-словарь. Его инструкция для агента изучает кодовую базу, находит используемые понятия и составляет таблицы терминов. Затем словарь получают и человек, и ИИ. Сам Покок держит его открытым во время интервью и планирования, чтобы сверять смысл обсуждаемых слов.

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

Размер шага ограничен скоростью проверки

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

Однако наличия инструментов недостаточно. По наблюдению Покока, агент склонен сначала написать большой объём кода и лишь потом вспомнить о проверке типов или тестах. К этому моменту он уже сделал много предположений, которые ещё не подтверждал. Покок связывает это с метафорой из The Pragmatic Programmer: нельзя ехать быстрее, чем позволяет освещённый фарами участок дороги. В разработке предел скорости задаёт частота получения обратной связи.

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

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

Глубокие модули упрощают проверку и навигацию

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

Сравнение Deep Modules и Shallow Modules: функциональность, сложность интерфейса и скрытие внутреннего устройства.
Слева — глубокие модули: много функциональности, простой интерфейс, скрытая сложность. Справа — неглубокие: мало функциональности, сложный интерфейс, детали вынесены наружу. ↗ 13:15

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

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

Прямоугольники разного размера изображают глубокие модули; у каждого выделена узкая верхняя полоса интерфейса.
Схема системы из глубоких модулей. Узкие полосы сверху обозначают интерфейсы, а большие прямоугольники — скрытую за ними реализацию. ↗ 14:04

Для поиска таких преобразований Покок использует инструкцию improve-codebase-architecture. Она предлагает изучить кодовую базу, найти связанную логику и рассмотреть возможность объединить её за границей глубокого модуля. Само преобразование он не называет простым, но считает, что поиск подходящих мест можно вести повторяемым процессом.

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

Делегировать реализацию, сохраняя контроль над границами

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

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

У этого совета есть существенное ограничение. Покок говорит о менее критичных частях приложения и прямо исключает из такого упрощения, например, финансовую логику. Делегирование опирается на проверяемую границу и понимание назначения модуля; одного доверия к сгенерированному коду недостаточно.

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

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

← Все статьи
Иллюстрация