«Индекс находится за границами диапазона»: как найти строку кода и починить цикл

· 11 мин

Содержание

Ошибка Индекс находится за границами диапазона в 1С означает одно: код обратился к элементу массива, таблицы значений или табличной части по номеру, которого в коллекции нет. Индексы в 1С начинаются с нуля, поэтому у массива из 5 элементов последний индекс 4, а обращение Массив[5] уже вызывает сбой.

Исправляют её тремя способами. Если элементы нужно просто перебрать — меняют цикл по индексу на Для Каждого. Если элементы удаляют — идут с конца. Если берут конкретный элемент, например первую строку результата запроса, — сначала проверяют, что коллекция не пустая. Главное — найти, какая строка модуля падает. На это уходит больше всего времени.

Почему 1С выходит за границы массива

У любой индексируемой коллекции 1С есть допустимый диапазон номеров: от 0 до Количество() - 1. Метод ВГраница() у массива возвращает верхнюю границу, то есть тот же Количество() - 1. У пустого массива ВГраница() равна -1.

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

{ОбщийМодуль.ОбработкаЗаказов.Модуль(148)}: Ошибка при вызове метода контекста (Удалить)
по причине:
Индекс находится за границами диапазона

Число в скобках — номер строки модуля. Это самая полезная часть сообщения, с неё и начинается поиск.

Ошибка возникает в коллекциях, к которым обращаются по номеру:

  • 📦 Массив и ФиксированныйМассив — Массив[Индекс], Удалить(Индекс), Установить(Индекс, Значение);
  • 📊 ТаблицаЗначений — Таблица[Индекс], Таблица.Получить(Индекс);
  • 📄 табличные части документов и справочников — Объект.Товары[Индекс];
  • 🌳 ДеревоЗначений — Дерево.Строки[Индекс];
  • 🔤 результат СтрРазделить() — это тоже массив.

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

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

Шесть ситуаций, в которых код падает с ошибкой индекса

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

1. Цикл до Количество(), а не до Количество() − 1

Классическая ошибка на единицу:

// Неправильно: последняя итерация обратится к Массив[5] при 5 элементах
Для Сч = 0 По Массив.Количество() Цикл
    Сообщить(Массив[Сч]);
КонецЦикла;

Правильная граница — Массив.Количество() - 1 или Массив.ВГраница().

2. Перепутаны НомерСтроки и индекс

У строки табличной части есть реквизит НомерСтроки, и он начинается с 1. Индекс той же строки начинается с 0. Код вида Объект.Товары[Строка.НомерСтроки] для последней строки выйдет за границу, а для остальных молча возьмёт соседнюю строку. Второе даже опаснее: ошибки нет, а данные неверные.

Правильно: Объект.Товары[Строка.НомерСтроки - 1], а ещё лучше — Объект.Товары.Индекс(Строка) или работа с самой строкой без индексов.

3. Удаление элементов в прямом цикле

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

Граница цикла Для вычисляется один раз, при входе. После первого удаления массив короче, а цикл всё равно дойдёт до старого последнего индекса. Плюс каждый элемент после удалённого сдвигается влево, и следующий элемент пропускается без проверки.

4. Обращение к первой строке пустого результата

Таблица = Запрос.Выполнить().Выгрузить();
Цена = Таблица[0].Цена; // упадёт, если запрос ничего не вернул

Код годами работает на тестовой базе, где данные есть всегда, и падает у пользователя на новом товаре без цены.

5. Разбор строки без проверки числа частей

Части = СтрРазделить("ИНН7701234567", ";");
КПП = Части[1]; // разделителя нет, в массиве один элемент

То же бывает при разборе файлов обмена, CSV и ответов внешних сервисов: формат «почти всегда» одинаковый, но одна кривая строка роняет всю загрузку.

6. Вложенные циклы и многомерные массивы

В массиве Новый Массив(3, 4) первый индекс идёт от 0 до 2, второй — от 0 до 3. Если перепутать счётчики внешнего и внутреннего цикла, ошибка вылезет не сразу, а только когда счётчик превысит меньшую размерность. Та же беда — когда во внутреннем цикле по ошибке используют переменную внешнего.

Диагностика: выберите симптом

Как найти место ошибки отладчиком 1С

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

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

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

  1. Откройте базу в Конфигураторе.
  2. Запустите отладку: Отладка → Начать отладку или клавиша F5. Откроется сеанс 1С:Предприятия под отладчиком.
  3. В Конфигураторе откройте Отладка → Остановка по ошибке… и включите остановку. Можно указать текст ошибки, например Индекс находится за границами диапазона, чтобы отладчик не реагировал на другие исключения.
  4. Повторите в пользовательском режиме действие, которое вызывает ошибку.
  5. Конфигуратор остановится на проблемной строке и подсветит её.

Что смотреть в точке остановки

  • 🔍 Значение индекса. Выделите переменную счётчика и нажмите Shift+F9 — окно «Вычислить выражение» покажет её значение.
  • 📏 Размер коллекции. В том же окне вычислите Массив.Количество() или Таблица.Количество(). Если индекс равен количеству — это ошибка на единицу, если коллекция пустая — нет проверки на пустоту.
  • 🧭 Стек вызовов. Покажет, через какую цепочку процедур выполнение пришло в эту строку. Полезно, когда массив собирается в одном модуле, а падает в другом.
  • 📌 Табло. Добавьте туда индекс и размер коллекции, чтобы видеть их на каждом шаге без повторных вычислений.

Точка останова с условием

Если цикл проходит тысячи итераций, а сбой — на последней, обычная точка останова (F9) бесполезна: придётся нажимать «Продолжить» тысячу раз. Поставьте точку останова с условием, например Сч >= Массив.Количество() - 1. Отладчик остановится только тогда, когда условие станет истинным. Дальше пройдите код по шагам: F10 — шаг с обходом процедур, F11 — шаг с заходом в процедуру.

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

Если отладчик недоступен

Бывает, что прав на Конфигуратор нет или ошибка воспроизводится только на сервере у одного пользователя. Тогда:

  1. Попросите пользователя в окне ошибки нажать кнопку подробностей и прислать полный текст — там будет модуль и строка.
  2. Найдите запись в журнале регистрации: Администрирование → Обслуживание → Журнал регистрации, отбор по уровню «Ошибка» и времени сбоя.
  3. Временно добавьте в код запись диагностики перед проблемной строкой:
ЗаписьЖурналаРегистрации("Диагностика.Индекс", УровеньЖурналаРегистрации.Информация, , ,
    СтрШаблон("Индекс %1, элементов %2", Сч, Массив.Количество()));

После разбора диагностику уберите, чтобы не засорять журнал.

Исправление ошибки индекса: безопасный перебор и удаление

Когда место найдено, исправление обычно занимает одну-две строки. Выбор приёма зависит от того, что делает код.

Просто перебрать все элементы — Для Каждого

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

Для Каждого Элемент Из Массив Цикл
    Сообщить(Элемент);
КонецЦикла;

Для таблиц значений и табличных частей — то же самое, переменная цикла будет строкой.

Нужен номер элемента — правильная граница

Для Сч = 0 По Массив.ВГраница() Цикл
    Сообщить(СтрШаблон("%1: %2", Сч + 1, Массив[Сч]));
КонецЦикла;

У пустого массива ВГраница() равна -1, и цикл от 0 до −1 не выполнится ни разу. Это корректное поведение, дополнительная проверка не нужна.

Удалить элементы — обратный цикл

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

ВсегоЭлементов = Массив.Количество();
Для Сч = 1 По ВсегоЭлементов Цикл
    Индекс = ВсегоЭлементов - Сч; // 4, 3, 2, 1, 0 при пяти элементах
    Если Массив[Индекс] = Неопределено Тогда
        Массив.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

Тот же результат через Пока:

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

Для таблицы значений приём тот же: проходите с конца и вызывайте Таблица.Удалить(Таблица[Индекс]) или удаляйте по индексу.

Взять конкретный элемент — сначала проверка

Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
    Цена = 0;
Иначе
    Цена = Результат.Выгрузить()[0].Цена;
КонецЕсли;

Для строки, разобранной через СтрРазделить, проверка выглядит так:

Части = СтрРазделить(СтрокаФайла, ";");
Если Части.Количество() < 3 Тогда
    // записать в протокол загрузки и перейти к следующей строке
    Продолжить;
КонецЕсли;
Артикул = Части[0];
Количество = Части[2];

Найти элемент, а не вычислять его номер

Если индекс нужен только для того, чтобы найти элемент по значению, используйте поиск коллекции. Массив.Найти(Значение) вернёт индекс или Неопределено, если значения нет. У таблицы значений есть Найти() и НайтиСтроки().

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

Сравнение способов обхода коллекций в 1С

СпособКогда использоватьРиск выхода за границы
Для Каждого … ИзПеребор без изменения коллекцииНет
Для Сч = 0 По ВГраница()Нужен номер элемента или параллельный проход двух массивовЕсть, если граница взята от другой коллекции
Обратный цикл с фиксированным количествомУдаление элементов по условиюНет, если количество зафиксировано до цикла
Пока Индекс >= 0Удаление, сложное условие выходаЕсть, если забыть уменьшить индекс — тогда зацикливание
Найти(), НайтиСтроки()Поиск по значениюНет, но результат надо проверить на Неопределено
Соответствие вместо массиваЧастый поиск по ключуНет, отсутствующий ключ даёт Неопределено

Отдельно про удаление внутри Для Каждого. Ошибки индекса там обычно нет, но после удаления текущего элемента следующий сдвигается на его место, и цикл его пропускает. Данные обрабатываются не все, а сообщения об этом нет. Поэтому для удаления — только обратный цикл.

Как не допускать ошибки индекса в сложных структурах

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

  • 🧱 Проверяйте данные на входе. Если процедура получает массив параметром и сразу берёт Параметр[0], добавьте проверку в начало процедуры. Одна проверка на входе надёжнее десятка по коду.
  • 🔗 Не храните индексы между операциями. Индекс строки, запомненный до сортировки, свёртки или удаления, после них указывает на другую строку. Храните ссылку на саму строку или ключевое значение.
  • 🗂 Синхронные массивы заменяйте структурами. Два массива «Товары» и «Цены» с общим индексом ломаются, как только в один добавили элемент, а в другой забыли. Массив структур или таблица значений с колонками надёжнее.
  • 🌲 Дерево перебирайте рекурсивно через Для Каждого. Обращение Дерево.Строки[0].Строки[0] падает на первой строке без подчинённых.
  • 📥 JSON и XML считайте недоверенными. После чтения JSON массивы приходят с любым числом элементов, в том числе пустые. Проверяйте Количество() перед каждым обращением по индексу.
  • 🧪 Тестируйте на краях. Ноль элементов, один элемент, ровно граница. Большинство ошибок индекса видно уже на пустой коллекции.
Проверка кода на ошибки индекса перед сдачей
Выполнено: 0 / 6

Если ошибка индекса не уходит

Бывает, что код исправлен, а сбой остался. Проверьте по порядку:

  1. Вы правите не тот модуль. Если ошибка в расширении, изменение в основной конфигурации не поможет, и наоборот. Сверьте имя модуля в тексте ошибки.
  2. Изменения не применены. После правки в Конфигураторе нужно обновить конфигурацию базы данных (F7), а пользователям — перезайти. Обновление структуры может потребовать монопольного доступа, поэтому делайте его, когда в базе никто не работает .
  3. Внешняя обработка взята из старого файла. Если обработка подключена через справочник дополнительных отчётов и обработок, загрузите туда исправленный файл заново.
  4. Сбой в типовом коде на ваших данных. Например, у документа повреждена табличная часть после неудачного обмена. Проверьте документ на копии базы: если ошибка воспроизводится только на нём, проблема в данных, а не в алгоритме.

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

Чем отличается Количество() от ВГраница()?
Количество() возвращает число элементов, ВГраница() — индекс последнего элемента. У массива из 10 элементов это 10 и 9. У пустого массива — 0 и −1.
Почему ошибка появляется не у всех пользователей?
Обычно из-за данных: у одних пользователей запрос возвращает строки, у других — пустой результат. Ещё одна причина — разные права доступа, из-за которых выборка у пользователя с ограничениями пустая.
Можно ли расширить массив, просто записав значение в новый индекс?
Нет. Запись Массив[10] = Значение в массив из 10 элементов вызовет ту же ошибку. Новые элементы добавляют методом Добавить() или Вставить().
Влияет ли режим совместимости на эту ошибку?
Нет. Индексация с нуля и проверка границ работают одинаково во всех режимах совместимости 8.3. Если ошибка появилась после обновления платформы, ищите изменения в данных или в коде конфигурации.
Подходит ли Попытка-Исключение для обхода ошибки?
Только при разборе внешних данных и с записью проблемной строки в протокол. В обычной логике она скрывает ошибку и даёт неполный результат.