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

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

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

Что такое рекурсия и как она работает в 1С

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

Основные компоненты рекурсивной функции:

  • 🔹 Базовый случай — условие, при котором рекурсия прекращается. Без него функция будет вызывать себя бесконечно, что приведёт к переполнению стека и ошибке.
  • 🔹 Рекурсивный случай — часть кода, где функция вызывает саму себя с изменёнными параметрами, приближаясь к базовому случаю.
  • 🔹 Параметры — данные, которые передаются между вызовами и модифицируются для достижения базового случая.

В 1С 8.3 рекурсия поддерживается на уровне языка, но имеет ограничения, связанные с глубиной стека вызовов (по умолчанию — около 1000 уровней). Превышение этого лимита приводит к исключению Переполнение стека. Например, при обходе глубокой иерархии справочников может потребоваться увеличение этого параметра через настройки конфигуратора.

📊 Как часто вы используете рекурсию в 1С?
Часто, это мой основной инструмент
Иногда, для специфических задач
Рядом не стоял, боюсь
Не знаю, что это такое

Простейший пример рекурсии — расчёт факториала числа. В математической нотации факториал числа n (обозначается как n!) равен произведению всех целых чисел от 1 до n. Рекурсивное решение этой задачи в выглядит так:


Функция Факториал(Число) Экспорт

Если Число = 1 Тогда

Возврат 1; // Базовый случай

Иначе

Возврат Число * Факториал(Число - 1); // Рекурсивный вызов

КонецЕсли;

КонецФункции

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

Где применяется рекурсия в 1С: реальные примеры

Рекурсия в 1С:Предприятие находит применение в следующих сценариях:

  • 📁 Обход иерархических справочников: построение деревьев подчинённости, расчёт итогов по группам, поиск элементов по всему дереву.
  • 📄 Обработка вложенных документов: например, расчёт себестоимости по многоуровневым спецификациям или обход цепочки связанных заказов.
  • 📊 Формирование отчётов с группировками: когда данные имеют вложенную структуру (например, отчёт по подразделениям с подчиненными отделами).
  • 🔄 Рекурсивные алгоритмы обработки: поиск циклов в графах, обход лабиринтов (например, в задачах логистики).
  • 🔧 Работа с XML/JSON: парсинг вложенных структур данных, когда глубина вложенности заранее неизвестна.

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


Процедура ОбойтиНоменклатуру(Группа, МассивРезультатов) Экспорт

// Получаем все элементы текущей группы

Выборка = Справочники.Номенклатура.Выбрать(,, Группа);

Пока Выборка.Следующий() Цикл

Если Выборка.ЭтоГруппа Тогда

ОбойтиНоменклатуру(Выборка.Ссылка, МассивРезультатов); // Рекурсия для вложенных групп

Иначе

МассивРезультатов.Добавить(Выборка.Ссылка);

КонецЕсли;

КонецЦикла;

КонецПроцедуры

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

💡

При обходе больших справочников (тысячи элементов) рекурсия может привести к переполнению стека. В таких случаях лучше использовать итеративный подход с очередью (стеком) для хранения обрабатываемых узлов.

Типичные ошибки при работе с рекурсией в 1С

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

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

    Функция БесконечнаяРекурсия(Параметр)

    Возврат БесконечнаяРекурсия(Параметр + 1); // Нет условия выхода!

    КонецФункции

  2. Неправильная модификация параметров — если параметры не изменяются или изменяются некорректно, рекурсия никогда не достигнет базового случая. Например, вместо уменьшения параметра его увеличивают.
  3. Чрезмерная глубина рекурсии — в по умолчанию ограничение на глубину стека вызовов составляет ~1000 уровней. При обходе глубоких структур (например, справочник с 2000 уровней вложенности) это приведёт к ошибке.
  4. Потеря контекста — при рекурсивных вызовах легко забыть, какие данные передаются между уровнями, что приводит к некорректным результатам.
  5. Игнорирование производительности — рекурсия может быть менее эффективной, чем итеративные алгоритмы, особенно при работе с большими объёмами данных.

Пример ошибки с отсутствием базового случая:


Функция СуммаЧисел(Число)

Возврат Число + СуммаЧисел(Число - 1); // Зацикливание, если Число > 0

КонецФункции

Исправленная версия:


Функция СуммаЧисел(Число)

Если Число = 0 Тогда

Возврат 0; // Базовый случай

Иначе

Возврат Число + СуммаЧисел(Число - 1);

КонецЕсли;

КонецФункции

💡

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

Рекурсия vs итерация: что выбрать в 1С

Рекурсия — не всегда оптимальное решение. В некоторых случаях итеративные алгоритмы (с использованием циклов) оказываются более эффективными. Давайте сравним оба подхода:

Критерий Рекурсия Итерация
Читаемость кода Часто более наглядна для иерархических задач (например, обход деревьев) Может быть менее интуитивной для вложенных структур
Производительность Менее эффективна из-за накладных расходов на вызов функции Как правило, быстрее, особенно для больших объёмов данных
Память Потребляет больше памяти (стек вызовов) Более экономна, так как не требует сохранения контекста
Ограничения Ограничена глубиной стека (~1000 уровней в 1С) Ограничена только ресурсами системы
Типичные задачи Обход деревьев, рекурсивные алгоритмы (например, быстрая сортировка) Линейная обработка данных, простые циклы

Пример задачи, где итерация предпочтительнее: суммирование элементов массива. Рекурсивное решение:


Функция СуммаМассиваРекурсия(Массив, Индекс = 0)

Если Индекс >= Массив.ВГраница() Тогда

Возврат 0;

Иначе

Возврат Массив[Индекс] + СуммаМассиваРекурсия(Массив, Индекс + 1);

КонецЕсли;

КонецФункции

Итеративное решение:


Функция СуммаМассиваИтерация(Массив)

Сумма = 0;

Для Каждого Элемент Из Массив Цикл

Сумма = Сумма + Элемент;

КонецЦикла;

Возврат Сумма;

КонецФункции

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

Когда рекурсия опасна в 1С?

При работе с большими объёмами данных (тысячи записей) рекурсия может привести к переполнению стека или значительному замедлению. Например, обход справочника с 5000 групп и 50 000 элементов рекурсивно может занять в десятки раз больше времени, чем итеративно. В таких случаях лучше использовать циклы с явным управлением стеком (например, через Массив или Структура для хранения текущего состояния).

Оптимизация рекурсивных алгоритмов в 1С

Если вы решили использовать рекурсию, её можно оптимизировать следующими способами:

  • Мемоизация — кэширование результатов вычислений для избежания повторных рекурсивных вызовов с теми же параметрами. Особенно полезно для задач с перекрывающимися подзадачами (например, числа Фибоначчи).
  • 🔄 Хвостовая рекурсия — оптимизация, при которой рекурсивный вызов является последней операцией в функции. В некоторых языках это позволяет компилятору преобразовать рекурсию в цикл, но в такая оптимизация не реализована.
  • 📉 Ограничение глубины — если глубина рекурсии заранее известна, можно добавить проверку на максимальное количество уровней.
  • 🗃️ Использование вспомогательных структур — например, передача аккумулятора (накопленного результата) в параметрах функции для уменьшения количества операций.

Пример мемоизации для функции Фибоначчи:


Перем мКэшФибоначчи;

Функция Фибоначчи(н) Экспорт

Если мКэшФибоначчи = Неопределено Тогда

мКэшФибоначчи = Новый Соответствие;

КонецЕсли;

Если мКэшФибоначчи.СодержитКлюч(н) Тогда

Возврат мКэшФибоначчи[н];

КонецЕсли;

Если н <= 1 Тогда

Возврат н;

Иначе

Результат = Фибоначчи(н - 1) + Фибоначчи(н - 2);

мКэшФибоначчи.Вставить(н, Результат);

Возврат Результат;

КонецЕсли;

КонецФункции

Без мемоизации вычисление Фибоначчи(40) может занять несколько секунд, а с кэшированием — доли секунды.

Базовый случай обработан корректно

Параметры изменяются при каждом вызове

Нет риска переполнения стека (глубина < 1000)

Кэширование применено там, где это уместно

Тестирование на крайние случаи (пустые данные, большие объёмы)-->

Рекурсия в отчётах и обработках 1С

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

Пример формирования строки отчёта с учётом иерархии:


Процедура ДобавитьСтрокиОтчета(Группа, Уровень = 0)

Выборка = Справочники.Подразделения.Выбрать(,, Группа);

Пока Выборка.Следующий() Цикл

Если Выборка.ЭтоГруппа Тогда

СтрокиОтчета.Добавить();

СтрокиОтчета[СтрокиОтчета.Количество() - 1].Уровень = Уровень;

СтрокиОтчета[СтрокиОтчета.Количество() - 1].Наименование = Повтор(" ", Уровень) + Выборка.Наименование;

ДобавитьСтрокиОтчета(Выборка.Ссылка, Уровень + 1); // Рекурсия для вложенных групп

Иначе

СтрокиОтчета.Добавить();

СтрокиОтчета[СтрокиОтчета.Количество() - 1].Уровень = Уровень;

СтрокиОтчета[СтрокиОтчета.Количество() - 1].Наименование = Повтор(" ", Уровень) + Выборка.Наименование;

КонецЕсли;

КонецЦикла;

КонецПроцедуры

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

Ещё один пример — рекурсивный расчёт итогов по группам номенклатуры. Допустим, нам нужно посчитать суммарный остаток по группе и всем её подгруппам:


Функция РассчитатьОстатокПоГруппе(Группа)

Остаток = 0;

Выборка = РегистрыНакопления.ОстаткиНоменклатуры.ВыбратьОстатки(Группа);

Пока Выборка.Следующий() Цикл

Остаток = Остаток + Выборка.КоличествоОстаток;

КонецЦикла;

// Рекурсия для подгрупп

ВыборкаГрупп = Справочники.Номенклатура.ВыбратьГруппы(Группа);

Пока ВыборкаГрупп.Следующий() Цикл

Остаток = Остаток + РассчитатьОстатокПоГруппе(ВыборкаГрупп.Ссылка);

КонецЦикла;

Возврат Остаток;

КонецФункции

💡

При формировании отчётов с рекурсией всегда учитывайте ограничения платформы 1С на количество строк в таблице значений (по умолчанию — 1 000 000). Для больших отчётов используйте постраничную обработку.

Рекурсия в обмене данными и интеграциях

Рекурсия также полезна при работе с внешними системами, когда данные имеют вложенную структуру. Например, при обмене с 1С:УТ и 1С:БП через УниверсальныйФорматОбменаДанными или при парсинге JSON/XML-ответов от веб-сервисов.

Пример рекурсивного обхода JSON-структуры:


Процедура ОбработатьJSON(Данные, Путь = "")

Если ТипЗнч(Данные) = Тип("Структура") Тогда

Для Каждого Ключ Из Данные Цикл

НовыйПуть = Путь + " -> " + Ключ;

ОбработатьJSON(Данные[Ключ], НовыйПуть); // Рекурсия для вложенных объектов

КонецЦикла;

ИначеЕсли ТипЗнч(Данные) = Тип("Массив") Тогда

Для Инд = 0 По Данные.ВГраница() Цикл

ОбработатьJSON(Данные[Инд], Путь + "[" + Инд + "]");

КонецЦикла;

Иначе

Сообщить(Путь + ": " + Данные);

КонецЕсли;

КонецПроцедуры

Этот код рекурсивно обходит все уровни JSON-объекта, выводя путь к каждому значению. Аналогичный подход применяется при разборе XML-документов с вложенными узлами.

При интеграции с другими системами рекурсия помогает:

  • 🔗 Синхронизировать иерархические справочники (например, номенклатуру с категориями из Bitrix24 или MoySklad).
  • 📦 Обрабатывать вложенные документы (например, заказы с позициями, которые сами являются комплектами).
  • 📡 Формировать сложные запросы к API, где ответ содержит ссылки на другие ресурсы.
💡

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

FAQ: ответы на частые вопросы о рекурсии в 1С

Можно ли в 1С сделать бесконечную рекурсию?

Технически да, но платформа 1С:Предприятие ограничивает глубину стека вызовов (по умолчанию ~1000 уровней). При превышении этого лимита возникает ошибка Переполнение стека. Чтобы увеличить лимит, можно изменить параметр запуска /StackSize в файле 1CEStart.cfg, но это требует прав администратора и не всегда безопасно.

Как отладить рекурсивную функцию в 1С?

Для отладки рекурсии в используйте:

  1. Пошаговый режим выполнения (F11) с наблюдением за изменением параметров.
  2. Вывод отладочной информации в окно сообщений (Сообщить()) на каждом уровне рекурсии.
  3. Проверку тестовыми данными с известным результатом (например, справочник с 2–3 уровнями вложенности).
  4. Инструмент Профайлер в конфигураторе для анализа производительности.

Пример отладочного вывода:

Сообщить("Уровень " + Уровень + ", группа: " + Группа.Наименование);
В каких случаях рекурсия в 1С работает медленно?

Рекурсия может тормозить в следующих сценариях:

  • 🐢 Глубокая вложенность (тысячи уровней) — каждый вызов функции добавляет накладные расходы.
  • 🗄️ Большой объём данных на каждом уровне (например, обход справочника с миллионом элементов).
  • 🔄 Повторные вычисления одних и тех же значений (решается мемоизацией).
  • 🖥️ Недостаток памяти — рекурсия потребляет больше ресурсов, чем циклы.

В таких случаях замените рекурсию на итеративный алгоритм с явным управлением стеком (например, через Массив или Очередь).

Как обойти ограничение на глубину рекурсии в 1С?

Если вам нужно обработать структуру с глубиной вложенности больше 1000, используйте:

  1. Итеративный подход — замените рекурсию циклом с собственным стеком (например, через Массив).
  2. Разбиение задачи — обрабатывайте уровни по частям, сохраняя промежуточные результаты.
  3. Увеличение стека — измените параметр /StackSize в файле запуска (требует прав администратора).

Пример итеративного обхода дерева:


Процедура ОбойтиИтеративно(Корень)

Стек = Новый Массив;

Стек.Добавить(Корень);

Пока Стек.Количество() > 0 Цикл

Текущий = Стек.Удалить(0);

// Обработка текущего узла

Если Текущий.ЭтоГруппа Тогда

Выборка = Справочники.Номенклатура.Выбрать(,, Текущий);

Пока Выборка.Следующий() Цикл

Стек.Добавить(Выборка.Ссылка);

КонецЦикла;

КонецЕсли;

КонецЦикла;

КонецПроцедуры

Можно ли использовать рекурсию в запросах 1С?

Нет, в языке запросов рекурсия напрямую не поддерживается. Однако вы можете:

  1. Использовать временные таблицы для пошаговой обработки иерархий.
  2. Применять рекурсивные функции на встроенном языке для постобработки результатов запроса.
  3. В 1С:Предприятие 8.3.20+ появилась поддержка рекурсивных ОБЪЕДИНЕНИЕ ВСЕ (рекурсивные UNION ALL), но синтаксис отличается от стандартного SQL.

Пример рекурсивного запроса (для версий 8.3.20+):


Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Подразделения.Ссылка КАК Ссылка,

| Подразделения.Наименование КАК Наименование,

| 0 КАК Уровень

|ПОМЕСТИТЬ ВТ_Подразделения

|ИЗ

| Справочник.Подразделения КАК Подразделения

|ГДЕ

| Подразделения.Родитель = &Корень

|

|ОБЪЕДИНИТЬ ВСЕ

|

|ВЫБРАТЬ

| Подразделения.Ссылка КАК Ссылка,

| Подразделения.Наименование КАК Наименование,

| ВТ_Подразделения.Уровень + 1 КАК Уровень

|ИЗ

| Справочник.Подразделения КАК Подразделения

| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Подразделения КАК ВТ_Подразделения

| ПО Подразделения.Родитель = ВТ_Подразделения.Ссылка";