Пост

Синки, а не пайпы: архитектура ПО в эпоху ИИ

Синки, а не пайпы: архитектура ПО в эпоху ИИ

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

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

Два десятилетия спустя мы сталкиваемся ровно с той же проблемой, только теперь “новый разработчик”, который каждое утро присоединяется к вашей кодовой базе, - это ИИ-агент. И у него нет памяти. Каждая сессия начинается с нуля.

Это меняет то, как нам следует думать об архитектуре программного обеспечения.

Новичок, который никогда не учится

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

Его метафора точна. Когда ИИ-агент входит в вашу кодовую базу, это как главный герой “Memento”. Никакого контекста, никакой институциональной памяти - только свежий взгляд и задача сделать что-то полезное. В отличие от человека-новичка, который постепенно строит ментальную модель за недели и месяцы, ИИ никогда не накопит это понимание. Завтра он начнёт заново. Через час он начнёт заново.

В своей диссертационной работе я изучал, можно делать запросы к архитектуре ПО, “навигацировать” по ней с помощью формальных методов. Я использовал инструмент под названием Grok (нет, не тот Grok), построенный на реляционном исчислении, чтобы извлекать и анализировать конкретную архитектуру реальных систем вроде PostgreSQL.

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

ИИ-агенты живут в этом разрыве постоянно.

Что моя диссертация поняла правильно (случайно)

Когда я работал над восстановлением архитектуры и навигацией по ней двадцать пять лет назад, мотивацией было понимание для людей. Как помочь разработчику разобраться в кодовой базе на миллион строк? Как отвечать на вопросы вроде: “от каких подсистем на самом деле зависит этот компонент?” или “есть ли архитектурные нарушения, где модуль A залезает во внутренности модуля B?”

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

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

Если, чтобы понять, что делает компонент, вам нужно прослеживать детали реализации, ваша архитектура так себе - будь читателем джун в 2002-м или ИИшка в 2026-м.

Глубокие модули и возвращение старых идей

Pocock выступает за то, что John Ousterhout называет “deep modules” в книге A Philosophy of Software Design. Это компоненты с простыми интерфейсами, скрывающими существенную сложность реализации. Вместо паутины крошечных “мелких” модулей, которые свободно импортируют друг друга, вы организуете кодовую базу в достаточно крупные части с чёткими границами. Интерфейс - наверху. Реализация - за стеной. Если тесты проходят, внутрь заглядывать не нужно.

Это не новая идея. Это инкапсуляция. Это сокрытие информации. Это Parnas, 1972. Это то, о чём полвека говорит каждый учебник по программной инженерии.

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

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

Синки, а не пайпы

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

“Пайп” (pipe) - это компонент, который выполняет свою работу, а затем запускает что-то ещё. Он пишет в базу данных, которая запускает триггер, который отправляет уведомление, которое ставит задачу в очередь, которая обновляет другую систему. Чтобы понять, что делает пайп, нужно пройти всю цепочку. Нужно вскрыть каждый компонент ниже по течению и отследить каскад побочных эффектов.

“Синк” (sink) - это компонент, который получает вход, выполняет свою работу и останавливается. Его эффекты локализованы. Вы можете описать, что он делает, не ссылаясь ни на что другое в системе. Вы можете рассматривать его как чёрный ящик.

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

Это огромный объём контекста для любого агента, и это ровно тот тип неявной связанности, который делает кодовые базы хрупкими.

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

Low coupling, high cohesion (но теперь мы действительно это имеем в виду)

Мы учим “low coupling и high cohesion” с 1970-х. На практике большинство кодовых баз сильно отклонились от этого идеала. Легко оправдать быстрый импорт через границу модулей. Легко позволить util функции обрасти зависимостями. Легко дать границам размыться, когда вы быстро делаете фичи.

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

В своей диссертации я использовал реляционные запросы, чтобы выявлять именно такие архитектурные нарушения в PostgreSQL. Мы могли находить места, где реальная структура зависимостей расходилась с задуманной архитектурой, где модули, которые должны были быть независимыми, отрастили скрытые связи. Тогда это было полезно для сопровождения и понимания. Сейчас это необходимо для надёжности ИИ.

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

Постепенное раскрытие сложности

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

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

Для ИИ-агентов это должно быть “впаяно” в файловую систему и сам код. Структура каталогов должна отражать концептуальную архитектуру. Публичные интерфейсы должны быть очевидными и хорошо типизированными. Детали реализации должны быть спрятаны за ясными границами. Когда ИИ-агент исследует вашу кодовую базу, он должен уметь построить рабочую ментальную модель снаружи внутрь - так же, как мои диссертационные инструменты позволяли делать это человеческим аналитикам два десятилетия назад.

Эхо длинною в двадцать пять лет

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

Теперь те же проблемы стали проблемами машинного масштаба. ИИ-агенты упираются в те же стены, что и новые разработчики, но делают это сотни раз в день - и не способны накапливать знания между сессиями. Архитектурные грехи, которые человеческая команда могла терпеть (скрытая связанность, недокументированные побочные эффекты, модули, которые имеют смысл только если ты знаешь историю), для ИИ разработки смертельны.

Ответ не новый. Он тот же, что был всегда: хорошо определённые границы модулей, ясные интерфейсы, минимум побочных эффектов, high cohesion внутри модулей, low coupling между ними. Единственное, что изменилось - цена ошибки.

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

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

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

Данная статья является вольным переводом статьи Sinks, Not Pipes: Software Architecture in the Age of AI

Авторский пост защищен лицензией CC BY 4.0 .