Разработка и Конфигурация · 12 мин
Что такое рекурсия в 1С и как её использовать
Что такое рекурсия в 1С: принцип работы рекурсивных процедур и функций, примеры кода, ограничения стека, типичные ошибки и альтернатива через цикл.
Рекурсия в 1С — это приём программирования, при котором процедура или функция вызывает саму себя для решения задачи, разбивая её на более мелкие однотипные подзадачи. Встроенный язык 1С полностью поддерживает рекурсивные вызовы, и этот механизм активно применяется при обходе иерархических справочников, разборе древовидных структур, обработке группировок и вычислениях, где результат зависит от результата той же операции на вложенном уровне.
Классическая задача, где рекурсия незаменима, — обход дерева неизвестной глубины. Например, справочник «Номенклатура» с произвольным числом уровней вложенности групп, структура подчинённости подразделений или дерево значений на форме. Циклом «пока» такие структуры обходятся громоздко, а рекурсивная функция описывает логику в несколько строк: обработать текущий элемент, затем вызвать себя для каждого дочернего элемента.
При этом рекурсия — инструмент с ценой. Каждый вызов занимает место в стеке вызовов, и при слишком глубокой вложенности платформа аварийно завершает выполнение с ошибкой переполнения стека. Поэтому рекурсивный код требует обязательного условия выхода и понимания ограничений, о которых рассказано ниже.
Как устроена рекурсия в языке 1С
Любая рекурсивная функция состоит из двух обязательных частей. Первая — базовый случай (условие выхода): ситуация, при которой функция возвращает результат без повторного вызова себя. Вторая — рекурсивный шаг: вызов той же функции с изменёнными параметрами, которые приближают выполнение к базовому случаю. Если условие выхода отсутствует или недостижимо, вызовы продолжаются до переполнения стека.
Каждый вызов функции создаёт новый кадр в стеке: платформа запоминает локальные переменные, параметры и точку возврата. Когда вложенный вызов завершается, управление возвращается на уровень выше, и выполнение продолжается с места вызова. Это позволяет «собирать» результат снизу вверх — например, суммировать значения по всем уровням дерева, возвращая частичные итоги из каждого вызова.
Простейший учебный пример — вычисление факториала числа:
Функция Факториал(Число)
Если Число <= 1 Тогда
Возврат 1; // базовый случай
КонецЕсли;
Возврат Число * Факториал(Число - 1); // рекурсивный шаг
КонецФункции
Здесь базовый случай — число меньше или равно единице, а рекурсивный шаг уменьшает аргумент на единицу, гарантируя достижение выхода. На практике в прикладных решениях факториал почти не нужен, но структура «проверка выхода → обработка → вызов себя с новыми данными» повторяется во всех рекурсивных алгоритмах 1С.
Типовая задача: обход иерархического справочника
Самый распространённый сценарий рекурсии в реальных конфигурациях — обход дерева групп справочника. Допустим, нужно собрать всю номенклатуру, входящую в указанную группу со всеми подгруппами. Рекурсивная процедура получает группу, обрабатывает её элементы и вызывает себя для каждой подчинённой группы:
Процедура ОбойтиГруппу(Группа, МассивРезультат)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.ЭтоГруппа
|ИЗ Справочник.Номенклатура КАК Номенклатура
|ГДЕ Номенклатура.Родитель = &Родитель";
Запрос.УстановитьПараметр("Родитель", Группа);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Если Выборка.ЭтоГруппа Тогда
ОбойтиГруппу(Выборка.Ссылка, МассивРезультат);
Иначе
МассивРезультат.Добавить(Выборка.Ссылка);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Обратите внимание: условие выхода здесь неявное — рекурсия заканчивается, когда в группе не остаётся подчинённых групп. Цикл по выборке просто не выполняет рекурсивный вызов, и управление возвращается наверх. Это корректный, но не самый быстрый вариант: на каждую группу выполняется отдельный запрос, что при глубокой иерархии создаёт заметную нагрузку на сервер базы данных.
Обход дерева значений и структур на форме
Второй частый сценарий — обход дерева значений, размещённого на управляемой форме: например, дерева группировок, настроек или строк документа с подчинёнными строками. Здесь данные уже в памяти, запросы не нужны, и рекурсия работает быстро:
Процедура ОбработатьСтрокиДерева(Строки, Уровень = 0)
Для Каждого Строка Из Строки Цикл
// обработка текущей строки
ОбработатьСтроку(Строка, Уровень);
// рекурсивный обход подчинённых строк
ОбработатьСтрокиДерева(Строка.ПолучитьЭлементы(), Уровень + 1);
КонецЦикла;
КонецПроцедуры
Параметр «Уровень» здесь выполняет две функции: позволяет учитывать глубину вложенности при обработке (например, для отступов при выводе) и может служить аварийным ограничителем — если уровень превысил разумный порог, обход можно прервать принудительно. Такой защитный счётчик полезен при обходе данных, которые приходят извне и чья структура не контролируется вашим кодом.
Аналогично строится рекурсивный разбор вложенных структур и соответствий — например, при обработке данных, полученных из JSON или при обходе настроек произвольной глубины. Ключевой принцип тот же: обработать текущий уровень, затем для каждого вложенного объекта вызвать ту же функцию.
- 1Определите базовый случай — условие, при котором вызовов себя не будет
- 2Убедитесь, что каждый рекурсивный вызов изменяет данные в сторону базового случая
- 3Напишите обработку текущего уровня до или после рекурсивного вызова — в зависимости от задачи
- 4Добавьте защитный счётчик глубины, если структура данных не контролируется
- 5Проверьте функцию на минимальном, среднем и глубоко вложенном наборе данных
Ограничения: стек вызовов и производительность
Главное техническое ограничение рекурсии в 1С — глубина стека вызовов. Платформа ограничивает количество вложенных вызовов, и при превышении лимита выполнение прерывается ошибкой «Переполнение стека». Точный предел зависит от версии платформы, режима исполнения и объёма локальных данных в каждом вызове, но практический вывод один: рекурсия не подходит для обработки структур с потенциально очень большой глубиной вложенности.
Второй аспект — производительность. Каждый вызов несёт накладные расходы на создание кадра стека, а если внутри вызова выполняется запрос к базе (как в примере со справочником), стоимость умножается. Для иерархий в десятки уровней это незаметно, но при массовой обработке в цикле проведения документов или обмене данными разница становится существенной.
Отдельного внимания заслуживает риск циклических ссылок. Если из-за ошибки в данных группа справочника окажется родителем самой себя (прямо или через цепочку), рекурсивный обход зациклится. Защититься можно двумя способами: хранить коллекцию уже посещённых элементов и проверять её перед вызовом, либо ограничивать глубину счётчиком с аварийным выходом.
Когда рекурсию лучше заменить
Рекурсия — не единственный и не всегда лучший способ обхода иерархий. В ряде ситуаций альтернативы проще, быстрее и безопаснее. Выбор зависит от того, где лежат данные и что именно нужно получить.
Рекурсия и её альтернативы
Подходит для дерева значений в памяти и сложной поуровневой логики. Ограничение — глубина стека и стоимость вызовов.
Лучший выбор для выборки элементов иерархического справочника из базы. Одно обращение к СУБД вместо множества запросов.
Подходит, когда нужны суммы или количество по уровням дерева. Платформа строит иерархическую выборку сама.
Замена рекурсии для очень глубоких структур: глубина ограничена только памятью, но код сложнее в сопровождении.
Замена рекурсии на цикл с явным стеком — рабочий приём для критичных мест. Идея проста: вместо вызова функции самой себя необработанные элементы складываются в массив, и цикл извлекает их по одному. Логика обхода сохраняется, но ограничение стека вызовов перестаёт действовать — упираться можно только в объём доступной памяти.
Процедура ОбойтиБезРекурсии(КорневыеСтроки)
Стек = Новый Массив;
Для Каждого Строка Из КорневыеСтроки Цикл
Стек.Добавить(Строка);
КонецЦикла;
Пока Стек.Количество() > 0 Цикл
Текущая = Стек[Стек.Количество() - 1];
Стек.Удалить(Стек.Количество() - 1);
ОбработатьСтроку(Текущая);
Для Каждого Подчиненная Из Текущая.ПолучитьЭлементы() Цикл
Стек.Добавить(Подчиненная);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Типичные ошибки при использовании рекурсии
Большинство проблем с рекурсией сводится к нескольким повторяющимся ошибкам, которые легко предотвратить на этапе написания кода.
Отдельно отметим тонкую ошибку с экспортными переменными и параметрами по ссылке. Если рекурсивная процедура накапливает результат в переменной модуля или параметре, переданном по значению, часть данных может теряться между уровнями вызовов. Надёжный шаблон — передавать накапливающую коллекцию (массив, соответствие) параметром: объекты в 1С передаются по ссылке, и все уровни рекурсии работают с одним экземпляром.
Как проверить рекурсивный код
Один элемент без вложенности обрабатывается корректно, рекурсивного вызова нет
Ошибка или лишний вызов уже на первом элементе — условие выхода неверно
Обход завершается, результат полный
Переполнение стека или потеря части веток
Зацикленные данные обнаруживаются, обход прерывается контролируемо
Код зависает или падает на кольцевой ссылке
FAQ: частые вопросы о рекурсии в 1С
Чем рекурсия отличается от цикла в 1С?
Цикл повторяет один блок кода в рамках одного вызова, а рекурсия создаёт цепочку вложенных вызовов, каждый со своими локальными переменными. Цикл предпочтителен для плоских коллекций и большого числа итераций; рекурсия естественнее описывает обход древовидных структур, где число уровней заранее неизвестно.
Какая максимальная глубина рекурсии допустима в 1С?
Единого фиксированного числа нет: предел зависит от версии платформы, разрядности, режима исполнения и объёма локальных данных в каждом вызове. Практическое правило — не проектировать рекурсию на глубину в тысячи вызовов; если такая вложенность реально возможна, переходите на цикл с явным стеком.
Как получить все элементы группы справочника без рекурсии?
Используйте запрос с оператором В ИЕРАРХИИ: «ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура ГДЕ Ссылка В ИЕРАРХИИ(&Группа)». Запрос вернёт саму группу и все вложенные элементы любой глубины одним обращением к базе — это быстрее и надёжнее рекурсивного перебора.
Что делать при ошибке «Переполнение стека»?
Сначала проверьте условие выхода: чаще всего оно отсутствует или недостижимо. Затем убедитесь, что в данных нет циклических ссылок. Если логика корректна, а глубина объективно велика — перепишите обход на цикл с массивом в роли стека либо перенесите выборку на уровень запроса.
Можно ли вызывать рекурсивно процедуру общего модуля?
Да, ограничений нет: процедура или функция общего модуля может вызывать саму себя так же, как метод объекта или формы. Важно лишь помнить, что в серверных общих модулях с признаком «Вызов сервера» каждый рекурсивный вызов выполняется в рамках того же серверного вызова — отдельных обращений клиент-сервер не создаётся.
Когда рекурсия действительно оправдана в прикладном коде?
Когда структура данных древовидная, глубина контролируема и невелика, а логика обработки естественно описывается поуровнево: обход дерева значений на форме, разбор вложенных структур из JSON, построение дерева из плоской таблицы. Для простых выборок из базы почти всегда выгоднее запрос.
Что важно запомнить
Рекурсия в 1С — это самовызов процедуры или функции с обязательным условием выхода. Она идеальна для обхода деревьев и иерархий умеренной глубины, но ограничена стеком и уязвима к циклическим ссылкам. Для выборок из иерархических справочников предпочитайте запрос с В ИЕРАРХИИ, а для очень глубоких структур — цикл с явным стеком.