Logo
Overview

AI для анализа устаревших систем: восстановление требований и документации из кода

July 20, 2026
14 min read

AI для анализа устаревших систем: восстановление требований и документации из кода

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

Ручной реверс-инжиниринг такого проекта — это недели чтения кода, попыток понять, где заканчивается один ограниченный контекст и начинается другой, и бесконечные вопросы к команде «а это зачем?». На каждый ответ получаешь пожимание плечами — никто уже не помнит.

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

Почему писать документацию постфактум руками — провальная стратегия

Если вам «повезло» разбирать легаси, вы знаете этот сценарий. Садитесь читать код. Находите контроллер на 800 строк. Внутри — вызов сервиса, который вызывает ещё три сервиса, а те дёргают внешнее API, пишут в БД и попутно отправляют событие в Kafka. Через два часа вы понимаете, что прочитали 3% кодовой базы и всё ещё не знаете, какие бизнес-правила реализованы в этом модуле.

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

А теперь посмотрите на это с другой стороны. LLM читает 50 тысяч строк за секунды. Она не устаёт, не отвлекается и не пропускает 600-строчные контроллеры по диагонали. Её проблема — не в скорости чтения, а в том, чтобы правильно сформулировать задачу: что именно искать в коде и в каком формате выдать.

Pipeline восстановления: от легаси-кода к документации

Весь процесс укладывается в 7 этапов. Сначала — общая схема, потом каждый этап с подробностями.

100%
graph TD
  LEGACY["Устаревшая система
Монолит/легаси
без документации"]
  CODE["Извлечение кода
Git-репозиторий,
SQL-схемы, конфиги"]
  PARSE["Парсинг
LLM разбирает код
на структурные блоки"]
  PATTERNS["Поиск паттернов
DDD-сущности,
бизнес-правила, интеграции"]
  RULES["Извлечение
бизнес-правил
валидации, workflow"]
  DEPS["Карта зависимостей
Вызовы сервисов,
схема БД, очереди"]
  REQ["Требования
ФТ и НФТ из кода
по шаблону"]
  DIAGRAMS["Диаграммы
C4, Sequence,
State Machine"]
  ADR["ADR-черновики
Архитектурные
решения из кода"]
  REVIEW["Валидация
Аналитик проверяет,
уточняет у команды"]

  LEGACY --> CODE
  CODE --> PARSE
  PARSE --> PATTERNS
  PARSE --> RULES
  PARSE --> DEPS
  PATTERNS --> REQ
  RULES --> REQ
  DEPS --> DIAGRAMS
  REQ --> ADR
  DIAGRAMS --> ADR
  ADR --> REVIEW
  REVIEW -.-> REQ

  style LEGACY fill:#e0e0e0,stroke:#999,color:#333
  style CODE fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style PARSE fill:#f0a500,stroke:#c88400,color:#fff
  style PATTERNS fill:#7b68ee,stroke:#5a4db2,color:#fff
  style RULES fill:#7b68ee,stroke:#5a4db2,color:#fff
  style DEPS fill:#7b68ee,stroke:#5a4db2,color:#fff
  style REQ fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style DIAGRAMS fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style ADR fill:#50c878,stroke:#3a9a5c,color:#fff
  style REVIEW fill:#f0a500,stroke:#c88400,color:#fff

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

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

Этап 1: Инвентаризация — что вообще есть в репозитории

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

Промпт для инвентаризации:

Ты — системный аналитик, который впервые видит этот проект.
Проанализируй структуру репозитория и выдай:
1. Технологический стек: язык(и), фреймворки, БД, брокеры сообщений,
серверы кеширования, API Gateway.
2. Список модулей/пакетов верхнего уровня с кратким описанием
назначения каждого (одно предложение).
3. Внешние интеграции: какие сторонние API или системы вызывает
этот проект? Найди их по импортам HTTP-клиентов, gRPC-стабам,
конфигурационным файлам.
4. Схема БД: перечисли таблицы и ключевые поля. Если в коде нет
миграций, ищи ORM-модели (Entity, @Table, Sequelize.define).
5. Точки входа: HTTP-контроллеры, gRPC-сервисы, консьюмеры очередей.
Для каждого пункта укажи файл-источник (путь от корня репозитория)
и номер строки, где ты это нашёл. Если что-то не найдено — напиши
«[НЕ НАЙДЕНО]», не выдумывай.
Ниже — содержимое репозитория. Для экономии контекста я передаю
только структуру файлов и ключевые фрагменты. Если нужно больше
контекста — скажи, какой файл загрузить следующим.
[структура репозитория или фрагменты кода]

После этого промпта у вас на руках карта проекта. Не детальная, но достаточная, чтобы понять, с чем имеем дело. Если на шаге 5 модель пишет «[НЕ НАЙДЕНО]» напротив консьюмеров очередей — значит, либо их действительно нет, либо они в другом репозитории. Это уже знание, а не догадка.

Этап 2: Карта зависимостей

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

На основе кода ниже построй карту зависимостей между компонентами.
Для каждого компонента (сервис, контроллер, обработчик) укажи:
1. Имя компонента.
2. Что он вызывает: список зависимостей с типом взаимодействия
(HTTP-запрос, вызов метода, запись в БД, публикация события,
чтение из очереди).
3. Кто его вызывает: список компонентов, которые используют этот.
Формат вывода — иерархический список:
Модуль оплаты (PaymentModule):
→ PaymentGatewayClient (HTTP POST /api/v1/charge)
→ OrderRepository (запись в таблицу payments)
→ EventPublisher (событие payment.completed)
Не включай зависимости на уровне утилит и хелперов
(Logger, DateTimeFormatter). Только бизнес-компоненты.
[фрагменты кода модуля]

Этап 3: Извлечение бизнес-правил

Самая ценная часть. Бизнес-правила — это то, ради чего система вообще существует, и именно их никто никогда не документирует. Они растворены в условных операторах, валидациях, конечных автоматах и магических константах.

Проанализируй код и извлеки все бизнес-правила. Бизнес-правило —
это логика, которая определяет, ЧТО можно делать, КОГДА и
ПРИ КАКИХ УСЛОВИЯХ. Это НЕ техническая логика (не пулы
соединений, не ретраи, не форматирование дат).
Для каждого правила укажи:
1. Идентификатор (BR-001, BR-002, ...).
2. Формулировка на человеческом языке: «Если ..., то ...».
Например: «Если сумма заказа превышает 100 000 рублей,
то требуется подтверждение менеджера».
3. Источник в коде: файл, строка, фрагмент кода.
4. Тип: Валидация / Вычисление / Процесс (workflow) /
Ограничение доступа / Интеграционное.
5. Связанные сущности: какие доменные объекты затронуты.
6. Признак: Явное (прямо выражено в коде) / Неявное
(выведено из структуры кода).
Отдельно отметь правила, которые кажутся противоречащими друг
другу (например, BR-005 разрешает скидку, а BR-012 запрещает её
для той же категории пользователей).
[фрагменты кода модуля]

Здесь есть тонкость: LLM склонна принимать любой if за бизнес-правило. if (user == null) throw new AuthException() — это не бизнес-правило, это защитное программирование. Поэтому в промпте явно сказано «это НЕ техническая логика». Но даже с этим уточнением часть правил придётся вычищать вручную — модель перестраховывается и включает всё подряд.

Стоит отметить: примерно 30–40% извлечённых таким образом правил потребуют уточнения у команды. Не потому что LLM ошиблась, а потому что код отражает не намерение, а реализацию. Реализация могла быть компромиссом: «сделаем пока так, потом перепишем». «Потом» не наступило (классика), а компромисс выглядит как бизнес-правило.

Этап 4: Восстановление функциональных и нефункциональных требований

Теперь самое интересное: превращаем паттерны кода и бизнес-правила в требования. Тут работаем в два прохода — ФТ и НФТ.

Функциональные требования:

На основе бизнес-правил и структуры кода (ниже) восстанови
функциональные требования, которые были реализованы.
Для каждого ФТ:
1. Идентификатор (FR-001, FR-002, ...).
2. Название (до 10 слов).
3. Описание: что система делает.
4. Actor: кто инициирует (роль пользователя или система).
5. Предусловие.
6. Основной поток.
7. Альтернативные потоки.
8. Связь с бизнес-правилами: укажи BR-xxx.
9. Степень уверенности: Высокая (явно из кода) /
Средняя (из комбинации правил) / Низкая (предположение).
Требования с низкой уверенностью пометь как
«[ТРЕБУЕТ ПОДТВЕРЖДЕНИЯ]». Не выдумывай требования,
которых нет в коде.
Бизнес-правила и структура кода:
[результаты этапов 2 и 3]

Нефункциональные требования:

На основе кода восстанови нефункциональные требования.
Ищи их в:
1. Конфигурациях (connection pool size, thread pool, cache TTL).
2. Аннотациях и декораторах (@Transactional, @Retryable,
@Timed, @Cacheable).
3. Константах (MAX_RETRIES, TIMEOUT_MS, RATE_LIMIT).
4. Комментариях к полям БД (VARCHAR(255) с комментарием
«ограничено требованиями PCI DSS»).
5. Библиотеках (Hystrix → требование к отказоустойчивости,
Redis → кеширование).
Для каждого НФТ:
1. Идентификатор (NFR-001, NFR-002, ...).
2. Категория: Производительность / Безопасность / Надёжность /
Масштабируемость / Удобство использования / Совместимость.
3. Описание.
4. Метрика (если найдена в коде).
5. Источник в коде (файл, строка).
6. Признак: Явное (конфиг) / Косвенное (выведено из библиотеки).
[фрагменты кода с конфигурациями, аннотациями, константами]

Самый ценный источник НФТ в легаси — это конфигурационные файлы и аннотации. application.yml с connection-timeout: 5000 и pool.size: 20 — это требование к производительности, просто никто не потрудился его сформулировать словами. LLM делает именно это: переводит конфиги обратно в человеческий язык.

Этап 5: Генерация диаграмм

После того как требования восстановлены, нужны диаграммы. Не чтобы «красиво было» (хотя красиво — тоже неплохо), а чтобы команда могла быстро понять, как система устроена. Без диаграмм вы получаете документ на 40 страниц, который никто не прочитает.

C4-диаграммы для легаси особенно хороши тем, что они показывают не только компоненты, но и связи между ними — то, что в коде размазано по десяткам файлов. Если практика C4 для вас новая — посмотрите AI в проектировании архитектуры: LLM для C4-диаграмм, ADR и trade-off анализа, там разбор подхода с примерами промптов.

Промпт для генерации C4 Container диаграммы:

Ты — архитектор. На основе карты зависимостей (ниже) построй
C4 Container диаграмму в формате Mermaid (flowchart TD).
Правила:
- Каждый контейнер — отдельный узел с квадратными скобками.
- Связи — стрелки с подписями: тип протокола и назначение.
- Цвета: синий для внутренних сервисов, зелёный для БД,
фиолетовый для шины сообщений, серый для внешних систем.
- Каждый узел должен содержать краткое описание (1 строка).
- Используй style для раскраски (например,
style PaymentService fill:#4a90d9,stroke:#2c5f8a,color:#fff).
Карта зависимостей:
[результаты этапа 2]

Модель выдаст Mermaid-код. Его можно сразу рендерить — или вставить в Confluence, он там поддерживается нативно.

Этап 6: Черновики ADR

ADR — это не только про новые решения. Часто архитектурно значимые решения уже приняты и живут в коде, просто не задокументированы. LLM может восстановить их постфактум — это не полноценный ADR, а скорее черновик, который команда допилит.

Промпт для генерации ADR-черновика:

Ты — архитектор, восстанавливающий архитектурные решения
из легаси-кода. Найди в коде признаки архитектурных решений
и оформи их как ADR по шаблону:
## ADR-NNN: <Название решения>
**Статус:** Accepted (выведено из кода)
**Дата:** неизвестна (нет в git history)
**Контекст:** в какой ситуации принималось решение.
**Решение:** что было выбрано и как реализовано.
**Альтернативы:** какие ещё варианты были возможны на основе
паттернов в коде.
**Последствия:** что из этого решения следует сейчас
(из кода: зависимости, ограничения).
ADR, которые стоит искать:
- Единая точка входа для платежей (PaymentGateway).
- Использование событийной модели вместо прямых вызовов.
- CQRS-подход (разделение чтения и записи).
- Выбор конкретной БД под задачу (из конфигов и ORM-моделей).
[фрагменты кода, результаты этапов 2-5]

Почему важно генерировать именно черновики ADR, а не просто раздел «Архитектурные решения» в описании системы? Потому что ADR — это формальный документ с контекстом и последствиями. Он даёт ответ не только на вопрос «как сделано», но и «почему сделано именно так». Даже если исходные причины принятия решения утеряны (автор уволился), зафиксированный факт «в коде это выглядит так, вероятная причина — X» уже лучше, чем ничего. Подробнее о практике ADR — в ADR: Architecture Decision Records — зачем и как документировать архитектурные решения.

Как это выглядит в бою: кейс с модулем оплаты

Давайте посмотрим на весь процесс на конкретном примере — модуль оплаты легаси-монолита интернет-магазина. Код на Java/Spring, 12 тысяч строк, 3 года без документации, первоначальный разработчик в отпуске. Навсегда.

100%
sequenceDiagram
  participant A as Аналитик
  participant LLM as LLM
  participant Repo as Git-репозиторий<br/>(легаси)
  participant Docs as Выходная<br/>документация
  
  A->>LLM: Задача<br/>«Восстанови требования<br/>по модулю оплаты»
  LLM->>Repo: Сканирование кода<br/>модуля оплаты
  Repo-->>LLM: Контроллеры, сервисы,<br/>SQL-миграции, конфиги
  
  LLM->>LLM: Анализ структуры:<br/>endpoint'ы, слои,<br/>зависимости
  
  LLM->>Docs: Проект требований:<br/>12 ФТ, 5 НФТ
  
  A->>LLM: «Откуда взято требование<br/>про 3 попытки платежа?»
  LLM->>Repo: Поиск константы<br/>PAYMENT_RETRY_COUNT
  Repo-->>LLM: PaymentService.java:142<br/>MAX_RETRIES = 3
  
  LLM->>A: Источник:<br/>PaymentService.java:142<br/>FR-007, FR-012
  
  A->>LLM: «Построй C4 Container<br/>для модуля оплаты»
  LLM->>Repo: Анализ импортов<br/>и HTTP-клиентов
  Repo-->>LLM: Вызовы: PaymentGateway,<br/>NotificationService, БД
  
  LLM->>Docs: C4 Container diagram<br/>+ описание контейнеров
  Docs-->>A: Готовая документация:<br/>требования + диаграммы + ADR

Диаграмма показывает итеративный процесс: аналитик ставит задачу, LLM сканирует код модуля оплаты и выдаёт первый драфт требований. Аналитик задаёт уточняющий вопрос («откуда взято требование про 3 попытки?»), модель находит константу MAX_RETRIES = 3 в PaymentService.java:142 — и требование становится верифицированным, а не предположительным. Затем запрос на C4-диаграмму — и модель анализирует импорты и HTTP-клиенты, чтобы построить контейнерную схему с реальными связями между сервисами. На выходе — полный пакет: требования с трассируемостью до кода, диаграммы и черновики ADR.

Что получилось в сухом остатке:

  • 12 функциональных требований — от «применить промокод» до «обработать частичный возврат средств», каждое с привязкой к файлу и строке.
  • 5 нефункциональных требований — включая «таймаут платёжного шлюза ≤ 30 секунд» (из application.yml) и «повторные попытки при ошибке: 3 раза с экспоненциальной задержкой» (из @Retryable с maxAttempts=3).
  • C4 Container диаграмма с шестью компонентами и реальными связями.
  • 3 ADR-черновика — включая решение использовать внешний платёжный шлюз как единую точку входа для всех платежей.
  • 2 конфликта — например, требование «скидка не применяется к товарам со статусом “распродажа”» противоречило логике в другом сервисе, которая проверяла только цену, но не статус товара.

Время: 35 минут работы аналитика (10 — настройка промптов, 15 — прогон кода через LLM, 10 — валидация и уточнения). Без LLM ручной разбор 12 тысяч строк занял бы 3–4 дня. Это не silver bullet — половину сгенерированных требований всё равно пришлось уточнять у команды. Но теперь у команды есть что уточнять, а не просто белый лист с заголовком «Требования к модулю оплаты».

Что LLM делает плохо (и зачем всё равно нужен аналитик)

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

Давность кода. LLM не различает живой код и мёртвый. Если в репозитории лежит OldPaymentService.java с аннотацией @Deprecated — модель честно извлечёт из него требования. Вам придётся вручную отсеивать устаревшие модули. Это не баг, но про это забывают.

Код, оставленный «на будущее». Комментарий // TODO: implement fraud detection — не требование. Но LLM, увидев пустой метод с многообещающим названием, может решить, что это часть системы. Фильтруйте TODO, FIXME и заглушки.

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

Бизнес-контекст за пределами кода. Требование «оплата должна приниматься в долларах» может быть в коде потому, что бизнес когда-то планировал выход на международный рынок — а может быть потому, что разработчик скопировал пример из Stack Overflow и не поменял валюту. Без доменного контекста разница неочевидна, и модель здесь бесполезна.

Таблица: типичные находки в легаси и что с ними делать

Что модель нашла в кодеЧто это значит на самом делеЧто делать аналитику
@Retryable(maxAttempts=3, backoff=@Backoff(delay=2000))Требование к надёжности интеграцииЗафиксировать как NFR, уточнить у архитектора
if (order.amount > 100_000) { requireApproval(); }Бизнес-правило: лимит на автоматическое одобрениеЗафиксировать как BR/FR
// TODO: implement cachingНезавершённая задача, не требованиеИгнорировать
@Deprecated на целом классеМёртвый код, который боятся удалитьИсключить из анализа
sendToQueue("payment.events", event)Событийная архитектура, асинхронная связьЗафиксировать как архитектурное решение в ADR
connection-timeout: 30000 в конфигеНФТ: максимальное время ожидания ответаЗафиксировать как NFR с метрикой
Два контроллера для одного эндпоинтаАрхитектурный дрейф, один устарелСравнить с git blame, оставить актуальный
VARCHAR(255) с комментарием PCI DSSРегуляторное ограничение на хранение данныхNFR: безопасность, регуляторное требование

Заключение

Легаси без документации — это не приговор. Это задача, в которой можно заменить три недели ручного чтения кода на час работы с LLM и день валидации результатов. Магии нет — модель не напишет за вас идеальную документацию с первой попытки. Но она прочитает 12 тысяч строк кода, найдёт все if, конфиги и аннотации и разложит их по шаблонам. А вы проверите, что из этого бизнес-логика, что — мёртвый код, а что — технический долг.

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

P.S. Если у вас в проекте 50 тысяч строк легаси, а начальство говорит «неделя на реверс-инжиниринг» — не спорьте. Просто запустите LLM. Через день вы принесёте им 40 требований, 5 диаграмм и список архитектурных решений — и спросите, чем занять оставшиеся четыре дня.