При разработке алгоритмов загрузки данных, синхронизации или пакетного создания документов разработчики часто сталкиваются с необходимостью проверить наличие иерархической структуры каталогов (папок) и, при их отсутствии, создать новые элементы. Попытка управлять ходом выполнения таких алгоритмов с помощью директив перехода нередко приводит к синтаксическим ошибкам еще на этапе проверки модулей конфигурации или во время компиляции формы.
В этой статье разберем, почему возникает ошибка «Оператор Продолжить (Continue) может употребляться только внутри цикла», какие логические ограничения накладывает работа с выборками в платформе 1С:Предприятие и как построить надежный алгоритм поиска и создания иерархических каталогов.
Платформа 1С строго регламентирует контекст использования операторов управления циклами. Разберем правило:
Продолжить предназначен исключительно для прерывания текущей итерации цикла и перехода к следующей.Прервать предназначен для досрочного выхода из цикла.Цикл и закрывающим КонецЦикла.Проанализируем фрагмент проблемного кода:
Если Выборка.Наименование = ТекСтрока[ИндексКолонкиОрган] Тогда
Пока Выборка.Следующий() Цикл
// Внутренний цикл
КонецЦикла;
Иначе
СоздатьКаталог(ИндексКолонкиОрган);
Продолжить; // Ошибка здесь!
КонецЕсли;
Посмотрим на структуру блоков: первый оператор Продолжить в исходном коде действительно находился внутри цикла Пока ... КонецЦикла, однако второй оператор расположен в ветке Иначе внешнего условия Если. Вокруг этой ветки никакого цикла нет. Компилятор платформы обнаруживает ключевое слово Продолжить вне контекста циклического блока и генерирует исключение.
Даже если бы внешний блок был обернут в цикл, попытка вернуться в начало обработки с помощью Продолжить столкнулась бы с архитектурными ограничениями работы с выборками в 1С:
ВыбратьИерархически() или Выбрать()) не имеет метода сброса позиции. Метод Следующий() последовательно считывает строки из базы данных. Если мы уже прочитали первую запись, вернуться назад невозможно без повторного вызова метода выборки.СоздатьКаталог() и записали новую группу в базу данных, созданный элемент автоматически в текущей выборке не появится.Выборка.Следующий() перед проверкой берет произвольную первую запись верхнего уровня. Если в базе уже есть несколько организаций, алгоритм сопоставит только одну случайную запись, а для остальных сразу уйдет в ветку создания дубликата.Если задача требует обязательного перебора элементов через выборку, необходимо поместить чтение данных в полноценный цикл, как рекомендовали участники обсуждения. Разберем этот вариант:
Выборка = Справочники.ПапкиВнутреннихДокументов.ВыбратьИерархически(Путь);
Пока Выборка.Следующий() Цикл
Если Выборка.Наименование = ИмяОрганизации Тогда
// Нашли нужную папку организации, обрабатываем вложенные элементы
КонецЕсли;
КонецЦикла;
Однако этот подход неудобен, если требуется создавать отсутствующие элементы «на лету», так как после создания нового каталога выборку пришлось бы перезапускать заново.
Вместо использования вложенных выборок и операторов перехода гораздо эффективнее и надежнее применить функцию гарантированного получения папки по цепочке иерархии. Выясним, как работает этот шаблон:
Посмотрим на реализацию вспомогательной функции:
Функция ПолучитьИлиСоздатьПапку(НаименованиеПапки, РодительскаяПапка = Неопределено)
// Ищем точное соответствие наименования с учетом родительского узла
СсылкаНаПапку = Справочники.ПапкиВнутреннихДокументов.НайтиПоНаименованию(НаименованиеПапки, Истина, РодительскаяПапка);
Если СсылкаНаПапку.Пустая() Тогда
ОбъектПапка = Справочники.ПапкиВнутреннихДокументов.СоздатьГруппу();
ОбъектПапка.Наименование = НаименованиеПапки;
Если РодительскаяПапка <> Неопределено И Не РодительскаяПапка.Пустая() Тогда
ОбъектПапка.Родитель = РодительскаяПапка;
КонецЕсли;
ОбъектПапка.Записать();
Возврат ОбъектПапка.Ссылка;
КонецЕсли;
Возврат СсылкаНаПапку;
КонецФункции
Теперь разберем, как кардинально упрощается основной алгоритм создания документов без использования рискованных конструкций с Продолжить:
Процедура СозданиеДокументов(ВременнаяТД)
Если ВременнаяТД.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Получаем имена из первой строки
ТекСтрока = ВременнаяТД[0];
ИмяОрганизации = ТекСтрока.Организация;
ИмяПодразделения = ТекСтрока.Подразделение;
// Гарантированно получаем папку верхнего уровня (Организация)
ПапкаОрганизации = ПолучитьИлиСоздатьПапку(ИмяОрганизации);
// Гарантированно получаем вложенную папку (Подразделение)
ПапкаПодразделения = ПолучитьИлиСоздатьПапку(ИмяПодразделения, ПапкаОрганизации);
// Создаем документы в целевой папке
НачатьТранзакцию();
Попытка
Для Каждого СтрокаТД Из ВременнаяТД Цикл
// Создание и запись документа с привязкой к ПапкаПодразделения
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
Если ВТранзакции() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ЗаписьЖурналаРегистрации("Создание документов", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Проанализируем еще несколько критических моментов, присутствовавших в исходном коде автора, которые могут вызвать скрытые ошибки и замедление работы:
ВременнаяТД.Колонки.Найти("Организцаия") допущена опечатка. Метод Найти() вернет Неопределено, а вызов ВременнаяТД.Колонки.Индекс(Неопределено) приведет к ошибке во время выполнения.ВременнаяТД.Колонки.Найти("Подразделение") на каждой итерации обхода таблицы создает лишнюю вычислительную нагрузку. К колонкам строки таблицы значений можно обращаться напрямую через точку (например, ТекСтрока.Подразделение) или определять индексы заранее до входа в цикл.Исключение перед вызовом ОтменитьТранзакцию() всегда следует проверять условие Если ВТранзакции() Тогда. Если ошибка произошла на уровне СУБД, транзакция может быть уже неактивна, и повторная отмена вызовет вторичную ошибку.