Ошибка «Индекс находится за границами массива»: где ломается код и как это найти

· 9 мин

Содержание

Код обратился к элементу массива, строки таблицы или коллекции по номеру, которого там нет. Платформа остановила выполнение и показала Индекс находится за границами массива. Чаще всего виноваты нумерация с нуля, пустой список или удаление элементов прямо во время обхода.

Пользователь видит это окно в самый неудобный момент: нажал «Провести», открыл отчёт, запустил обработку. Программист получает в сообщении имя модуля и номер строки. Ниже — что означает ошибка, как быстро найти виновника и как исправить типовые случаи.

Как в 1С устроены индексы и где проходит граница

Массив, таблица значений, список значений, строки табличной части — всё это упорядоченные коллекции. Доступ к элементу идёт по индексу в квадратных скобках: Массив[2].

Главное правило: индексы начинаются с нуля. В массиве из пяти элементов допустимы номера от 0 до 4. Обращение к Массив[5] и даёт ошибку.

Что проверяемКак получитьЗначение для массива из 5 элементов
Число элементовМассив.Количество()5
Последний допустимый индексМассив.ВГраница()4
Индекс первого элемента—0
Индекс для пустого массиваВГраница()-1

У пустого массива ВГраница() возвращает -1, и любое Массив[0] на нём сразу падает.

Не путайте индекс и номер строки. У строки табличной части свойство НомерСтроки начинается с единицы, а индекс в Объект.Товары[Индекс] — с нуля. Первая строка имеет НомерСтроки = 1, но индекс 0.

Типовые причины: от самых частых к редким

Ошибка обычно возникает в одной из следующих ситуаций.

  • 🔢 Счёт с единицы. Цикл Для Индекс = 1 По Массив.Количество() доходит до индекса, равного количеству, — а такого элемента нет.
  • 📭 Пустая коллекция. Код берёт Результат[0], не проверив, что запрос или отбор вообще что-то вернул.
  • ✂️ Удаление в цикле. Элементы удаляются в прямом порядке, коллекция сокращается, а счётчик ещё помнит старый размер.
  • 🔪 Разбор строки. СтрРазделить вернула один элемент вместо двух, потому что разделителя в исходной строке не оказалось.
  • 📍 Устаревший индекс. Индекс запомнили, потом в коллекцию что-то добавили или убрали, и сохранённое число уже указывает не туда.
  • 🧩 Чужой код. Внешняя печатная форма, обработка или расширение рассчитывают на структуру данных, которой в вашей базе нет.

Цикл с лишним шагом

Классическая ошибка начинающего:

Для Индекс = 1 По Массив.Количество() Цикл
    Сообщить(Массив[Индекс]);
КонецЦикла;

На последнем шаге Индекс равен Количество(), то есть выходит за границу. Правильно так:

Для Индекс = 0 По Массив.ВГраница() Цикл
    Сообщить(Массив[Индекс]);
КонецЦикла;

Если индекс не нужен, обходите коллекцию через Для Каждого Элемент Из Массив Цикл — выйти за границу там невозможно.

Удаление элементов при обходе

Допустим, из массива надо убрать все пустые значения. Прямой цикл ломается: после Удалить(2) бывший элемент 3 становится элементом 2 и пропускается, а в конце счётчик уходит за новую границу.

// Неправильно
Для Индекс = 0 По Массив.ВГраница() Цикл
    Если Не ЗначениеЗаполнено(Массив[Индекс]) Тогда
        Массив.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

Правильно идти с конца к началу: удалённый элемент не сдвигает те, что ещё предстоит проверить.

Индекс = Массив.ВГраница();
Пока Индекс >= 0 Цикл
    Если Не ЗначениеЗаполнено(Массив[Индекс]) Тогда
        Массив.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Индекс стартует с исходной верхней границы и только уменьшается. Удаление сдвигает лишь элементы правее текущего, а их цикл уже проверил. Для таблицы значений и табличной части приём тот же: начните с ТЧ.Количество() - 1 и идите вниз до нуля.

Индекс = ТЧ.Количество() - 1;
Пока Индекс >= 0 Цикл
    Если Не ЗначениеЗаполнено(ТЧ[Индекс].Номенклатура) Тогда
        ТЧ.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Пустой результат запроса

Другой частый сценарий: код ожидает ровно одну запись.

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    Цена = Выборка.Цена;
КонецЕсли;

Если вместо выборки берут выгрузку в таблицу значений и сразу читают Таблица[0].Цена, то при пустом результате получаем нашу ошибку. Проверка Таблица.Количество() > 0 стоит одной строки.

Как найти место ошибки по тексту сообщения

Раньше чем править, нужно понять, какая именно строка упала. Разберём по шагам.

  1. Прочитайте сообщение целиком. Нажмите ссылку «Подробно» в окне ошибки: там указаны имя модуля и номер строки в фигурных скобках. Те же сведения видны в отладчике и в журнале регистрации.
  2. Откройте модуль на этой строке. В Конфигураторе: поиск по конфигурации или переход из списка модулей объекта. Часто видно сразу, какой массив читается.
  3. Поставьте точку останова на строку выше и повторите действие в режиме отладки.
  4. Посмотрите значения. Вычислите Массив.Количество() и значение индекса — расхождение видно за секунду.
  5. Найдите, откуда взялась пустота или лишняя единица. Это уже вопрос логики, а не синтаксиса.

Отлаживайте на копии базы. Повторять проведение документов или закрытие месяца на рабочей базе ради поиска строки опасно: ошибка может сработать в середине операции.

Если строки в сообщении нет

В обычном приложении пользователю часто показывают короткий текст без модуля. Тогда помогут три источника:

  • 🧾 Журнал регистрации. Откройте его и отфильтруйте по пользователю и времени, события с уровнем «Ошибка». В записи часто есть место возникновения.
  • 🖥️ Режим запуска с отладкой. Тот же сценарий воспроизводят у себя, подключив отладчик к клиенту.
  • 📸 Что делал пользователь. Какой документ, какая кнопка, какие данные в табличной части. Без этого воспроизвести ошибку трудно.

Заодно проверьте, подключены ли расширения и внешние обработки. Если ошибка исчезает при их отключении, виновник найден: проблемный код внутри них.

Что делать пользователю без доступа к коду

Вы бухгалтер или кассир, программиста рядом нет. Исправить код вы не можете, но можете сузить поиск и передать разработчику нужное.

Вот что стоит выяснить:

  • Воспроизводится ли ошибка каждый раз или только с определённым документом.
  • Падает ли то же действие в другом документе, у другого контрагента, за другой период.
  • Есть ли ошибка у соседнего пользователя на том же сервере.
  • Менялось ли что-то накануне: обновление релиза, подключение расширения, загрузка новой внешней печатной формы.

Если ошибка только на одном документе — дело в его данных. Например, пустая табличная часть, в которой код ожидает хотя бы одну строку. Проверьте заполнение табличных частей и реквизитов документа: добавьте недостающие строки и попробуйте провести снова.

Если ошибка у всех и появилась после обновления — это дефект кода. Нужно сообщить разработчику (или в поддержку конфигурации), приложив точный текст сообщения, названия документа и действия, а также скриншот. Обычно такой случай чинится исправленным релизом.

Если ошибка в печатной форме, попробуйте распечатать другую форму того же документа. Сработала — проблема во внешней форме, подключённой в базу. Отключите её или замените штатной.

Сценарии из практики и их решения

Таблица ниже показывает, как по контексту быстро выйти на причину.

Где выскочила ошибкаВероятная причинаЧто сделать
При проведении документаКод читает первую строку пустой табличной частиДобавить проверку количества строк до обращения
При формировании отчётаОтбор вернул пусто, а код берёт [0]Обработать случай «данных нет»
В печатной формеМакет рассчитан на другое число колонок или строкИсправить форму либо вернуть штатную
При разборе файла или строкиНет разделителя, строка короче ожидаемойПроверить длину массива после СтрРазделить
После обновленияСтарое расширение переопределяет изменившийся методОбновить или отключить расширение
При удалении строк в циклеПрямой обход с удалениемИдти с конца к началу

Разбор строки на части

Пример из обмена данными: приходит строка Иванов;Москва, а код ждёт три поля: фамилию, город и телефон.

Части = СтрРазделить(Строка, ";");
Если Части.Количество() < 3 Тогда
    ВызватьИсключение "Неверный формат строки: ожидалось 3 поля, получено "
        + Части.Количество();
КонецЕсли;
Город = Части[1];
Телефон = Части[2];

Лучше упасть с понятным текстом, чем с общим «индекс за границами». Через полгода никто не вспомнит, какой именно массив имелся в виду.

Табличная часть и выбранная строка

Когда пользователь жмёт кнопку над таблицей, код берёт Элементы.Товары.ТекущаяСтрока. Если строка не выбрана, значение будет Неопределено, и дальнейшие обращения к данным строки приведут к ошибкам. Проверяйте результат до работы с ним.

Идентификатор текущей строки в управляемой форме — это не индекс, а внутренний номер строки. Получить саму строку по нему можно методом НайтиПоИдентификатору, а не обращением в квадратных скобках. Многие ошибки появляются именно из-за подмены одного другим.

Профилактика: привычки, которые убирают ошибку навсегда

Несколько правил, которые разработчик применяет почти автоматически.

  • ✅ Перед [0] проверяйте Количество(). Одна строка защиты избавляет от целого класса сбоев.
  • ✅ Используйте Для Каждого, если индекс не нужен. Границы коллекции платформа соблюдает сама.
  • ✅ Для обхода с индексом берите ВГраница(), а не Количество().
  • ✅ Не запоминайте индексы надолго. После добавления или удаления элементов пересчитайте.
  • ✅ Для поиска используйте Найти. Метод массива возвращает Неопределено, если значения нет, — проверьте это до использования результата.
  • ✅ Проверяйте входные данные. Файлы, строки обмена и ответы внешних сервисов не всегда соответствуют формату.

Договоритесь в команде, что делать, когда данных нет. «Вернуть пустое значение», «показать сообщение» или «бросить исключение» — выбор зависит от задачи, но он должен быть осознанным.

При ревью чужого кода ищите три подозрительных шаблона: [0] без проверки, Количество() как верхняя граница цикла и Удалить внутри прямого обхода.

Два мифа, которые мешают найти причину

«Это сбой платформы, надо переустанавливать 1С». Нет. Платформа сработала правильно: ей передали несуществующий номер. Переустановка ничего не изменит, а при ошибке в коде она бессмысленна.

«Помогает перезапуск и чистка кэша». Кэш к границам массива отношения не имеет. Если ошибка исчезла после перезапуска, значит изменились данные в момент запуска или вы просто воспроизвели действие иначе. Причина осталась в коде.

Исключение — когда ошибка плавающая и зависит от данных, например, от наличия записей за конкретный период. Тогда «лечится» не кэш, а пустой период, и ошибка вернётся.

Отладка для сложных случаев

Если простой взгляд на строку причину не показал, подключайте инструменты.

Остановка в момент ошибки

Запустите клиент под отладчиком и включите остановку по ошибке в меню отладки Конфигуратора. Тогда выполнение прервётся не после сообщения, а прямо перед сбоем, и вы увидите состояние всех переменных. Посмотрите стек вызовов: ошибочное обращение часто находится не там, где значение испортили.

Вычисление выражений

В остановленном отладчике вычислите выражение для проверки гипотезы: количество элементов, содержимое массива, значение индекса. Если массив пришёл из функции, вычислите результат её вызова отдельно — так видно, пустая ли она.

Условные точки останова

Если ошибка срабатывает в цикле на тысячной итерации, ставить обычную точку останова бессмысленно. Задайте условие, например Индекс > Массив.ВГраница(), и отладчик остановится ровно на проблемном шаге.

Диагностика на рабочем сервере

Когда ошибку не воспроизвести у себя, но она стабильно есть у пользователей, помогает запись контекста. Добавьте в проблемное место временное сообщение с размером коллекции и значением индекса, запишите их в журнал регистрации, потом уберите. Так вы получаете реальные значения без остановки работы.

Не оставляйте отладочный вывод в рабочем коде. Он засоряет журнал, а в коде, который выполняется для каждой строки документа, ещё и замедляет работу.

Частые вопросы

Почему массив в 1С нумеруется с нуля, а строки табличной части показывают номер с единицы?
Индекс — это программная позиция в коллекции, она идёт с нуля. НомерСтроки — это отображаемый пользователю номер, он идёт с единицы. Для обращения по индексу нужен первый, а не второй.
Чему равен `ВГраница()` у пустого массива?
Минус единице. Поэтому любая попытка прочитать элемент пустого массива приводит к ошибке, а цикл Для Индекс = 0 По Массив.ВГраница() на пустом массиве просто не выполнится.
Одинаково ли ведут себя массив и таблица значений?
По части индексов — да, оба считают с нуля и выдают ту же ошибку. Разница в доступе к данным: в таблице значений за индексом строки идёт обращение к колонке по имени.
Можно ли «проглотить» ошибку через `Попытка … Исключение`?
Технически можно, но это прячет дефект. Сначала проверьте условие, а Попытка оставьте для случаев, когда сбой внешний и предсказать его нельзя.
Ошибка появилась после обновления конфигурации. Что проверить в первую очередь?
Расширения и внешние печатные формы. Они часто привязаны к структуре данных старого релиза. Отключите их по одному и проверьте, исчезает ли ошибка.