Борьба с ИИ-спагетти: C4 + Structurizr

ИИ пишет код быстро, но за недели превращает проект в спагетти. Мы (инженерная лаборатория «Микрофабрика») попробовали решить это с помощью C4 Model и Structurizr DSL - и получили рабочий инструмент, который помогает и людям, и ИИ-агентам удерживать структуру проекта. Рефакторинг, на который мы закладывали неделю, с его помощью занял один вечер. В статье - что такое C4, почему графическое программирование не спасает, почему Structurizr не просто «ещё один рендерер диаграмм», и как всё это работает на практике.
Для кого: для разработчиков, которые пишут код с помощью ИИ и замечают, что проект постепенно превращается в кашу. Для тимлидов и архитекторов. Для тех, кто слышал про C4, но не понимает, зачем это нужно.
Проблема: ИИ пишет быстро, но не думает об архитектуре
Мы пишем код в связке с ИИ (работаем через DeepSeek Harness и OpenCode; модель Kimi K2.7 Code через провайдеров). Это ускоряет разработку в разы. Но есть и негативный эффект, который проявляется не сразу, а через несколько недель работы.
Каждая правка кода, которую выполняет ИИ, как правило, логична. Но когда таких правок тысячи, архитектура разваливается. Возникает дублирование кода: ИИ не видит, что нужная функция уже есть, и пишет новую. Модули начинают знать друг о друге лишнее - нарушается инкапсуляция. Функции обрастают скрытыми зависимостями, у них появляется общее изменяемое состояние: их поведение зависит от того, что происходит в другом месте. Появляются «временные» решения, которые никто не убирает. В целом это классический технический долг, вот только растёт он гораздо быстрее, чем при работе человека, поскольку ИИ вносит правки быстрее.
Все эти симптомы - следствие одного: у ИИ нет структурированного представления об архитектуре. Он видит функции и классы, но не видит, кто за что отвечает и где границы модулей.
Можно возразить: а почему просто не дать ИИ весь код в контекст? Контекст ограничен, между сессиями памяти нет, но главное - даже имея весь код, ИИ не выделяет из него структуру. Он видит текст, а не карту. Структура - это отдельное представление, и его надо дать явно.
У людей архитектура живёт на листочке бумаги, у тимлида в голове, на доске в переговорке, в вики (которая устарела три месяца назад). А иногда архитектуры вообще нет - она размазана по коду. ИИ в этом смысле обнажает проблему: он не держит в голове общую картину, у него нет этого «листочка». На каждой правке он видит только тот кусок кода, на который ему указали или который он сам определил как ключевой, и «улучшает» его локально, но при этом ломает структуру глобально.
Отсюда вывод: ИИ нужен тот самый листочек, но в цифровом виде. Не просто ещё один файл с кодом, а отдельное описание структуры: что за что отвечает и как связано.
Почему графическое программирование не помогло
И тут кажется очевидным решение: перейти на программирование в тех самых блоках и стрелочках, которые архитектор рисует на листочке. Но давайте взглянем на проблему шире. С чего начиналось программирование?
Сначала был текст. Он прекрасно масштабировался до определённого предела, а потом для борьбы со сложностью начала применяться декомпозиция: разбиение на файлы, каталоги, модули. Это помогло, но возникла новая проблема - с ростом объёма текста становится всё труднее удерживать в голове связи между его частями.
Закономерно возникла идея записывать программу как граф - блоками и стрелочками. Получаем наглядную структуру, на которой видны связи элементов программы. Идея получила развитие: блок-схемы, Visual Basic, LabVIEW, Simulink, Scratch, а теперь ещё Low-code и No-code. Список длинный, но у всех этих подходов одна и та же проблема - графы плохо масштабируются.
И дело тут не в том, что «мозг не справляется с двумерностью»: люди прекрасно работают с картами, чертежами и схемами метро. Дело в самой природе графа.
Иногда говорят, что текст «одномерен», а граф «двумерен». Но настоящая разница не в размерности. Текст - это тотальный порядок (последовательность символов), а граф - частичный порядок (узлы и рёбра). У текста есть каноническая линейная форма, у графа её нет. Один и тот же граф можно разложить бесконечно многими способами, и ни один нельзя назвать единственно правильным.
Из этого следуют вполне практические последствия. Diff текста читается глазами: добавил строку - увидел зелёную, убрал - красную. Изменение локальное, и diff локальный. В графе так не получится: вставка узла требует переукладки, и на картинке едут чужие узлы. Даже идеальный алгоритм сопоставления не спасёт - задача сопоставления графов в общем случае принципиально тяжёлая, а человек, глядя на две картинки, всё равно не поймёт, что именно изменилось. Поэтому текст диффится, мержится, ищется и рефакторится, а граф - нет.
Проблему усугубляет смешение семантики и расположения. В большинстве визуальных редакторов граф и его расположение хранятся вместе. Изменил одну связь - поехала вся картинка, а git diff показывает мусор вместо смысла.
И наконец, графика плохо описывает алгоритмическую логику. Циклы, условия, рекурсия, generics - всё это словами точнее и компактнее, чем мышкой. for i in range(n) - одна строка; тот же цикл в узлах - это узел-счётчик, узел-условие, тело, обратная связь.
Вывод: графическое программирование проиграло как способ писать код. Но оно позволяет смотреть на код под другим углом - описывать структуру программы графикой. Отображать то, чего не хватает при обычном «текстовом» подходе: кто на кого ссылается, где границы модулей, кто за что отвечает. Но для этого нужны две вещи: иерархическое представление (уровни абстракции) и разделение семантики и расположения блоков.
C4 Model: четыре уровня, чтобы не утонуть
Оказалось, что мы не первые, кто задумывался над этой проблемой. Есть C4 Model - методология описания архитектуры от Саймона Брауна. Название - аббревиатура от четырёх уровней: Context, Containers, Components, Code. Идея простая: описывать систему не одной гигантской диаграммой, а четырьмя уровнями абстракции.
C1 - System Context. Кто пользователи системы? С какими внешними системами она общается? Уровень «для заказчика», где не нужно объяснять код.
C2 - Containers. Из каких высокоуровневых «коробок» состоит система: бэкенд, фронтенд, база данных, кэш, внешние API. Уровень архитектурных решений.
C3 - Components. Что внутри конкретного контейнера: какие бизнес-модули и за что каждый отвечает.
C4 - Code. Классы и методы. Этот уровень в C4 рисуют редко: источник правды здесь - сам код, а диаграммы классов быстро устаревают.
Главная ценность не в самих картинках, а в уровнях описания. Они позволяют остановиться на нужной глубине и держать фокус: когда смотришь на Components, видишь взаимодействия компонентов внутри выбранного контейнера и связи этих компонентов с внешним миром - а не компоненты всех контейнеров сразу.
Structurizr: почему не draw.io и не Mermaid
Для работы с C4 нам нужен инструмент, который удовлетворяет требованиям:
диаграммы как код: у диаграмм есть текстовое представление, которое можно хранить в Git;
разделение семантики и расположения блоков: связи описываются отдельно от координат блоков;
иерархическое представление: нативная поддержка уровней C4, а не плоский граф.
Structurizr - это инструмент, который написал тот же человек, что придумал C4 - Саймон Браун. И это принципиально важно - когда методологию и инструмент создаёт один человек, между ними нет зазора в понимании. Structurizr не «поддерживает C4 как одну из диаграмм» - его DSL и есть C4.
В проекте мы создаём директорию docs/architecture/ со следующей структурой:
docs/architecture/
├── workspace.dsl ← семантика (git-friendly, текстовый файл)
├── workspace.json ← расположение блоков + состояние редактора
└── mermaid/ ← для GitHub-рендерингаworkspace.dsl - это файл на Structurizr DSL. В нём описываются элементы и связи между ними. Никаких координат, никакого «поставь эту коробочку слева, а эту - справа». Только смысл.
Пример (сокращённый):
workspace "URL Shortener" "Example microservices architecture" {
model {
user = person "User" "Creates short links and views stats"
urlShortener = softwareSystem "URL Shortener" "Shortens URLs" {
apiGateway = container "API Gateway" "Go" "Reverse proxy to services"
postgres = container "PostgreSQL" "PostgreSQL 16" "Storage"
redis = container "Redis" "Redis 7" "Cache"
linkService = container "Link Service" "Go" "CRUD links" {
linkHandler = component "Link Handler" "Go" "HTTP handlers"
linkRepo = component "Link Repository" "Go" "DB access"
linkHandler -> linkRepo "Reads/Writes"
}
user -> apiGateway "Uses"
apiGateway -> linkService "REST API"
}
}
views {
systemContext urlShortener "SystemContext" {
include *
autolayout lr
}
container urlShortener "Containers" {
include *
autolayout lr
}
}
}Сохранили файл - Structurizr перерисовал диаграммы. Изменили связь в DSL - картинка обновилась. А ручное расположение блоков, которое вы задали мышкой в интерфейсе Structurizr, сохранилось: оно лежит в workspace.json и не теряется при следующем рендере.
Вот в этом и отличие от альтернатив. draw.io даёт полный контроль над расположением, но смешивает семантику и координаты в одном файле: git diff показывает мусор. Mermaid вообще не хранит расположение элементов, поэтому оно нестабильно: при каждом рендере картинка перестраивается заново, и блоки прыгают. Structurizr разделяет их: семантика в workspace.dsl, расположение в workspace.json.
Таким образом, если вы хотите описывать архитектуру продукта - используйте Structurizr. А если вам понадобится вставить диаграмму связей в README - экспортируйте её в Mermaid.
Как мы это используем
Мы попробовали разные способы работы со Structurizr и в итоге пришли к тому, что запускаем Structurizr в Docker и просто смотрим диаграммы локально в браузере:
cd docs/architecture
# Сгенерировать workspace.json из DSL
structurizr export -workspace workspace.dsl -format json -output .
# Запустить локальный веб-сервер
docker run -it --rm -p 8080:8080 \
-v "$(pwd):/usr/local/structurizr" \
structurizr/structurizr local
# Открыть http://localhost:8080/workspace/1/diagramsДиаграммы обновляются при каждом сохранении workspace.dsl. Расположение блоков сохраняется в workspace.json, который тоже коммитится в Git. Если файла workspace.json ещё нет, Structurizr создаст его сам при старте. Плюс в браузере есть навигация между уровнями - можно щёлкать по элементам и проваливаться в содержимое.
От автоматической генерации PNG/SVG отказались - статичные файлы проигрывают живым диаграммам: в браузере они обновляются на лету. Плюс ко всему старый structurizr-cli из AUR (Arch Linux) не умеет рендерить PNG/SVG - он умеет только текстовые экспорты (PlantUML, Mermaid, JSON, DOT). Технически картинки можно получить связкой Structurizr + Puppeteer (headless Chromium), но это отдельный пайплайн, и ради статичных файлов мы его не держим.
Mermaid-экспорт для GitHub тоже не держим. Диаграмма и так живёт в workspace.dsl - её в любой момент можно открыть в Structurizr. А Mermaid-файл в репозитории - это статичный снимок, который устареет на следующей правке и начнёт врать. Если понадобится показать диаграмму в README, экспорт делается одной командой.
Что в итоге:
Планировали | Реально используем |
|---|---|
Единый DSL как источник правды | ✅ Да, постоянно |
C4 для рефакторинга архитектуры | ✅ Да, главный кейс |
Локальный Structurizr для просмотра | ✅ Да, каждый раз |
Автогенерацию PNG в CI | ❌ Нет, в браузере удобнее смотреть, плюс есть навигация между слоями |
Mermaid-экспорт для GitHub | ❌ Нет, разве что для README |
C4 для мелких правок | ❌ Нет, не детализировали уровень «Code» |
Пример из практики: как C4 спас рефакторинг
Мы работаем над проектом «Трафаретка» - сервис, который позволяет заказать бумажный трафарет по вашему изображению (картинка превращается в полутон из точек, поэтому трафарет масштабируется - хоть на всю стену). Загружаешь картинку, настраиваешь преобразование в полутон, указываешь адрес - и через какое-то время получаешь готовый трафарет.
Код в значительной степени генерировался ИИ. Первая версия собралась быстро, но через несколько недель фронтенд превратился в кашу. Модули переплетались и дублировали функционал, состояние страниц терялось при переходах, на устранение багов уходили дни.
Мы сгенерировали workspace.dsl как снимок текущего состояния - не чтобы дальше генерировать его на каждой правке, а чтобы один раз увидеть картину целиком. Посмотрели на диаграмму - и стало видно, где что должно жить. Рефакторинг занял один вечер вместо недели, которую мы на него закладывали. Без диаграммы это был бы долгий разговор «а кто за что отвечает», с диаграммой - решение нашлось за один обзор экрана.
Но C4 - не серебряная пуля. Диаграмма показывает структуру, а не поток данных: у нас был случай, когда стрелка «Editor → Order» на диаграмме есть, а параметр marginHorizontalMm не сохранялся в состоянии. Баг вылез только в runtime. Тесты и логи C4 не заменяет.
Сама «Трафаретка» сейчас в тестовом режиме: весь путь пользователя можно пройти от начала до конца, но реальные заказы пока не исполняются. Если хотите попробовать: https://trafaretka.website.yandexcloud.net/
C4 и ИИ: главное, зачем это всё
Когда ИИ-агент работает с проектом, у него нет общей картины: он видит только тот код, который ему дали.
C4-модель эту картину даёт. Она позволяет описать архитектуру системы, а Structurizr - сохранить описание в файл workspace.dsl. Это не «весь код в контексте», а компактная модель с явными границами между модулями. Теперь ИИ увидит, что Order отвечает за данные заказа, а не за отрисовку. Что логика переходов живёт в Coordinator, а не размазана по обработчикам кнопок. Что Frame Geometry - отдельная сущность, а не набор магических чисел в коде.
Чтобы ИИ нашёл эту карту, нужна точка входа - AGENTS.md в корне репозитория. Файл с правилами: как запускать проект, какой стиль кода, где посмотреть архитектуру. ИИ читает его и перестаёт гадать. Это де-факто стандарт, поддерживается в Cursor, OpenCode и других средах. И в этом же файле стоит записать правила работы с архитектурой - чтобы ИИ их не забывал:
Меняешь архитектуру - обновляй
workspace.dsl. Иначе диаграмма врёт.C4 не заменяет тесты, логи и внимательность.
C4 - не для мелких правок: для CSS и рефакторинга одной функции он не нужен.
Заключение
Мы начинали эту историю с простой боли: ИИ пишет код быстро, но за недели превращает проект в кашу. А закончили тем, что нашли ту самую «цифровую версию листочка» - файл workspace.dsl в репозитории, который видит и человек, и ИИ-агент.
Главный вывод: графическое программирование проиграло как способ писать код, но нашло свою нишу - описывать структуру. C4 Model + Structurizr DSL - это не «ещё один инструмент для диаграмм», а способ удержать архитектуру в проекте, где код генерируется быстрее, чем вы успеваете его читать.
Мы тоже ещё в процессе: продолжаем осваивать Structurizr и улучшать подход к коду с ИИ. Но даже в таком виде это уже экономит время. Если работаете с ИИ - попробуйте: один файл workspace.dsl, AGENTS.md с правилами, две команды для запуска. Дальше Structurizr сам покажет, что у вас в проекте творится. У нас на «Трафаретке» это заняло один вечер - и сэкономило неделю рефакторинга.
А как вы работаете с архитектурой проекта? 😎
Больше заметок про инженерные эксперименты, ЧПУ, ИИ и DevOps - в нашем сообществе во ВКонтакте: https://vk.ru/microfabrica.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.