Как правильно написать рекурсивную функцию в 1С и избежать ошибок при возврате значений

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Анализ основной ошибки: почему теряется результат

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


&НаКлиенте
Функция ПолучитьРодителяПоУровню(ПоследнийЭлемент, КоличествоВозвратов)
    ТекущийУровень = КоличествоВозвратов - 1;
    Если ТекущийУровень > 0 тогда
        ПолучитьРодителяПоУровню(ПоследнийЭлемент.ПолучитьРодителя, ТекущийУровень);
    Иначе
        Возврат ПоследнийЭлемент.ПолучитьРодителя;
    КонецЕсли;
КонецФункции

Выясним причину, по которой этот код работает некорректно. Основная проблема здесь заключается в строке вызова функции внутри самой себя. Когда ТекущийУровень > 0, программа вызывает ПолучитьРодителяПоУровню, выполнение «уходит» глубже в стек, доходит до терминальной ветки (где срабатывает Возврат), но результат этого вложенного вызова никуда не передается в вышестоящую итерацию.

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

Решение №1: Использование оператора Возврат для рекурсивного вызова

Чтобы исправить эту ошибку, нам нужно гарантировать, что результат самого нижнего вызова будет «проброшен» на самый верх через все уровни стека. Проанализируем исправленный вариант кода:


&НаКлиенте
Функция ПолучитьРодителяПоУровню(ПоследнийЭлемент, КоличествоВозвратов)
    ТекущийУровень = КоличествоВозвратов - 1;
    Если ТекущийУровень > 0 тогда
        // Добавляем ключевое слово Возврат перед рекурсивным вызовом
        Возврат ПолучитьРодителяПоУровню(ПоследнийЭлемент.ПолучитьРодителя, ТекущийУровень);
    Иначе
        Возврат ПоследнийЭлемент.ПолучитьРодителя;
    КонецЕсли;
КонецФункции

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

Решение №2: Повышение наглядности и использование промежуточной переменной

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


&НаКлиенте
Функция ПолучитьРодителяПоУровню(ПоследнийЭлемент, КоличествоВозвратов)
    ТекущийУровень = КоличествоВозвратов - 1;
    Если ТекущийУровень > 0 Тогда
        // Сначала получаем результат из глубины, затем возвращаем его
        Результат = ПолучитьРодителяПоУровню(ПоследнийЭлемент.ПолучитьРодителя, ТекущийУровень);
        Возврат Результат;
    Иначе
        Возврат ПоследнийЭлемент.ПолучитьРодителя;
    КонецЕсли;
КонецФункции

При таком подходе вы можете поставить точку останова на строке Возврат Результат и точно увидеть, какое именно значение «прилетело» из вложенного вызова перед тем, как оно уйдет выше.

Важность использования ключевого слова Знач

В ходе дискуссии был затронут важный технический аспект 1С — способ передачи параметров. По умолчанию в 1С параметры передаются по ссылке. Это означает, что если вы случайно измените значение параметра внутри рекурсии, оно изменится и для всех предыдущих вызовов, которые еще находятся в памяти (в стеке).

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

  1. Без Знач переменная КоличествоВозвратов в каждой итерации ссылается на одну и ту же область памяти.
  2. Если в коде возникнет сложная логика с изменением этой переменной, это может привести к бесконечной рекурсии или преждевременному выходу.
  3. Использование Знач создает локальную копию переменной для каждой итерации, обеспечивая изоляцию данных.

Компактная реализация (Тернарный оператор)

Если логика функции проста, её можно сократить до одной строки. Посмотрим на пример функции для поиска самого верхнего родителя (корня):


Функция ПолучитьВерхнегоРодителя(Элемент)
    Возврат ?(ЗначениеЗаполнено(Элемент.Родитель), ПолучитьВерхнегоРодителя(Элемент.Родитель), Элемент);
КонецФункции

Здесь используется встроенная функция ?(). Если родитель заполнен, функция снова вызывает себя, если нет (достигли корня) — возвращает текущий элемент. Это элегантное решение для простых задач обхода дерева.

Ограничения рекурсии в 1С

При использовании рекурсии важно помнить о технических лимитах платформы. Проанализируем потенциальные риски:

Альтернатива: использование циклов

Если глубина вложенности заранее неизвестна и может быть огромной, стоит рассмотреть замену рекурсии обычным циклом Пока. Это безопаснее с точки зрения памяти. Рассмотрим пример:


Функция ПолучитьРодителяНаУровнеИтерационно(Элемент, Глубина)
    ТекущийРодитель = Элемент;
    Для Сч = 1 По Глубина Цикл
        ТекущийРодитель = ТекущийРодитель.Родитель;
        Если ТекущийРодитель.Пустая() Тогда
            Прервать;
        КонецЕсли;
    КонецЦикла;
    Возврат ТекущийРодитель;
КонецФункции

Этот подход избавляет от накладных расходов на создание стека вызовов и гарантирует отсутствие ошибки переполнения стека.

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

← На главную