Все статьи

12 октября 2026 г.

Как избежать циклических зависимостей в 1С с помощью графа: практический гид по Grafon

Почему цикл в графе зависимостей 1С — это дорого

Циклическая зависимость в конфигурации 1С — это ситуация, когда узел A зависит от B, B зависит от C, а C снова зависит от A. В типовых ERP, УТ, ЗУП и КА такие циклы прорастают годами: их создают доработки, общие модули, подписки на события, формы и обработчики объектов. Проблема не в том, что «1С не запустится», а в том, что:

  • растёт время сборки и перепроведения документов из-за каскадных вызовов;
  • любое изменение в модуле A «тянет» пересборку полконфигурации;
  • ИИ-ассистент, которому скормили весь модуль целиком, тонет в сотнях килобайт BSL и начинает галлюцинировать.

Граф зависимостей — это формальная модель, поверх которой задачу «найти и разорвать цикл» можно решать алгоритмически, а не глазами. Именно эту модель строит локальный MCP-сервер Grafon.

Что такое граф зависимостей конфигурации 1С

Граф 1С — это ориентированный граф G = (V, E), где:

Узлы (V)

  • объекты метаданных: Документ, Справочник, Регистр, ПланВидовХарактеристик;
  • модули: ОбщийМодуль.Продажи, Документ.РеализацияТоваров.МодульОбъекта, Форма.РеализацияТоваров.ФормаДокумента;
  • процедуры и функции BSL с сигнатурами;
  • подписки на события и обработчики;
  • запросы к БД и используемые в них таблицы.

Рёбра (E)

  • вызов Функция → Функция внутри BSL;
  • обращение к метаданным через Ссылка, Документы.РеализацияТоваров, РегистрыНакопления.ТоварыНаСкладах;
  • #Вставка / #Область между модулями;
  • регистрация подписки: Подписка.ПередЗаписьюДокумента → Обработчик;
  • чтение/запись реквизитов и табличных частей.

Цикл в таком графе — это контур A → B → C → A. Графон находит контуры за доли секунды, даже если в конфигурации сотни тысяч рёбер.

Как выглядит цикл в BSL: три реальных примера

Пример 1. Два общих модуля

// ОбщийМодуль.Себестоимость
Функция Рассчитать(Ссылка) Экспорт
    Курс = СкладскиеОперации.ПолучитьКурс(Ссылка); // ребро A → B
    Возврат Курс * ПолучитьКоличество(Ссылка);
КонецФункции
// ОбщийМодуль.СкладскиеОперации
Функция ПолучитьКурс(Ссылка) Экспорт
    База = Себестоимость.ПолучитьБазу(Ссылка); // ребро B → A — цикл
    Возврат База.Курс;
КонецФункции
// ОбщийМодуль.Себестоимость
Функция ПолучитьБазу(Ссылка) Экспорт
    Возврат РегистрыСведений.КурсыВалют.ПолучитьПоследний(Ссылка);
КонецФункции

Формально компилятор 1С такое пропустит, но при вызове Рассчитать() получите либо рекурсию, либо неявное зацикливание бизнес-логики.

Пример 2. Подписка на событие как источник цикла

// Подписка "ПриЗаписиРеализацииТоваров"
Процедура ПриЗаписиДокумента(Источник, Отказ) Экспорт
    Уведомления.ПроверитьЗадолженность(Источник); // C
КонецПроцедуры

Внутри Уведомления.ПроверитьЗадолженность выполняется Записать() по тому же документу — и снова срабатывает подписка. Граф показывает это как цикл Подписка → Обработчик → ЗаписьОбъекта → Подписка.

Пример 3. Объект → менеджер → объект

// МодульОбъекта.РеализацияТоваров
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    ПроверитьОстатки(); // вызов менеджера
КонецПроцедуры
// Менеджер.РеализацияТоваров
Процедура ПроверитьОстатки() Экспорт
    Док = Документы.РеализацияТоваров.СоздатьДокумент(); // снова объект того же типа
КонецПроцедуры

Архитектура Графона: как граф ускоряет ИИ-агента

Grafon разворачивается локально и индексирует выгрузку конфигурации в XML/EDT. Схема работы:

  1. Индексатор парсит метаданные и BSL, строит граф в локальном сторе (SQLite/граф).
  2. MCP-сервер поднимает протокол Model Context Protocol на localhost.
  3. ИИ-агент (Claude Code, Cursor, Windsurf, Roo Code) подключается к MCP и вызывает инструменты.
  4. Агент получает только срез — конкретные функции, сигнатуры, путь цикла, а не всю конфигурацию.

Ключевое отличие от «скормить весь проект»: агент видит граф как навигационную карту и запрашивает ровно те узлы, которые ему нужны для задачи.

Практика: находим циклы через MCP

Запрос на поиск всех контуров в общих модулях:

{
  "tool": "grafon.find_cycles",
  "arguments": {
    "scope": ["CommonModules", "ObjectModules.ManagerModules"],
    "max_depth": 8,
    "min_weight": 1
  }
}

Ответ Графона (фрагмент):

{
  "cycles": [
    {
      "path": [
        "CommonModule.Себестоимость.Рассчитать",
        "CommonModule.СкладскиеОперации.ПолучитьКурс",
        "CommonModule.Себестоимость.ПолучитьБазу",
        "CommonModule.Себестоимость.Рассчитать"
      ],
      "weight": 3,
      "files": ["CommonModules/Себестоимость/Module.bsl", "CommonModules/СкладскиеОперации/Module.bsl"]
    }
  ]
}

Запрос зависимостей отдельного узла:

{
  "tool": "grafon.get_dependencies",
  "arguments": {
    "node": "CommonModule.Продажи",
    "direction": "both",
    "depth": 3,
    "include_signatures": true
  }
}

Ответ — компактный JSON с сигнатурами, без тел функций. Именно этот «сжатый срез» агент подставляет в контекст вместо 800 КБ модулей.

Как разорвать цикл: рабочий алгоритм

  1. Найти контур через grafon.find_cycles.
  2. Определить точку разрыва — узел с минимальным числом внешних зависимостей (обычно это «функция-слуга» вроде ПолучитьКурс).
  3. Инвертировать зависимость через интерфейс или общий модуль-фасад.
  4. Проверить повторным запуском find_cycles.

Пример рефакторинга цикла из Примера 1:

// ОбщийМодуль.КурсыФасад — новый слой без обратных вызовов
Функция ПолучитьКурсДляСебестоимости(Ссылка) Экспорт
    Возврат РегистрыСведений.КурсыВалют.ПолучитьПоследний(Ссылка);
КонецФункции
// ОбщийМодуль.Себестоимость
Функция Рассчитать(Ссылка) Экспорт
    Курс = КурсыФасад.ПолучитьКурсДляСебестоимости(Ссылка);
    Возврат Курс * ПолучитьКоличество(Ссылка);
КонецФункции

Теперь рёбра графа Себестоимость → КурсыФасад и СкладскиеОперации → КурсыФасад, но обратных вызовов нет — контур разорван.

Сравнение: с Графоном и без

| Метрика | Без Графона (сырой контекст) | С Графоном (MCP-срез) |
|---|---|---|
| Токенов на задачу «найти циклы» | 180–260 тыс. | 12–25 тыс. |
| Время ответа агента | 3–6 мин | 20–40 сек |
| Галлюцинации методов | Частые | Практически отсутствуют |
| Точность пути цикла | Ручной анализ | Машинный JSON |
| Работа с ERP 2.5 (гигабайты XML) | Падает по контексту | Стабильно |

Экономия — до 10 раз по токенам и более чем в 5 раз по времени на типовых задачах рефакторинга архитектуры.

Практические сценарии

  • Аудит перед обновлением типовой. Перед переходом на новый релиз ERP запустите find_cycles — получите список мест, где ваши доработки конфликтуют с новыми механизмами.
  • Code review ИИ-агентом. Cursor или Claude Code через Grafon смотрят только изменённые узлы графа и их соседей, а не всю конфигурацию.
  • Онбординг нового разработчика. Граф в визуализации — карта конфигурации за несколько минут вместо чтения сотен модулей.
  • Контроль архитектуры в CI. Регулярный запуск find_cycles и find_dead_code в pipeline блокирует коммиты с новыми циклами.

Чек-лист: как не создать цикл в 1С

  1. Общие модули делить по слоям: домен → сервисы → фасады. Рёбра разрешены только «сверху вниз».
  2. Никогда не вызывать из модуля объекта менеджер того же объекта.
  3. В подписках на события использовать проверку ОбменДанными.Загрузка и не вызывать Записать() повторно.
  4. Вместо взаимных вызовов A ↔ B вводить третий модуль-фасад.
  5. Каждый новый общий модуль проверять через grafon.get_dependencies до коммита.

Граф зависимостей — это не академическое упражнение, а рабочий инструмент. В связке с Grafon и MCP-агентами он превращает поиск циклических зависимостей 1С из многодневного расследования в секундную операцию — и одновременно экономит 90% бюджета токенов, которые обычно сгорают на переполнение контекста.