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