Ошибка «Индекс находится за границами диапазона»: как найти строку кода и закрыть обращение
Содержание
Ошибка индекса за пределами массива в 1С возникает, когда код обращается к элементу коллекции по номеру, которого нет: Массив[5] при пяти элементах, Таблица[0] при пустой таблице, отрицательный индекс. Платформа пишет Индекс находится за границами диапазона и прерывает выполнение. Исправить сбой можно только в коде: найти строку, где берётся неверный индекс, и добавить проверку количества или переписать цикл.
Номер нужной строки почти всегда есть в самом сообщении. Нажмите «Подробно» в окне ошибки: там будет запись вида {ОбщийМодуль.РаботаСФайлами.Модуль(214)}: Индекс находится за границами диапазона. В скобках — модуль и номер строки. С этого и начинается диагностика.
Как платформа нумерует элементы и когда срабатывает ошибка
Все индексируемые коллекции 1С нумеруются с нуля. Это касается Массив, ФиксированныйМассив, СписокЗначений, ТаблицаЗначений, строк табличной части объекта и коллекций данных формы.
Отсюда простое правило:
- 🔢 первый элемент — индекс
0; - 🔚 последний элемент —
Количество() - 1, его же возвращает методВГраница(); - 🕳️ у пустого массива
Количество()равно0, аВГраница()возвращает-1; - ⛔ любой индекс меньше
0или большеВГраница()вызывает исключение.
Ошибка возникает в момент обращения, а не раньше. Платформа не проверяет код при компиляции модуля: синтаксический контроль пройдёт, конфигурация сохранится, а сбой появится только на тех данных, где коллекция оказалась короче ожидаемого. Поэтому типичная история такая: обработка год работала, а потом пришёл документ без строк, и всё упало.
Исключение одно и то же для чтения, записи и удаления. Строки Значение = Массив[3], Массив[3] = 10, Массив.Удалить(3) и Массив.Вставить(7, Значение) при неподходящем индексе дают одинаковый текст ошибки.
Если в тексте ошибки другая формулировка — Поле объекта не обнаружено или Значение не является значением объектного типа — это другая проблема. Первая означает обращение к несуществующему свойству или ключу структуры, вторая — что вместо коллекции пришло Неопределено.
Где в коде чаще всего ломается индексация
Ниже — сценарии, которые встречаются в отладке снова и снова. Узнать свой случай обычно удаётся по строке, на которую указывает сообщение.
Цикл до Количество() вместо ВГраница()
Самая частая опечатка — лишний проход в конце:
// Ошибка: последний проход берёт Товары[Количество()]
Для Индекс = 0 По Товары.Количество() Цикл
Сумма = Сумма + Товары[Индекс].Сумма;
КонецЦикла;
Правильно — По Товары.Количество() - 1 или По Товары.ВГраница(). Ещё надёжнее — Для Каждого Строка Из Товары Цикл, там индекса нет вообще.
Удаление элементов в прямом цикле
Граница цикла Для … По вычисляется один раз, при входе. Если внутри удалять элементы, коллекция короче, а счётчик продолжает идти до старой границы:
// Ошибка: после первого удаления последний индекс уже не существует
Для Индекс = 0 По Массив.ВГраница() Цикл
Если Массив[Индекс] = 0 Тогда
Массив.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Как удалять безопасно, показано в разделе про исправление.
Взяли первую строку, а строк нет
- 📄
Объект.Товары[0]в документе, у которого табличная часть пустая; - 🔍
Запрос.Выполнить().Выгрузить()[0]при пустом результате запроса; - 📋
Элементы.Список.ВыделенныеСтроки[0], когда пользователь ничего не выделил; - 📑
ТаблицаЗначений.ВыгрузитьКолонку("Ссылка")[0]от пустой таблицы.
Разбор строк и файлов
СтрРазделить(Строка, ";") возвращает столько элементов, сколько частей нашлось. Код, рассчитанный на пять колонок CSV, падает на строке, где их четыре: например, на последней пустой строке файла или на строке, где поставщик забыл разделитель.
Тот же эффект дают массивы из JSON, ответы веб-сервисов и выгрузки из внешних систем: форма данных зависит не от вас.
Пакетный запрос
Запрос.ВыполнитьПакет() возвращает массив результатов по одному на каждый запрос пакета, включая запросы с ПОМЕСТИТЬ и УНИЧТОЖИТЬ. Добавили в начало пакета ещё одну временную таблицу — все индексы сдвинулись, и Результаты[3] в коротком пакете уже не существует.
Новый Массив с размером
Новый Массив(5) создаёт массив из пяти элементов со значением Неопределено. Если потом вызвать Добавить(), данные лягут в индексы 5, 6 и дальше, а первые пять останутся пустыми. Ошибка индекса тут не всегда, зато соседний код, который считает «реальные» элементы, начинает промахиваться.
Как поймать неверный индекс отладчиком
Сообщение с номером строки показывает, где упало. Отладчик показывает, почему: какой был индекс и сколько было элементов.
Шаг 1. Включите остановку по ошибке
- Откройте конфигурацию в Конфигураторе.
- В меню
ОтладкавыберитеОстановка по ошибке. - Включите остановку и, при желании, задайте фильтр по тексту
за границами диапазона, чтобы отладчик не останавливался на других исключениях.
Теперь отладчик сам встанет на строке, где выброшено исключение, даже если она обёрнута в Попытка … Исключение.
Шаг 2. Запустите отладку и повторите действие
Нажмите F5 (Отладка → Начать отладку) и выберите нужный клиент. В открывшемся сеансе повторите действие, которое вызывало ошибку: проведите тот же документ, загрузите тот же файл.
Шаг 3. Посмотрите значения в момент сбоя
Когда выполнение остановилось:
- выделите в коде имя коллекции и нажмите
Shift+F9— откроется «Вычислить выражение»; введитеМассив.Количество()и переменную-индекс; - откройте стек вызовов (
Ctrl+Alt+C), чтобы понять, из какой процедуры пришёл вызов и с какими параметрами; - добавьте нужные переменные в табло (
Отладка → Табло), если будете проходить цикл по шагам.
Для пошагового прохода ставьте точку останова на начало цикла (F9) и шагайте F10 (через вызов) или F11 (внутрь вызова).
Если код выполняется на сервере
В файловой базе серверный код отлаживается сразу. В клиент-серверной нужно, чтобы сервер 1С был запущен с разрешённой отладкой: в командной строке службы агента сервера должен стоять ключ -debug.
Чтобы добавить ключ, службу агента сервера придётся перезапустить. Все сеансы пользователей при этом оборвутся — делайте это вне рабочего времени и предупредите людей заранее. На рабочем сервере отладку лучше вообще не включать: снимите копию базы на тестовый сервер.
Без доступа к Конфигуратору
Пользователю или администратору без прав разработчика хватит трёх действий:
- Нажать в окне ошибки «Подробно» и скопировать текст целиком.
- Посмотреть, чей модуль указан: типовой, расширения (имя расширения видно в пути) или внешней обработки.
- Найти ту же запись в журнале регистрации (
Администрирование → Поддержка и обслуживание → Журнал регистрации), отбор по уровню «Ошибка» — там видно пользователя, время и объект.
Если модуль из расширения или внешней обработки, временно отключите его и повторите действие. Ошибка пропала — передавайте текст тому, кто это расширение писал.
Как исправить обращение по индексу и защитить код
Способов три, от самого правильного к запасному.
| Способ | Когда подходит | Минусы |
|---|---|---|
Убрать индекс: Для Каждого, Найти, НайтиСтроки | Нужен перебор или поиск, а не конкретная позиция | Нельзя удалять элементы внутри Для Каждого |
| Проверить границы перед обращением | Нужен именно элемент с номером: первый, последний, N-й | Проверку легко забыть в соседней ветке |
Попытка … Исключение | Внешние данные, которые невозможно проверить заранее | Скрывает настоящую причину, медленнее |
Проверка количества перед обращением
Выборка = Запрос.Выполнить();
Если Выборка.Пустой() Тогда
Возврат Неопределено;
КонецЕсли;
Таблица = Выборка.Выгрузить();
ПерваяСтрока = Таблица[0];
Для одного значения из запроса ещё проще не выгружать таблицу, а взять выборку:
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Цена = Выборка.Цена;
КонецЕсли;
Для выделенных строк формы:
Выделенные = Элементы.Список.ВыделенныеСтроки;
Если Выделенные.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Выделите хотя бы одну строку.");
Возврат;
КонецЕсли;
Безопасное удаление элементов
Обход с конца: удаление элемента сдвигает только те, что правее, а их цикл уже прошёл.
Индекс = Массив.ВГраница();
Пока Индекс >= 0 Цикл
Если Массив[Индекс] = 0 Тогда
Массив.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Для таблицы значений нагляднее собрать строки в отдельный массив, а удалить потом:
КУдалению = Новый Массив;
Для Каждого Строка Из Таблица Цикл
Если Строка.Количество = 0 Тогда
КУдалению.Добавить(Строка);
КонецЕсли;
КонецЦикла;
Для Каждого Строка Из КУдалению Цикл
Таблица.Удалить(Строка);
КонецЦикла;
Разбор строки с проверкой частей
Части = СтрРазделить(ТекстСтроки, ";", Ложь);
Если Части.Количество() < 5 Тогда
Сообщить(СтрШаблон("Строка %1 пропущена: частей %2 из 5",
Формат(НомерСтроки, "ЧГ=0"), Формат(Части.Количество(), "ЧГ=0")));
Продолжить;
КонецЕсли;
Артикул = СокрЛП(Части[0]);
Количество = Число(Части[3]);
Третий параметр Ложь отбрасывает пустые части: лишний разделитель в конце строки не создаст пустой элемент. Но учтите: если пустое поле в середине — законная ситуация, этот параметр сдвинет колонки. Тогда оставьте значение по умолчанию и проверяйте каждую часть отдельно.
Когда уместна Попытка
Попытка ставьте вокруг кода, который читает чужие данные: ответ сервиса, произвольный JSON. В блоке Исключение запишите в журнал регистрации ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) и решите, что делать дальше. Пустой блок Исключение — худший вариант: ошибка исчезнет из вида, а данные останутся неполными.
Особенности списка значений и таблицы значений
У этих двух коллекций есть свои способы обращения, и не все они бросают исключение.
СписокЗначений
- 🔢
Список[0]иСписок.Получить(0)возвращают элемент списка (ЭлементСпискаЗначений), а не само значение. Значение берут через.Значение. - 🔍
Список.НайтиПоЗначению(Значение)возвращает элемент илиНеопределено— проверяйте результат, а не индекс. - 🧮
Список.Индекс(Элемент)даёт позицию существующего элемента; полученный индекс пересчитывайте после каждого удаления. - 🗑️
Удалить()принимает и элемент, и индекс. Передача элемента избавляет от сдвига номеров.
ТаблицаЗначений
- 🔍
Таблица.Найти(Значение, "Колонка")возвращает строку илиНеопределено; - 📋
Таблица.НайтиСтроки(Новый Структура("Колонка", Значение))возвращает массив — его длину тоже надо проверить, прежде чем брать[0]; - 🧱
Таблица.Колонки[Индекс]— та же нумерация с нуля; надёжнее обращаться к колонке по имени:Таблица.Колонки.Найти("Сумма"); - 🔃 после
Сортировать()индексы строк меняются — запомненные заранее номера больше не указывают на те же строки.
Табличная часть объекта и данные формы
Табличная часть документа (Объект.Товары) индексируется так же, с нуля. Поле НомерСтроки начинается с единицы. Отсюда классическая ошибка Объект.Товары[Строка.НомерСтроки] — на последней строке индекс выходит за границу. Верно: Объект.Товары[Строка.НомерСтроки - 1], а лучше обращаться к самой строке без индекса.
В форме строки коллекции идентифицируются не индексом, а идентификатором: Объект.Товары.НайтиПоИдентификатору(Элементы.Товары.ТекущаяСтрока). Индекс строки в форме может не совпасть с индексом на сервере, если таблица отфильтрована или отсортирована на экране.
Как не допускать таких ошибок в конфигурации
- ✅ Перебор пишите через
Для Каждого. Цикл по индексу — только когда номер действительно нужен. - ✅ Перед любым
[0]— проверкаКоличество()илиПустой(). Можно вынести в общую функцию «ПервыйЭлемент(Коллекция)», которая возвращаетНеопределенодля пустой коллекции. - ✅ Пакетные запросы: результаты разбирайте сразу после выполнения в переменные с понятными именами, а не по номерам в середине кода.
- ✅ Загрузку внешних файлов проверяйте на граничных данных: пустой файл, файл из одной строки, строка без разделителей, лишняя пустая строка в конце.
- ✅ Проверка кода: в Конфигураторе есть расширенная проверка (
Конфигурация → Проверка конфигурации), в 1С:EDT — встроенные проверки качества кода. Ошибку индекса они не поймают, но часть соседних проблем покажут. - ✅ Изменения расширений тестируйте на копии базы после каждого обновления типовой конфигурации.
Хороший тест для обработки — прогнать её на документе без строк и на документе из одной строки. Большинство ошибок индекса проявляются уже на этих двух случаях.
Частые вопросы
Почему ошибка появилась после обновления типовой конфигурации, хотя код не менялся?
Помогает ли очистка кэша или тестирование и исправление базы?
Можно ли просто обернуть строку в Попытку?
Почему Для Каждого не падает, а пропускает элементы при удалении?
Отрицательный индекс считается с конца, как в других языках?
ВГраница().