Разбор от сообщества: Как обойти дерево значений
Обход дерева значений — задача, с которой сталкивается каждый разработчик 1С. Казалось бы, что может быть проще: рекурсивная процедура, вызов ПолучитьСтроки() — и готово. Но на практике именно этот «простой» подход приводит к неожиданным падениям в рабочих базах, когда глубина иерархии превышает 50–100 уровней. Разберём, почему рекурсия опасна, как заменить её итеративным обходом без потери производительности, и в каких случаях от дерева значений лучше отказаться вовсе.
Рекурсивный обход: классика и её подводные камни
Как устроено дерево значений
ДеревоЗначений в платформе 1С — это коллекция строк, каждая из которых может содержать вложенные строки (свойство Строки). Внутренняя реализация — связный список с ссылками на родителя и потомков. При рекурсивном обходе мы последовательно обращаемся к Строки.Количество(), затем для каждой строки вызываем ту же процедуру. На первый взгляд — элегантно.
Процедура ОбойтиРекурсивно(СтрокиДерева)
Для Каждого ТекСтрока Из СтрокиДерева Цикл
// Обработка строки
Сообщить(ТекСтрока.Имя);
// Рекурсивный вызов для подчинённых
ОбойтиРекурсивно(ТекСтрока.Строки);
КонецЦикла;
КонецПроцедуры;
// Вызов
ОбойтиРекурсивно(Дерево.Строки);
Ключевой тезис: Рекурсия в 1С не оптимизирована под глубокие иерархии. Каждый вложенный вызов — это новый фрейм стека, а платформа выделяет под стек ограниченный объём памяти (обычно около 1–2 МБ). При глубине более 100–200 уровней вы гарантированно получите исключение «Переполнение стека».
Ошибка переполнения стека
Проблема проявляется не сразу. В тестовой базе с 10–20 уровнями всё работает. Но когда в боевом контуре появляется справочник «Классификатор товаров» с вложенностью 300+ строк, рекурсивный обход падает с Ошибка выполнения: Стек вызовов переполнен. Причём отловить это исключение стандартной конструкцией Попытка…Исключение не всегда удаётся — платформа может аварийно завершить сеанс.
Дополнительная ловушка: рекурсия замедляется экспоненциально, если внутри цикла выполняются «тяжёлые» операции (запросы, запись в регистры). Каждый уровень добавляет накладные расходы на вызов процедуры.
Итеративный обход с явным стеком
Реализация на основе массива
Вместо рекурсии используем ручное управление стеком. Стек — это обычный массив, в который мы складываем строки, которые нужно обработать. Алгоритм:
- Помещаем в стек корневые строки (в обратном порядке, чтобы сохранить порядок обхода).
- Пока стек не пуст: извлекаем строку, обрабатываем, затем добавляем её подчинённые строки в стек (снова в обратном порядке).
Процедура ОбойтиИтеративно(Дерево)
Стек = Новый Массив;
// Заполняем стек корневыми строками в обратном порядке
Для Инд = Дерево.Строки.Количество() - 1 По 0 Цикл -1
Стек.Добавить(Дерево.Строки[Инд]);
КонецЦикла;
Пока Стек.Количество() > 0 Цикл
ТекСтрока = Стек[Стек.ВГраница()];
Стек.Удалить(Стек.ВГраница());
// Обработка строки
Сообщить(ТекСтрока.Имя);
// Добавляем подчинённые строки в обратном порядке
Для Инд = ТекСтрока.Строки.Количество() - 1 По 0 Цикл -1
Стек.Добавить(ТекСтрока.Строки[Инд]);
КонецЦикла;
КонецЦикла;
КонецПроцедуры;
Примечание: Порядок обхода при использовании стека — «в глубину» (DFS), но с обратным порядком добавления мы получаем тот же порядок, что и при рекурсии (сначала первый потомок, затем его поддерево). Если нужен обход «в ширину» (BFS), используйте очередь (например,
СписокЗначенийс добавлением в конец и извлечением из начала).
Сравнение подходов
| Характеристика | Рекурсия | Итеративный стек |
|---|---|---|
| Глубина иерархии | Ограничена (обычно < 200) | Не ограничена (только память) |
| Скорость | Медленнее из-за накладных расходов на вызовы | Быстрее (один цикл) |
| Читаемость кода | Высокая | Средняя (требуется комментарий) |
| Риск переполнения стека | Высокий | Отсутствует |
Альтернативы: когда дерево не нужно
Таблица значений с иерархией
Если данные хранятся в справочнике или регистре сведений, а не формируются динамически, часто выгоднее использовать плоскую таблицу значений с колонкой «Родитель». Обход такой таблицы выполняется одним запросом (например, ВЫБРАТЬ ... ИЗ ... ГДЕ Родитель = &Текущий) или рекурсивным запросом через ОБЪЕДИНИТЬ ВСЕ. Это избавляет от проблем с глубиной и даёт возможность использовать индексы.
// Пример рекурсивного запроса (платформа 8.3.10+)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Ссылка,
| Наименование,
| Родитель
|ИЗ
| Справочник.Номенклатура
|ГДЕ
| Родитель = &Текущий
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| Ссылка,
| Наименование,
| Родитель
|ИЗ
| Справочник.Номенклатура
|ГДЕ
| Родитель В (ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура КАК Влож ГДЕ Влож.Родитель = &Текущий)";
// ... и так далее до нужной глубины (обычно 10-15 уровней)
Замечание: Рекурсивные запросы через
ОБЪЕДИНИТЬ ВСЕимеют ограничение на количество объединений (обычно 256). Для очень глубоких иерархий используйте временные таблицы и цикл.
Использование СписокЗначений с группировкой
Если дерево нужно только для отображения в форме, рассмотрите СписокЗначений с пометкой групп. Это легковеснее и не требует рекурсивного обхода для получения плоского списка. Однако для программной обработки вложенности такой подход менее удобен.
Практические рекомендации
Чек-лист: что проверить перед обходом дерева
- Оцените максимальную глубину иерархии в ваших данных. Если она превышает 50 — откажитесь от рекурсии.
- Проверьте, не содержит ли дерево циклических ссылок (строка ссылается сама на себя через родителя). Это приведёт к бесконечному циклу.
- Убедитесь, что в строках нет «тяжёлых» реквизитов (например, картинок), которые замедляют обход.
- Если дерево формируется динамически (например, из запроса), рассмотрите возможность построения плоской таблицы значений с колонкой
Уровень.
Типичные ошибки
-
Расширение «НОПик» для 1С — встраиваемый коннектор к внешнему AI с интеллектуальным поиском по базе. Задавайте вопросы обычными словами - AI сам найдёт нужное. 45 дней бесплатно.
Попробовать НОПик →Знаете ответ на такие вопросы не хуже автора статьи? Пройдите бесплатную анонимную проверку уровня на infolimp.ru - 3 практических задачи, 15 минут, публичный токен-профиль, который можно показать работодателю или заказчику. Без регистрации по почте.
Проверить свой уровень бесплатно →