Индекс находится за границами массива в 1С — ошибка времени выполнения: код обратился к элементу коллекции по номеру, которого в ней нет. Индексы считаются с нуля, поэтому у коллекции из трёх элементов последний индекс — 2, а [3], [-1] или [0] у пустой коллекции дают одно и то же сообщение. Так падают массив, таблица значений, табличная часть, данные формы, список значений и строка таблицы значений, если колонку берут по номеру. Ниже — причины с защитным кодом, поиск места ошибки и разбор случая после обновления типовой.
Выход за границы массива в 1С почти всегда выглядит как одна из строк таблицы:
| Код в строке ошибки | Что пошло не так |
|---|---|
Для Инд = 0 По Товары.Количество() Цикл | индекс доходит до количества строк |
Товары[СтрокаТЧ.НомерСтроки] | номер строки считается с 1, индекс — с 0 |
Товары.Удалить(Инд) в цикле Для … По | граница цикла вычислена до удалений |
СтрРазделить(Стр, ";")[2] | полей меньше, чем ждали |
Запрос.Выполнить().Выгрузить()[0] | запрос ничего не вернул |
Запрос.ВыполнитьПакет()[3] | запросов в пакете стало меньше |
СтрокаПрайса[3] | в таблице меньше колонок |
Объект.Товары[Элементы.Товары.ТекущаяСтрока] | это идентификатор строки, а не индекс |
Элементы.Список.ВыделенныеСтроки[0], Элементы.Склад.СписокВыбора[0] | список пуст или ничего не выделено |
Условие для всех случаев одно: индекс не меньше нуля и меньше Количество(). Устройство массива и разница между ВГраница() и Количество() — в статье Массив в 1С 8.3.
Полный текст: {ОбщийМодуль.ЗагрузкаПрайсов.Модуль(214)}: Индекс находится за границами массива — в фигурных скобках модуль и номер строки. Если исключение выбросил метод коллекции, перед текстом стоит его имя: Ошибка при вызове метода контекста (Получить): Индекс находится за границами массива.
Попытка.Если ошибка бывает только в фоновом задании или у одного пользователя, пусть исключение само называет коллекцию и её размер:
// Общий модуль РаботаСКоллекциями: флажки «Клиент (управляемое приложение)»,
// «Сервер», «Внешнее соединение».
// Подходит для массива, списка значений, таблицы значений, табличной части
// и данных формы: у всех есть Количество() и обращение [Индекс].
// Истина, если элемент с таким индексом есть в коллекции
Функция ИндексВГраницах(Коллекция, Индекс) Экспорт
Если ТипЗнч(Индекс) <> Тип("Число") Тогда
Возврат Ложь; // например, Неопределено из Найти()
КонецЕсли;
Возврат Индекс >= 0 И Индекс < Коллекция.Количество();
КонецФункции
// Элемент по индексу. Вместо «Индекс находится за границами массива»
// исключение называет коллекцию, индекс и фактический размер.
Функция ЭлементПоИндексу(Коллекция, Индекс, ИмяКоллекции) Экспорт
Если Не ИндексВГраницах(Коллекция, Индекс) Тогда
ВызватьИсключение СтрШаблон("%1: нет элемента с индексом %2, всего элементов %3",
ИмяКоллекции, Индекс, Коллекция.Количество());
КонецЕсли;
Возврат Коллекция[Индекс];
КонецФункции
// Последний элемент или ЗначениеПоУмолчанию, если коллекция пустая
Функция ПоследнийЭлемент(Коллекция, ЗначениеПоУмолчанию = Неопределено) Экспорт
ВсегоЭлементов = Коллекция.Количество();
Если ВсегоЭлементов = 0 Тогда
Возврат ЗначениеПоУмолчанию;
КонецЕсли;
Возврат Коллекция[ВсегоЭлементов - 1];
КонецФункции
Вызов РаботаСКоллекциями.ЭлементПоИндексу(Поля, 2, "Поля строки прайса") вместо Поля[2] даёт текст «Поля строки прайса: нет элемента с индексом 2, всего элементов 2» — причина видна без отладчика.
С нуля считаются только индексы. Всё, что видит пользователь, нумеруется с единицы: НомерСтроки, строки и колонки табличного документа, позиция в СтрНайти(). Ошибка на единицу даёт два симптома: код берёт соседнюю строку, а на последней падает. У таблицы значений и табличной части нет ВГраница(), последний индекс — Количество() - 1. Вторая частая причина — общий индекс для двух таблиц разной длины.
// Сервер: модуль документа, обработки или общий модуль.
// ТоварыЗаказа, ТоварыРеализации — табличные части или таблицы значений
// с колонками Номенклатура и Количество.
//
// Было:
// Для Индекс = 0 По ТоварыЗаказа.Количество() Цикл
// СтрокаЗаказа = ТоварыЗаказа[Индекс];
// СтрокаРеализации = ТоварыРеализации[Индекс];
// ТоварыЗаказа[Индекс] выходит за границу на последнем проходе (индекс равен
// количеству строк), ТоварыРеализации[Индекс] — раньше, если в реализации строк меньше.
Функция РасхожденияПоСтрокам(ТоварыЗаказа, ТоварыРеализации) Экспорт
Расхождения = Новый Массив;
ОбщихСтрок = Мин(ТоварыЗаказа.Количество(), ТоварыРеализации.Количество());
// У таблиц нет ВГраница(): последний индекс равен Количество() - 1
Для Индекс = 0 По ОбщихСтрок - 1 Цикл
СтрокаЗаказа = ТоварыЗаказа[Индекс];
СтрокаРеализации = ТоварыРеализации[Индекс];
Если СтрокаЗаказа.Номенклатура <> СтрокаРеализации.Номенклатура
Или СтрокаЗаказа.Количество <> СтрокаРеализации.Количество Тогда
// Пользователю — номер строки, он на единицу больше индекса
Расхождения.Добавить(СтрШаблон("Строка %1: в заказе %2 (%3), в реализации %4 (%5)",
Индекс + 1, СтрокаЗаказа.Номенклатура, СтрокаЗаказа.Количество,
СтрокаРеализации.Номенклатура, СтрокаРеализации.Количество));
КонецЕсли;
КонецЦикла;
Если ТоварыЗаказа.Количество() <> ТоварыРеализации.Количество() Тогда
Расхождения.Добавить(СтрШаблон("Строк в заказе: %1, в реализации: %2",
ТоварыЗаказа.Количество(), ТоварыРеализации.Количество()));
КонецЕсли;
Возврат Расхождения;
КонецФункции
// Строка табличной части по НомерСтроки (из запроса или из текста сообщения).
// Было: Возврат Товары[НомерСтроки];
// Номер считается с 1, индекс с 0: код берёт следующую строку, а на последней падает.
Функция СтрокаПоНомеру(Товары, НомерСтроки) Экспорт
Индекс = НомерСтроки - 1;
Если Индекс < 0 Или Индекс >= Товары.Количество() Тогда
Возврат Неопределено; // строку удалили после того, как получили номер
КонецЕсли;
Возврат Товары[Индекс];
КонецФункции
Мин() ограничивает цикл короткой таблицей, а разница в длине уходит в сообщение, а не в исключение.Прямой цикл Для … По с Удалить(Индекс) падает, если удалена хоть одна строка, кроме последней: конечное значение цикла платформа вычисляет один раз до первого прохода — так оператор Для описан в справке. После удаления последние индексы указывают за конец таблицы, а строка, вставшая на место удалённой, не проверяется. В Для Каждого исключения нет, но соседняя строка тоже пропускается. Надёжнее удалять по самой строке: Удалить() у таблицы значений, табличной части и данных формы её принимает. Другие операции — в статье Табличная часть документа программно.
// Сервер или клиент: табличная часть, таблица значений, данные формы.
//
// Было:
// Для Индекс = 0 По Товары.Количество() - 1 Цикл
// Если Товары[Индекс].Количество = 0 Тогда
// Товары.Удалить(Индекс);
// КонецЕсли;
// КонецЦикла;
// Граница цикла вычислена до первого удаления. Строк становится меньше,
// последние индексы указывают за конец таблицы, а строка, вставшая
// на место удалённой, не проверяется.
// Условие на равенство: строки находит НайтиСтроки(), удаляем по самой строке
Процедура УдалитьСтрокиБезКоличества(Товары) Экспорт
НайденныеСтроки = Товары.НайтиСтроки(Новый Структура("Количество", 0));
Для Каждого СтрокаТоваров Из НайденныеСтроки Цикл
Товары.Удалить(СтрокаТоваров);
КонецЦикла;
КонецПроцедуры
// Сложное условие: сначала собираем строки, потом удаляем
Процедура УдалитьНезаполненныеСтроки(Товары) Экспорт
КУдалению = Новый Массив;
Для Каждого СтрокаТоваров Из Товары Цикл
Если Не ЗначениеЗаполнено(СтрокаТоваров.Номенклатура) Или СтрокаТоваров.Количество <= 0 Тогда
КУдалению.Добавить(СтрокаТоваров);
КонецЕсли;
КонецЦикла;
Для Каждого СтрокаТоваров Из КУдалению Цикл
Товары.Удалить(СтрокаТоваров);
КонецЦикла;
КонецПроцедуры
// Массив значений: ничего не удаляем, собираем новый
Функция БезПустыхЗначений(Значения) Экспорт
Результат = Новый Массив;
Для Каждого Значение Из Значения Цикл
Если ЗначениеЗаполнено(Значение) Тогда
Результат.Добавить(Значение);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
НайтиСтроки() возвращает отдельный массив, удаление из таблицы его не сокращает.Массив.Удалить() с индексом больше ВГраница() ничего не делает и ошибку не выдаёт — в прямом цикле падает чтение Массив[Индекс] в условии.Разбор файлов, выгрузок ТСД и ФИО — главный источник ошибки в загрузках: в данных попадаются пустые строки, строки без последнего поля и двойные пробелы. Многое решает третий параметр ВключатьПустые:
| Строка | СтрРазделить(С, ";") | СтрРазделить(С, ";", Ложь) |
|---|---|---|
| пустая | 1 элемент — пустая строка | 0 элементов |
4601234567890;2 | 2 элемента | 2 элемента |
4601234567890;;150 | 3, средний пустой | 2 — цена встала на место количества |
Для полей по позициям Ложь не ставьте — пустое поле сдвинет следующие. Для списка слов, наоборот, нужен Ложь. Функция БСП СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок() без третьего параметра для пустой строки возвращает пустой массив (с разделителем-пробелом — массив из одной пустой строки), поэтому замена одной функции другой меняет число элементов.
// Клиент или сервер: разбор строки из файла ТСД "штрихкод;количество;цена".
// Цена есть не в каждой строке, в конце файла бывает пустая строка.
//
// Было:
// Части = СтрРазделить(СтрокаФайла, ";");
// Штрихкод = Части[0];
// Количество = Число(Части[1]);
// Цена = Число(Части[2]);
// Строка без цены — два элемента, и Части[2] за границей;
// пустая строка — один пустой элемент, и падает уже Части[1].
Функция РазобратьСтрокуТСД(СтрокаФайла, ТекстОшибки) Экспорт
ТекстОшибки = "";
Если ПустаяСтрока(СтрокаФайла) Тогда
Возврат Неопределено; // пустую строку пропускаем без ошибки
КонецЕсли;
// Третий параметр не указан (Истина): пустое поле в середине
// остаётся на своём месте и не сдвигает цену на место количества
Части = СтрРазделить(СтрокаФайла, ";");
Если Части.Количество() < 2 Тогда
ТекстОшибки = СтрШаблон("Меньше двух полей в строке: %1", СтрокаФайла);
Возврат Неопределено;
КонецЕсли;
Результат = Новый Структура("Штрихкод, Количество, Цена", СокрЛП(Части[0]), 0, 0);
Результат.Количество = ЧислоИзТекста(Части[1]);
Если Части.Количество() > 2 Тогда // цена необязательна
Результат.Цена = ЧислоИзТекста(Части[2]);
КонецЕсли;
Возврат Результат;
КонецФункции
// Нечисловой текст даёт 0, а не исключение
Функция ЧислоИзТекста(Текст)
ТипЧисло = Новый ОписаниеТипов("Число");
Возврат ТипЧисло.ПривестиЗначение(СтрЗаменить(СокрЛП(Текст), ",", "."));
КонецФункции
// Фамилия, имя и отчество из одной строки.
// Было: Отчество = Слова[2]; — у сотрудника без отчества два слова.
Функция ЧастиФИО(ФИО) Экспорт
Результат = Новый Структура("Фамилия, Имя, Отчество", "", "", "");
// Здесь Ложь: двойной пробел не должен давать пустое слово
Слова = СтрРазделить(СокрЛП(ФИО), " ", Ложь);
Если Слова.Количество() > 0 Тогда
Результат.Фамилия = Слова[0];
КонецЕсли;
Если Слова.Количество() > 1 Тогда
Результат.Имя = Слова[1];
КонецЕсли;
Если Слова.Количество() > 2 Тогда
// Всё после имени — отчество, в том числе из двух слов
Слова.Удалить(1);
Слова.Удалить(0);
Результат.Отчество = СтрСоединить(Слова, " ");
КонецЕсли;
Возврат Результат;
КонецФункции
Условие проверки всегда на единицу больше максимального индекса: для Части[2] — Части.Количество() > 2. Другие функции — в справочнике Строковые функции 1С.
Запрос.Выполнить().Выгрузить()[0] работает, пока запрос что-то находит. Нет данных или прав на часть записей — таблица пустая. Первую запись берут через Выбрать() и Следующий(), другие проверки — в статье Пустой запрос 1С.
У пакета своя ловушка: ВыполнитьПакет() возвращает результаты в порядке запросов в тексте, включая служебные: для ПОМЕСТИТЬ — результат с числом записей, для УНИЧТОЖИТЬ — Неопределено. Результаты[3] ломается, как только в пакет добавили или убрали временную таблицу, например в типовом запросе после обновления. Надёжнее искать результат по колонкам.
// Сервер. Товар по артикулу.
// Было: Возврат Запрос.Выполнить().Выгрузить()[0].Ссылка;
// Артикула нет в базе — таблица пустая, строки с индексом 0 в ней нет.
Функция ТоварПоАртикулу(Артикул) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Артикул = &Артикул
| И НЕ Номенклатура.ЭтоГруппа
| И НЕ Номенклатура.ПометкаУдаления";
Запрос.УстановитьПараметр("Артикул", Артикул);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Ссылка;
КонецЕсли;
Возврат Справочники.Номенклатура.ПустаяСсылка();
КонецФункции
// Результат пакета по составу колонок, а не по номеру.
// Было: Товары = Запрос.ВыполнитьПакет()[3].Выгрузить();
// Из пакета убрали временную таблицу — результатов стало три, и [3] за границей;
// добавили — код молча берёт чужой результат.
// Вызов: РезультатСКолонками(Запрос.ВыполнитьПакет(), "Номенклатура, Склад, Количество")
Функция РезультатСКолонками(Результаты, ИменаКолонок) Экспорт
НужныеКолонки = СтрРазделить(ИменаКолонок, ",", Ложь);
Индекс = Результаты.ВГраница(); // с конца: итоговая выборка обычно последняя
Пока Индекс >= 0 Цикл
Результат = Результаты[Индекс];
Индекс = Индекс - 1;
Если Результат = Неопределено Тогда
Продолжить; // так в массиве выглядит запрос УНИЧТОЖИТЬ
КонецЕсли;
ВсеКолонкиЕсть = Истина;
Для Каждого ИмяКолонки Из НужныеКолонки Цикл
Если Результат.Колонки.Найти(СокрЛП(ИмяКолонки)) = Неопределено Тогда
ВсеКолонкиЕсть = Ложь;
Прервать;
КонецЕсли;
КонецЦикла;
Если ВсеКолонкиЕсть Тогда
Возврат Результат;
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Ищите по двум-трём характерным колонкам: у служебного результата ПОМЕСТИТЬ тоже есть колонка. Что вернул каждый запрос пакета, видно в бесплатной консоли запросов.
Строка таблицы значений отдаёт значение и по номеру колонки: СтрокаТаблицы[3] — четвёртая колонка. Так читают таблицы из файлов, где колонки без имён: у одного поставщика их четыре, у другого три, и [3] падает на первой строке. Колонки проверяют один раз до цикла. Подробнее о таблице — в статье Таблица значений 1С.
// Сервер. Таблица — прайс из файла, колонки по порядку: 0 — артикул,
// 1 — наименование, 2 — цена, 3 — остаток (есть не у всех поставщиков).
// Было: Позиция.Остаток = СтрокаПрайса[3];
// В прайсе без остатков три колонки — ошибка на первой же строке.
Функция ПозицииПрайса(Таблица) Экспорт
ЧислоКолонок = Таблица.Колонки.Количество();
Если ЧислоКолонок < 3 Тогда
ВызватьИсключение СтрШаблон(
"В прайсе колонок: %1. Нужны минимум артикул, наименование и цена", ЧислоКолонок);
КонецЕсли;
ЕстьОстаток = ЧислоКолонок > 3;
Позиции = Новый Массив;
Для Каждого СтрокаПрайса Из Таблица Цикл
Позиция = Новый Структура("Артикул, Наименование, Цена, Остаток");
Позиция.Артикул = СтрокаПрайса[0];
Позиция.Наименование = СтрокаПрайса[1];
Позиция.Цена = СтрокаПрайса[2];
Если ЕстьОстаток Тогда
Позиция.Остаток = СтрокаПрайса[3];
КонецЕсли;
Позиции.Добавить(Позиция);
КонецЦикла;
Возврат Позиции;
КонецФункции
Элементы.Товары.ТекущаяСтрока и элементы ВыделенныеСтроки — идентификаторы строк данных формы, а не индексы. Идентификатор закреплён за строкой и не обязан совпадать с её позицией, особенно после удалений, поэтому Объект.Товары[Элементы.Товары.ТекущаяСтрока] берёт чужую строку или выходит за границу — строку ищут через НайтиПоИдентификатору(). В динамическом списке ВыделенныеСтроки[0] падает, если список пуст, — см. Строки динамического списка 1С; так же ведёт себя пустой список значений в СписокВыбора.
// Модуль формы документа с табличной частью Товары.
&НаКлиенте
Процедура ЦенуТекущейСтрокиВоВсеСтроки(Команда)
// Было: Образец = Объект.Товары[Элементы.Товары.ТекущаяСтрока];
// ТекущаяСтрока — идентификатор строки, а не индекс. После удаления
// строк он может оказаться больше их количества, и обращение
// по нему выходит за границу.
Идентификатор = Элементы.Товары.ТекущаяСтрока;
Если Идентификатор = Неопределено Тогда
Возврат; // в таблице нет строк
КонецЕсли;
Образец = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
Если Образец = Неопределено Тогда
Возврат;
КонецЕсли;
// Цена текущей строки — во все строки с тем же товаром
// (пересчёт суммы упрощён: без скидок и НДС)
Для Каждого СтрокаТоваров Из Объект.Товары Цикл
Если СтрокаТоваров.Номенклатура = Образец.Номенклатура Тогда
СтрокаТоваров.Цена = Образец.Цена;
СтрокаТоваров.Сумма = СтрокаТоваров.Цена * СтрокаТоваров.Количество;
КонецЕсли;
КонецЦикла;
Модифицированность = Истина;
КонецПроцедуры
&НаКлиенте
Процедура УдалитьВыделенныеСтроки(Команда)
// Было: Объект.Товары.Удалить(Идентификатор) в цикле по ВыделенныеСтроки.
// Число Удалить() понимает как индекс, а не идентификатор:
// удаляются не те строки.
СтрокиКУдалению = Новый Массив;
Для Каждого Идентификатор Из Элементы.Товары.ВыделенныеСтроки Цикл
СтрокаТоваров = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
Если СтрокаТоваров <> Неопределено Тогда
СтрокиКУдалению.Добавить(СтрокаТоваров);
КонецЕсли;
КонецЦикла;
Для Каждого СтрокаТоваров Из СтрокиКУдалению Цикл
Объект.Товары.Удалить(СтрокаТоваров);
КонецЦикла;
Модифицированность = Истина;
КонецПроцедуры
Удаление идёт по найденным строкам, а не по числам. Больше о курсоре — в статье Таблица формы 1С.
Порядок разбора, если ошибка появилась сразу после обновления:
ВнешняяОбработка или ВнешнийОтчет — виновата внешняя печатная форма или обработка под старый релиз. Отключите её и отдайте автору.// Расширение конфигурации, модуль заимствованного общего модуля.
// Имена типовых методов условные — подставьте метод из текста ошибки.
// Вариант 1: копия типового метода с контролем изменений.
// Тело копируется целиком, своё — только между #Вставка и #КонецВставки.
&ИзменениеИКонтроль("ДанныеПервогоОснования")
Функция Расш1_ДанныеПервогоОснования(Основания)
#Вставка
Если Основания.Количество() = 0 Тогда
Возврат Неопределено; // пустой список оснований больше не роняет печать
КонецЕсли;
#КонецВставки
Основание = Основания[0];
Возврат Новый Структура("Ссылка, Представление", Основание, Строка(Основание));
КонецФункции
// Вариант 2: для пустых данных типовой код не вызывается.
&Вместо("ЗаполнитьТоварыПоОснованиям")
Процедура Расш1_ЗаполнитьТоварыПоОснованиям(ДокументОбъект, Основания)
Если Основания.Количество() = 0 Тогда
Возврат;
КонецЕсли;
ПродолжитьВызов(ДокументОбъект, Основания);
КонецПроцедуры
Если 1С изменит заимствованный метод в следующем релизе, расширение применится, но метод с &ИзменениеИКонтроль расширен не будет — так сказано в справке платформы, и исправление перестанет действовать. Поэтому после каждого обновления запускайте в конфигураторе, в окне «Расширения конфигурации», команду «Конфигурация → Проверка возможности применения». Об аннотациях — в статье Расширение конфигурации 1С.
Индекс <= Количество(). Пропускает индекс, равный количеству; нужно <.Если Части.Количество() > 0 безопасна только Части[0].Товары[Товары.Количество() - Сч] в цикле Для Сч = 1 По Товары.Количество() после удаления перескакивает строку, а к концу даёт отрицательный индекс. Запомните количество до цикла.Свернуть()) или отсортированной копии применили к исходной табличной части.Попытка вокруг обращения. Сообщение пропадает, а загрузка «успешна» без половины строк. Исключение ловите только, чтобы записать причину в журнал регистрации.Размер коллекции известен только при выполнении, а код с индексом за границей массива синтаксически правилен. Её ловят проверки размера и остановка по ошибке в отладчике.
У него другие данные: меньше строк, файл без колонки, ничего не выделено. Или права: при ограничении доступа на уровне записей запрос ВЫБРАТЬ РАЗРЕШЕННЫЕ возвращает ему меньше строк, и Выгрузить()[0] падает только у него, — см. RLS в 1С.
Нет, строка не коллекция. Сред() с начальным номером меньше единицы начинает с первого символа, а за концом строки возвращает пустую строку. СтрНайти() с позицией вне строки даёт другую ошибку — «Неверное значение параметра». Индекс выходит за границу уже в массиве после СтрРазделить().
Тот же выход за границу у COMSafeArray — массива для обмена с COM-объектами, например с Excel. Нижняя граница у него не обязательно 0: допустимые индексы возвращают GetLowerBound() и GetUpperBound().
Если ошибка появилась после обновления, а в базе несколько расширений и внешних печатных форм, разбор быстрее отдать программисту: он найдёт код, который обращается к пустой коллекции, и исправит его расширением без снятия с поддержки. Опишите задачу на странице разместить задачу и приложите полный текст ошибки — оценка бесплатная, ответ в течение рабочего дня. Направления работ — в разделе услуги по доработке 1С.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.