Выход за границы диапазона в коде 1С: где искать строку и как переписать обращение
Содержание
Ошибка «индекс находится за границами массива» в 1С означает одно: код обратился к элементу коллекции по номеру, которого в ней нет. Например, в массиве три элемента с индексами 0, 1 и 2, а программа просит Массив[3]. Платформа 1С 8.3 выводит это сообщение как Индекс находится за границами диапазона — слово «массив» в нём не встречается, хотя чаще всего речь именно о массиве.
Частое заблуждение — считать, что «испортилась база» и пора запускать тестирование и исправление. Почти всегда виноват код: типовой модуль, расширение, внешняя обработка или печатная форма. Данные лишь провоцируют сбой — например, пустая табличная часть, которую код считал заполненной.
Откуда берётся выход за границы коллекции
В 1С индексация начинается с нуля. Последний элемент коллекции из N элементов имеет индекс N - 1. Всё, что меньше нуля или больше этой границы, вызывает ошибку.
Индексом можно обратиться к разным объектам:
- 📦
МассивиФиксированныйМассив; - 📋
ТаблицаЗначенийи её строке (по номеру колонки); - 🧾 табличной части объекта и коллекции данных формы;
- 📑
СписокЗначений; - 🔗 массиву результатов пакетного запроса.
Механика у всех одна. Ошибку дают пять типовых ситуаций:
- Граница цикла на единицу больше. Написали
Для Инд = 0 По Массив.Количество() ЦиклвместоКоличество() - 1. Последняя итерация лезет за край. - Обращение к первому элементу пустой коллекции.
Выборка = Результат.Выгрузить()[0]при пустом результате запроса. - Удаление элементов в прямом цикле. После каждого
Удалить()коллекция короче, а счётчик идёт дальше. - Перепутаны индекс и идентификатор. Номер строки формы или поле
НомерСтрокииспользуют как индекс. - Разбор строки без проверки.
Части = СтрРазделить(Строка, ";"), затемЧасти[1], хотя разделителя в строке не было.
Пятый случай коварен: код годами работает на «правильных» данных и падает на первой нестандартной строке из файла обмена.
Помимо чтения, индекс проверяют и методы изменения коллекции. Массив.Удалить(Инд) и Массив.Установить(Инд, Значение) требуют, чтобы элемент с таким номером уже существовал. Добавить новый элемент в конец можно только через Добавить(): запись Массив[Массив.Количество()] = Значение даёт ту же ошибку.
Ещё одна деталь — конструктор Новый Массив(3). Он создаёт массив из трёх элементов со значением Неопределено, а не пустой массив «на три места». Если затем вызвать Добавить(), новый элемент получит индекс 3, а не 0. Код, который ждал данные в Массив[0], получит Неопределено и упадёт уже на следующем шаге с другим сообщением.
Как найти строку с ошибкой в режиме Предприятия
Сначала выясните, где именно упал код. Без этого любые действия — гадание.
Подробный текст сообщения
В окне ошибки откройте подробное описание (кнопка или ссылка «Подробно»). Там будет строка такого вида:
{Обработка.ЗагрузкаПрайса.Форма.Форма.Форма(214)}: Индекс находится за границами диапазона
Цена = Колонки[5];
В фигурных скобках — объект и модуль, в круглых — номер строки. Ниже платформа часто показывает саму строку кода. Это уже половина диагностики.
По первому слову в пути понятно, чей код сломался:
| Начало пути | Чей код | Кто исправляет |
|---|---|---|
ОбщийМодуль.…, Документ.…, Справочник.… | типовая конфигурация или доработка | разработчик или обновление релиза |
ВнешняяОбработка.…, ВнешнийОтчет.… | внешний файл .epf/.erf | автор обработки |
| объект с префиксом расширения | расширение конфигурации | автор расширения |
Журнал регистрации
Если пользователь закрыл окно, текст ошибки остаётся в журнале. В БП 3.0 и ЗУП 3.1 он открывается через Администрирование → Обслуживание → Журнал регистрации. Отберите события по уровню «Ошибка» и периоду, когда случился сбой. В комментарии события будет тот же путь к модулю.
Отладчик с остановкой по ошибке
Когда номер строки известен, а причина нет, нужен отладчик. В Конфигураторе:
- Включите
Отладка → Остановка по ошибке…и поставьте флажок остановки. - В поле фильтра впишите фрагмент
за границами диапазона, чтобы не ловить другие исключения. - Запустите клиент через
Отладка → Начать отладку. - Повторите действие пользователя. Отладчик остановится на проблемной строке.
- В табло посмотрите
Количество()коллекции и значение индекса.
Ошибка в фоновом задании или обмене
Регламентные задания и обмены работают без окна, сообщение пользователь не видит. Признак — «зависшая» загрузка или не выполненное задание. Текст ошибки с путём к модулю ищите в том же журнале регистрации, отобрав события по имени задания или обмена. Чтобы поймать такой сбой в отладчике, проще всего вызвать ту же процедуру вручную из обработки на копии базы: тогда она выполнится в обычном сеансе и остановится на ошибке.
Если ошибка воспроизводится только на рабочей базе, а отладку там включать нельзя, выгрузите копию в файловый вариант и ловите сбой на ней.
Табличные части: удаление строк и путаница с номерами
Самый частый источник ошибки в прикладном коде — табличные части документов.
Удаление в прямом цикле
Вот классический сбой:
// Ошибка: граница цикла вычислена один раз, а строки удаляются
Для Инд = 0 По Товары.Количество() - 1 Цикл
Если Товары[Инд].Количество = 0 Тогда
Товары.Удалить(Инд);
КонецЕсли;
КонецЦикла;
Граница По вычисляется один раз перед стартом цикла. Было 10 строк, удалили 2 — на Инд = 8 строки уже нет. Правильно идти с конца:
Инд = Товары.Количество() - 1;
Пока Инд >= 0 Цикл
Если Товары[Инд].Количество = 0 Тогда
Товары.Удалить(Инд);
КонецЕсли;
Инд = Инд - 1;
КонецЦикла;
При обратном проходе удаление строки не сдвигает те, что ещё впереди по ходу цикла.
Для Каждого с удалением внутри тоже ошибочен, хотя исключения не даёт: он молча пропускает строки. Это хуже, потому что ошибку никто не видит.
Если условие простое, цикл вообще не нужен. Найдите строки через НайтиСтроки() и удаляйте их по ссылкам на строки, а не по номерам.
НомерСтроки — не индекс
У строки табличной части есть поле НомерСтроки. Оно начинается с единицы, а индекс — с нуля. Код Товары[Строка.НомерСтроки] всегда смотрит на следующую строку. А на последней строке падает с ошибкой.
Нужен индекс — берите Товары.Индекс(Строка). Нужна сама строка — работайте со ссылкой на неё, без номеров.
Строка таблицы значений по номеру колонки
К строке ТаблицыЗначений можно обратиться по номеру колонки: Строка[3]. Так часто пишут загрузку из Excel или CSV. Если в файле меньше колонок, чем ожидал код, получите ту же ошибку. Перед чтением проверяйте ТЗ.Колонки.Количество(), а лучше обращайтесь к колонкам по именам.
Формы и динамические списки
На управляемых формах ошибка возникает там, где путают три разные вещи: индекс, идентификатор строки и выделение.
ТекущаяСтрока табличной части формы
Для табличной части на форме свойство Элементы.Товары.ТекущаяСтрока возвращает идентификатор строки, а не её индекс. Идентификатор — служебное число, он не равен позиции строки, особенно после удалений и сортировки.
// Ошибка: идентификатор используется как индекс
ДанныеСтроки = Объект.Товары[Элементы.Товары.ТекущаяСтрока];
// Правильно
ИдСтроки = Элементы.Товары.ТекущаяСтрока;
Если ИдСтроки <> Неопределено Тогда
ДанныеСтроки = Объект.Товары.НайтиПоИдентификатору(ИдСтроки);
КонецЕсли;
Ещё проще взять Элементы.Товары.ТекущиеДанные — это данные активной строки. Но и его проверяйте на Неопределено: в пустой таблице активной строки нет.
Выделенные строки динамического списка
У динамического списка Элементы.Список.ВыделенныеСтроки — массив ключей выделенных строк. Если пользователь ничего не выделил, массив пуст, и ВыделенныеСтроки[0] падает.
Выделенные = Элементы.Список.ВыделенныеСтроки;
Если Выделенные.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Выделите хотя бы одну строку.");
Возврат;
КонецЕсли;
Для динамического списка ТекущаяСтрока — это ссылка на объект или ключ записи, а не число. Использовать её как индекс нельзя вовсе.
Клиент, сервер и устаревшие индексы
Ещё одна ловушка — индекс, запомненный до вызова сервера. Клиент сохранил номер строки 5, вызвал серверную процедуру, а та пересобрала табличную часть. Вернулись на клиент — строк уже 3.
Передавайте на сервер идентификаторы строк, а не индексы, и после серверного вызова заново получайте данные.
Запросы, пакеты и временные таблицы
Пустой результат запроса
// Ошибка на пустой выборке
Цена = Запрос.Выполнить().Выгрузить()[0].Цена;
Если записей нет, таблица пуста, элемента с индексом 0 нет. Надёжный вариант — через выборку:
Выборка = Запрос.Выполнить().Выбрать();
Цена = 0;
Если Выборка.Следующий() Тогда
Цена = Выборка.Цена;
КонецЕсли;
Если нужна только проверка наличия данных, используйте Результат.Пустой() до выгрузки.
Номер результата в пакете
Запрос.ВыполнитьПакет() возвращает массив результатов по каждому запросу пакета. Сюда входят и запросы ПОМЕСТИТЬ, создающие временные таблицы, и УНИЧТОЖИТЬ.
Пример. В пакете было четыре запроса, итог брали как Результаты[3]. Разработчик удалил ненужную временную таблицу — запросов стало три. Индекс 3 теперь за границей.
Два способа обезопасить себя:
- 🔚 брать последний результат как
Результаты[Результаты.ВГраница()]; - 🧩 вынести нужный запрос последним и не завязываться на порядковые номера промежуточных.
ВГраница() у пустого массива возвращает -1, поэтому цикл Для Инд = 0 По Массив.ВГраница() по пустому массиву не выполнится ни разу. Это удобная и безопасная форма записи.
Выгрузка колонки
Результат.Выгрузить().ВыгрузитьКолонку("Ссылка") даёт массив. Обращение Массив[0] без проверки количества — тот же случай, что и с пустой таблицей.
Что делать пользователю без доступа к коду
Бухгалтер или кладовщик код не правит, но решить проблему может.
Что помогает, а что нет:
| Действие | Когда помогает | Риск |
|---|---|---|
| Обновить внешнюю обработку | путь начинается с ВнешняяОбработка | нет |
| Отключить расширение | ошибка в объекте расширения | пропадут функции расширения |
| Обновить конфигурацию | ошибка в типовом модуле, есть свежий релиз | нужна резервная копия |
| Тестирование и исправление | почти никогда при этой ошибке | долго, нужен монопольный режим |
Тестирование и исправление информационной базы чинит физическую структуру данных. Логическую ошибку в алгоритме оно не исправит. Запускать его ради этого сообщения нет смысла.
Как писать код, который не выходит за границы
Несколько правил закрывают почти все сценарии.
- Проверяйте количество перед обращением. Любое
[0],[1]должно стоять послеЕсли Коллекция.Количество() > N. - Используйте
ВГраница()в циклах. ЗаписьДля Инд = 0 По Массив.ВГраница()не ошибается на единицу. - Удаляйте только с конца. Обратный проход — единственный безопасный способ удалить элементы по индексу.
- Предпочитайте
Для Каждогодля чтения. Если номер не нужен, индексы не нужны тоже. - Обращайтесь к колонкам по имени. Номер колонки ломается при первом изменении файла или запроса.
- Не храните индексы между вызовами. Для строк формы — идентификаторы, для объектов — ссылки.
- Проверяйте результат
СтрРазделить. Сколько частей вернётся, зависит от данных, а не от ожиданий.
Пример разбора строки с проверкой:
Части = СтрРазделить(СтрокаФайла, ";", Истина);
Если Части.Количество() < 3 Тогда
ТекстОшибки = СтрШаблон("Строка %1: ожидалось 3 поля, получено %2",
Формат(НомерСтроки, "ЧГ=0"), Формат(Части.Количество(), "ЧГ=0"));
Сообщить(ТекстОшибки);
Продолжить;
КонецЕсли;
Артикул = Части[0];
Цена = Части[2];
Третий параметр Истина в СтрРазделить сохраняет пустые части. Это значение по умолчанию, но его полезно писать явно: если кто-то поменяет его на Ложь, строка "А;;10" даст две части вместо трёх, и Части[2] упадёт.
Не прячьте ошибку в Попытка … Исключение без обработки. Код перестанет падать, но начнёт молча терять данные: строка не загрузилась, а никто об этом не узнал.