Цикл «Для каждого» упал на пустом значении: как найти и починить
Содержание
Ошибка Итератор для значения не определен значит одно: цикл Для Каждого ... Из ... получил справа не коллекцию. Чаще всего там Неопределено, пустая ссылка или значение простого типа: число, строка, дата. Платформа не знает, как обойти такой объект, и останавливает код.
Лечится это в три шага: найти строку цикла по стеку вызовов, выяснить в отладчике, что реально лежит в переменной, и либо исправить источник значения, либо добавить проверку перед циклом. Ниже — как это делается на практике, без гаданий.
Откуда берётся ошибка: что должно стоять справа от «Из»
Оператор Для Каждого Элемент Из Коллекция Цикл работает только с объектами, которые умеют отдавать элементы по одному. К ним относятся:
- 📦
Массив,СписокЗначений,Структура,Соответствие; - 📊
ТаблицаЗначений,ДеревоЗначений, табличная часть документа; - 🔍 результат запроса и его выборка;
- 📁 коллекции платформы: реквизиты, элементы формы, параметры.
Всё остальное для платформы «не итерируемо». Если в переменной Неопределено, Null, "", 0, Ложь или пустая ссылка вроде Справочники.Номенклатура.ПустаяСсылка(), цикл не стартует и вы получаете это сообщение.
Ошибка срабатывает в момент входа в цикл, а не внутри него. Если сообщение указывает на строку Для Каждого, искать причину в теле цикла бессмысленно: она выше по коду, там, где переменной присвоили значение.
Сравните два случая. В первом коллекция есть, но пустая — цикл просто не сделает ни одного прохода, ошибки не будет. Во втором коллекции нет вообще — будет сбой. Это принципиальная разница, и на ней строится вся диагностика.
Где эта ошибка прячется чаще всего
За годы работы с типовыми и самописными конфигурациями набирается узкий список повторяющихся сценариев.
Функция вернула не то, что ожидали
Самый частый случай. Функция должна вернуть массив или таблицу, но на одной из веток ничего не возвращает. В 1С функция без Возврат отдаёт Неопределено, и вызывающий код падает на цикле.
Функция ПолучитьСписокДоговоров(Контрагент)
Если Не ЗначениеЗаполнено(Контрагент) Тогда
Возврат; // здесь вернётся Неопределено
КонецЕсли;
// ... запрос и возврат таблицы
КонецФункции
Для Каждого Строка Из ПолучитьСписокДоговоров(Контрагент) Цикл
Если контрагент не выбран, цикл получит Неопределено. Исправление — возвращать пустую таблицу или пустой массив, а не «ничего».
Запрос без результата, к которому обратились не так
После Запрос.Выполнить() получаем результат, из него Выбрать() или Выгрузить(). Если по ошибке обойти сам объект Запрос или забыть выгрузку, получим не коллекцию. Ещё вариант: переменной присвоили результат метода, который возвращает Неопределено при отсутствии данных (например, Найти у массива или ПолучитьСтроку).
Реквизит объекта оказался пустой ссылкой
Код вроде Для Каждого Стр Из Документ.Товары работает, пока обходится табличная часть. Но стоит написать Для Каждого Стр Из Документ.ДоговорКонтрагента, где реквизит — ссылка, и платформа получит значение, которое не является коллекцией. Опечатка в имени реквизита здесь выглядит как вполне рабочий код.
Параметр пришёл пустым
Обработка вызывается из внешнего источника: другой обработки, регламентного задания, обмена. Параметр не передали или передали Неопределено, а внутри его сразу обходят в цикле.
Данные обмена или внешней обработки изменились
Формат файла обмена поменялся, узел XML исчез, а чтение вернуло Неопределено вместо массива узлов. Код, написанный под старую структуру, ломается на первой же итерации.
| Сценарий | Что лежит в переменной | Где искать причину |
|---|---|---|
Функция без Возврат | Неопределено | ветки Если внутри функции |
| Обход ссылки вместо ТЧ | ссылка на объект | имя реквизита в цикле |
| Пустой параметр | Неопределено | место вызова процедуры |
| Сбой чтения внешних данных | Неопределено или Null | код разбора файла или XML |
| Запрос не выгружен | объект результата | цепочка Выполнить().Выгрузить() |
Как найти строку и значение: диагностика через стек вызовов
Не пытайтесь угадать по тексту сообщения. Платформа показывает стек, и по нему ошибка находится за пять минут.
Шаг 1. Откройте подробное сообщение
В окне с ошибкой нажмите «Подробно» (если кнопка есть). В тексте будут имя модуля, номер строки и фрагмент кода. Номер строки указывает на сам Для Каждого.
Шаг 2. Запустите отладку на воспроизведении
Подключитесь к базе в режиме конфигуратора, поставьте точку останова на строке цикла и повторите действие пользователя. Отладка работает, если ошибка воспроизводится именно у вас. Если нет, смотрите журнал регистрации и рассылку ошибок: там остаётся тот же стек.
Шаг 3. Посмотрите значение и тип
Когда выполнение остановится на строке, наведите курсор на коллекцию или добавьте её в окно «Вычислить выражение». Нужны две вещи:
- 🔎 Само значение:
Неопределено, пустая ссылка, число. - 🔎 Тип: выражение
ТипЗнч(Коллекция)покажет, что реально пришло.
Шаг 4. Поднимитесь по стеку
В окне «Стек вызовов» кликните на вышестоящую процедуру. Так вы увидите, кто вызвал код и с какими параметрами. Нередко виноват не тот модуль, где упало, а вызывающий, который передал пустое значение.
Не правьте код в рабочей базе наугад. Сначала воспроизведите ошибку на копии и сделайте резервную копию информационной базы перед любыми изменениями в конфигурации.
Правильный обход выборок и коллекций
Половина случаев — это неверная работа с запросами. Запомните стандартную цепочку.
Выгрузка в таблицу или выборка
Есть два нормальных способа получить данные из запроса:
Результат = Запрос.Выполнить();
// Способ 1: выгрузка в таблицу значений
Таблица = Результат.Выгрузить();
Для Каждого Строка Из Таблица Цикл
// работа со строкой
КонецЦикла;
// Способ 2: выборка
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// работа с текущей записью
КонецЦикла;
Обратите внимание: выборка обходится через Пока Выборка.Следующий(), а таблица значений — через Для Каждого. Если применить Для Каждого к самой выборке без выгрузки, можно получить неожиданное поведение, а на самом результате Выполнить() — ту самую ошибку.
Проверка на пустоту до цикла
Перед обходом результата можно проверить, есть ли в нём данные:
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Это быстрее, чем выгружать пустую таблицу, и сразу защищает от лишних операций.
Контроль типа
Если коллекция приходит снаружи (параметр, ответ веб-сервиса, чтение файла), проверяйте тип явно:
Если ТипЗнч(Данные) <> Тип("Массив") Тогда
Данные = Новый Массив;
КонецЕсли;
Да, это добавляет три строки. Зато цикл ниже уже гарантированно получает массив, а не сюрприз.
Попытка-Исключение: когда уместна, а когда вредна
Конструкция Попытка ... Исключение ... КонецПопытки часто становится первой реакцией на ошибку. Но у неё есть цена.
Когда она помогает
Если данные приходят из ненадёжного источника (файл от контрагента, ответ внешнего сервиса), оборачивать разбор в Попытку имеет смысл. Вы обрабатываете сбой осмысленно: пишете причину в журнал, показываете понятное сообщение пользователю.
Попытка
Для Каждого Узел Из РазобратьФайл(Файл) Цикл
ОбработатьУзел(Узел);
КонецЦикла;
Исключение
ЗаписьЖурналаРегистрации("ИмпортФайла",
УровеньЖурналаРегистрации.Ошибка,,,
ОписаниеОшибки());
ВызватьИсключение "Не удалось разобрать файл обмена";
КонецПопытки;
Когда она вредит
Если обернуть в Попытку собственный код, а в Исключении ничего не делать, ошибка исчезнет из виду, но данные останутся необработанными. Пользователь получит «молча не сработало», а вы — расследование через месяц.
Пустой блок Исключение — худший вариант. Если уж ловите сбой, обязательно фиксируйте ОписаниеОшибки() в журнал регистрации или показывайте сообщение.
Правило простое: для предсказуемой ситуации (коллекции может не быть) ставьте проверку Если. Для непредсказуемой (внешний мир ведёт себя странно) — Попытку с логированием.
Управляемые формы: клиент, сервер и потерянный контекст
В управляемых формах появляется своя группа причин, которых нет в обычных. Дело в разделении кода на клиентский и серверный.
Данные формы — не то же самое, что объект
Реквизит формы типа ДанныеФормыКоллекция — это не ТаблицаЗначений. Цикл по нему на клиенте работает, но если вы передали его на сервер через параметр без нужного описания, получите другой тип или пустоту. Проверяйте, что именно пришло на сервер.
Вызов из клиента без контекста
Процедура на клиенте обращается к реквизиту, который заполняется только на сервере (например, в ПриСозданииНаСервере). Если обращение происходит раньше заполнения, внутри Неопределено. Порядок событий формы важнее, чем кажется.
Обращение к элементу формы по имени
Код вида Элементы[ИмяЭлемента] падает, если элемента с таким именем нет: он удалён, переименован или скрыт настройкой доработки. Итоговая ошибка при последующем обходе выглядит похоже.
Данные из переданных параметров
Параметры открытия формы приходят в структуре. Если ключа нет, обращение даёт Неопределено. Читайте параметры безопасно:
Если Параметры.Свойство("СписокДокументов") Тогда
Для Каждого Документ Из Параметры.СписокДокументов Цикл
// ...
КонецЦикла;
КонецЕсли;
Как не получить эту ошибку снова: приёмы для разработчика
Профилактика дешевле отладки. Несколько привычек, которые закрывают почти все описанные выше случаи.
Функции всегда возвращают коллекцию
Если функция заявлена как «вернуть массив», она возвращает массив на любой ветке, даже пустой. Начните тело функции с создания результата и возвращайте его в конце:
Функция ПолучитьСписок(Отбор)
Результат = Новый Массив;
Если Не ЗначениеЗаполнено(Отбор) Тогда
Возврат Результат;
КонецЕсли;
// ... наполнение
Возврат Результат;
КонецФункции
Один выход, один тип
Не смешивайте в одной функции возврат таблицы и Неопределено. Вызывающий код будет вынужден проверять тип каждый раз, а забывают об этом первым делом.
Проверка параметров на входе
В начале процедуры проверьте то, без чего работа невозможна. Ошибка с понятным текстом («Не передан список документов») находится в разы быстрее, чем «итератор для значения не определен» где-то в глубине.
Комментарии к контракту функции
Одна строка над функцией: что принимает, что возвращает, может ли вернуть пустое значение. Через полгода это спасает и вас, и коллегу.
Тестовая база для доработок
Любая правка кода сначала проходит на копии. Прогоните три сценария: данные есть, данные пустые, данных нет совсем. Именно третий ловит нашу ошибку.
| Привычка | Что закрывает |
|---|---|
| Возврат пустой коллекции | функции без Возврат |
Проверка ТипЗнч для внешних данных | неверный тип параметра |
Результат.Пустой() перед выгрузкой | пустые запросы |
Логирование в Исключении | «тихие» сбои |
| Тест на пустых данных | регрессии после правок |
Частые вопросы
Чем отличается пустая коллекция от неопределённой?
Можно ли обойтись без правки кода, если ошибка у пользователя в типовой конфигурации?
Почему ошибка появилась после обновления?
Нужно ли ловить эту ошибку через Попытку?
Попытка нужна там, где источник данных вам неподконтролен.