К статье
Практический материал

Разработка и Конфигурация · 12 мин

Что такое рекурсия в 1С и как её использовать

Что такое рекурсия в 1С: принцип работы рекурсивных процедур и функций, примеры кода, ограничения стека, типичные ошибки и альтернатива через цикл.

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

Классическая задача, где рекурсия незаменима, — обход дерева неизвестной глубины. Например, справочник «Номенклатура» с произвольным числом уровней вложенности групп, структура подчинённости подразделений или дерево значений на форме. Циклом «пока» такие структуры обходятся громоздко, а рекурсивная функция описывает логику в несколько строк: обработать текущий элемент, затем вызвать себя для каждого дочернего элемента.

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

Как устроена рекурсия в языке 1С

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

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

Простейший учебный пример — вычисление факториала числа:

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

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

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

КонецЕсли;

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

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

Здесь базовый случай — число меньше или равно единице, а рекурсивный шаг уменьшает аргумент на единицу, гарантируя достижение выхода. На практике в прикладных решениях факториал почти не нужен, но структура «проверка выхода → обработка → вызов себя с новыми данными» повторяется во всех рекурсивных алгоритмах 1С.

Типовая задача: обход иерархического справочника

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

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

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

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

"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.ЭтоГруппа

|ИЗ Справочник.Номенклатура КАК Номенклатура

|ГДЕ Номенклатура.Родитель = &Родитель";

Запрос.УстановитьПараметр("Родитель", Группа);

Выборка = Запрос.Выполнить().Выбрать();

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

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

ОбойтиГруппу(Выборка.Ссылка, МассивРезультат);

Иначе

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

КонецЕсли;

КонецЦикла;

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

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

Обход дерева значений и структур на форме

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

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

Для Каждого Строка Из Строки Цикл

// обработка текущей строки

ОбработатьСтроку(Строка, Уровень);

// рекурсивный обход подчинённых строк

ОбработатьСтрокиДерева(Строка.ПолучитьЭлементы(), Уровень + 1);

КонецЦикла;

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

Параметр «Уровень» здесь выполняет две функции: позволяет учитывать глубину вложенности при обработке (например, для отступов при выводе) и может служить аварийным ограничителем — если уровень превысил разумный порог, обход можно прервать принудительно. Такой защитный счётчик полезен при обходе данных, которые приходят извне и чья структура не контролируется вашим кодом.

Аналогично строится рекурсивный разбор вложенных структур и соответствий — например, при обработке данных, полученных из JSON или при обходе настроек произвольной глубины. Ключевой принцип тот же: обработать текущий уровень, затем для каждого вложенного объекта вызвать ту же функцию.

Как написать корректную рекурсивную функцию
  1. 1
    Определите базовый случай — условие, при котором вызовов себя не будет
  2. 2
    Убедитесь, что каждый рекурсивный вызов изменяет данные в сторону базового случая
  3. 3
    Напишите обработку текущего уровня до или после рекурсивного вызова — в зависимости от задачи
  4. 4
    Добавьте защитный счётчик глубины, если структура данных не контролируется
  5. 5
    Проверьте функцию на минимальном, среднем и глубоко вложенном наборе данных

Ограничения: стек вызовов и производительность

Главное техническое ограничение рекурсии в 1С — глубина стека вызовов. Платформа ограничивает количество вложенных вызовов, и при превышении лимита выполнение прерывается ошибкой «Переполнение стека». Точный предел зависит от версии платформы, режима исполнения и объёма локальных данных в каждом вызове, но практический вывод один: рекурсия не подходит для обработки структур с потенциально очень большой глубиной вложенности.

Второй аспект — производительность. Каждый вызов несёт накладные расходы на создание кадра стека, а если внутри вызова выполняется запрос к базе (как в примере со справочником), стоимость умножается. Для иерархий в десятки уровней это незаметно, но при массовой обработке в цикле проведения документов или обмене данными разница становится существенной.

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

Когда рекурсию лучше заменить

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

Рекурсия и её альтернативы

Рекурсивная процедура

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

Запрос с В ИЕРАРХИИ

Лучший выбор для выборки элементов иерархического справочника из базы. Одно обращение к СУБД вместо множества запросов.

Итоги запроса по иерархии (ИТОГИ ... ИЕРАРХИЯ)

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

Цикл с явным стеком (массив или соответствие)

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

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

Процедура ОбойтиБезРекурсии(КорневыеСтроки)

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

Для Каждого Строка Из КорневыеСтроки Цикл

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

КонецЦикла;

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

Текущая = Стек[Стек.Количество() - 1];

Стек.Удалить(Стек.Количество() - 1);

ОбработатьСтроку(Текущая);

Для Каждого Подчиненная Из Текущая.ПолучитьЭлементы() Цикл

Стек.Добавить(Подчиненная);

КонецЦикла;

КонецЦикла;

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

Типичные ошибки при использовании рекурсии

Большинство проблем с рекурсией сводится к нескольким повторяющимся ошибкам, которые легко предотвратить на этапе написания кода.

Типичные ошибки
01
Отсутствие условия выхода — функция вызывает себя бесконечно, сеанс падает с переполнением стека
02
Условие выхода недостижимо: параметр не изменяется или изменяется в обратную сторону
03
Запрос к базе внутри каждого вызова там, где хватило бы одного запроса с В ИЕРАРХИИ — резкое падение производительности
04
Игнорирование циклических ссылок в данных — зацикливание обхода даже при корректном условии выхода
05
Использование рекурсии для плоских коллекций, где достаточно обычного цикла — избыточная сложность кода

Отдельно отметим тонкую ошибку с экспортными переменными и параметрами по ссылке. Если рекурсивная процедура накапливает результат в переменной модуля или параметре, переданном по значению, часть данных может теряться между уровнями вызовов. Надёжный шаблон — передавать накапливающую коллекцию (массив, соответствие) параметром: объекты в 1С передаются по ссылке, и все уровни рекурсии работают с одним экземпляром.

Контрольные точки

Как проверить рекурсивный код

Поведение на минимальных данных
Норма

Один элемент без вложенности обрабатывается корректно, рекурсивного вызова нет

Тревожный признак

Ошибка или лишний вызов уже на первом элементе — условие выхода неверно

Поведение на глубокой вложенности
Норма

Обход завершается, результат полный

Тревожный признак

Переполнение стека или потеря части веток

Защита от циклов
Норма

Зацикленные данные обнаруживаются, обход прерывается контролируемо

Тревожный признак

Код зависает или падает на кольцевой ссылке

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

Чем рекурсия отличается от цикла в 1С?

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

Какая максимальная глубина рекурсии допустима в 1С?

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

Как получить все элементы группы справочника без рекурсии?

Используйте запрос с оператором В ИЕРАРХИИ: «ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура ГДЕ Ссылка В ИЕРАРХИИ(&Группа)». Запрос вернёт саму группу и все вложенные элементы любой глубины одним обращением к базе — это быстрее и надёжнее рекурсивного перебора.

Что делать при ошибке «Переполнение стека»?

Сначала проверьте условие выхода: чаще всего оно отсутствует или недостижимо. Затем убедитесь, что в данных нет циклических ссылок. Если логика корректна, а глубина объективно велика — перепишите обход на цикл с массивом в роли стека либо перенесите выборку на уровень запроса.

Можно ли вызывать рекурсивно процедуру общего модуля?

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

Когда рекурсия действительно оправдана в прикладном коде?

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

Итог

Что важно запомнить

Рекурсия в 1С — это самовызов процедуры или функции с обязательным условием выхода. Она идеальна для обхода деревьев и иерархий умеренной глубины, но ограничена стеком и уязвима к циклическим ссылкам. Для выборок из иерархических справочников предпочитайте запрос с В ИЕРАРХИИ, а для очень глубоких структур — цикл с явным стеком.