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