Как исправить ошибку «Значение индекса выходит за границы диапазона» в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке в среде «1С:Предприятие» каждый программист рано или поздно сталкивается с ошибкой «Значение индекса выходит за границы диапазона». Эта проблема возникает при попытке обратиться к элементу коллекции (массиву, таблице значений, соответствию или списку) по индексу, которого не существует. Разберем по шагам, почему возникает эта ошибка на примере кода автора, как правильно работать с индексами и как оптимизировать алгоритмы обхода коллекций.

Выясняем причину: почему счетчик не может быть равен количеству?

Рассмотрим исходный код, который вызвал затруднение:


Для Счетчик = 1 По ТЗ.Колонки.Количество() Цикл
    Если Тип("СправочникСсылка.ФизическиеЛица") = ТИПЗНЧ(Строка.Получить(Счетчик)) тогда
        Сообщить(Строка.Получить(Счетчик));
    КонецЕсли;
КонецЦикла

На первый взгляд логика автора кажется понятной: если у нас есть определенное количество колонок, мы хотим пробежать по ним от первой до последней. Однако в 1С, как и во многих других языках программирования, принята нулевая индексация. Это означает, что первый элемент любой коллекции всегда имеет индекс 0, а не 1.

Давайте проанализируем ситуацию на простом примере. Представим, что в нашей таблице значений ТЗ ровно 3 колонки. Метод ТЗ.Колонки.Количество() вернет число 3. Индексы колонок при этом распределятся следующим образом:

  1. Первая колонка — индекс 0
  2. Вторая колонка — индекс 1
  3. Третья колонка — индекс 2

Если мы запускаем цикл Для Счетчик = 1 По 3, то на первой же итерации (при значении 1) мы пропускаем самый первый элемент с индексом 0. На третьей итерации (при значении 3) программа пытается обратиться к элементу с индексом 3 через метод Строка.Получить(3). Но максимальный существующий индекс в коллекции равен 2. В этот момент платформа аварийно завершает выполнение кода и выдает ошибку: «Значение индекса выходит за границы диапазона».

Решение проблемы через исправление границ цикла

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

Посмотрим на исправленный вариант классического цикла:


Для Счетчик = 0 По ТЗ.Колонки.Количество() - 1 Цикл
    ТекущееЗначение = Строка.Получить(Счетчик);
    Если Тип("СправочникСсылка.ФизическиеЛица") = ТипЗнч(ТекущееЗначение) Тогда
        Сообщить(ТекущееЗначение);
    КонецЕсли;
КонецЦикла

Этот код отработает без ошибок. Обратите внимание, что мы также оптимизировали код, получив значение один раз в переменную ТекущееЗначение, вместо того чтобы дважды вызывать метод Получить() для одного и того же индекса.

Современный подход: использование цикла «Для каждого»

В современной разработке на платформе «1С:Предприятие 8» явное использование индексов и счетчиков для простого перебора элементов считается устаревшим стилем. Если вам не требуется изменять структуру коллекции в процессе обхода (например, удалять или добавлять колонки/строки), лучше всего использовать конструкцию Для Каждого ... Из ... Цикл.

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


Для Каждого Колонка Из ТЗ.Колонки Цикл
    ИмяКолонки = Колонка.Имя;
    ЗначениеЯчейки = Строка[ИмяКолонки];
    
    Если ТипЗнч(ЗначениеЯчейки) = Тип("СправочникСсылка.ФизическиеЛица") Тогда
        Сообщить(ЗначениеЯчейки);
    КонецЕсли;
КонецЦикла

Вместо метода Получить() здесь мы применили более лаконичный и быстрый синтаксис обращения к свойствам строки через квадратные скобки Строка[ИмяКолонки]. Это делает код более читаемым и производительным.

Особенности работы с массивами: метод ВГраница()

Стоит отметить, что хотя в примере автора идет работа со строкой Таблицы Значений (о чем справедливо упомянул один из участников обсуждения), для работы с обычными одномерными массивами в 1С предусмотрен удобный встроенный метод ВГраница().

Этот метод автоматически возвращает индекс самого последнего элемента массива (что эквивалентно формуле Количество() - 1). Если вы работаете именно с массивом, цикл будет выглядеть так:


МойМассив = Новый Массив;
// Заполнение массива...

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

Использование ВГраница() избавляет разработчика от необходимости вручную вычитать единицу и делает намерение кода более очевидным.

Важный нюанс: обход коллекции при удалении элементов

Если перед вами стоит задача не просто проанализировать данные, а удалить определенные элементы из коллекции в цикле, использование стандартного обхода «от 0 до Количество - 1» приведет к логическим ошибкам или падению системы.

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


// Пример удаления строк из таблицы значений с конца
Для Индекс = -(ТЗ.Количество() - 1) По 0 Цикл
    ТекущийИндекс = Абс(Индекс);
    СтрокаТаблицы = ТЗ[ТекущийИндекс];
    
    Если СтрокаТаблицы.ФизическоеЛицо.Пустая() Тогда
        ТЗ.Удалить(ТекущийИндекс);
    КонецЕсли;
КонецЦикла;

Также можно использовать альтернативный вариант организации обратного шага, если это поддерживается синтаксисом конкретных обработчиков, но схема с использованием отрицательного счетчика и функции Абс() является наиболее универсальной для платформы 1С.

Оптимизация поиска через метаданные

Давайте посмотрим шире на задачу автора. Он пытается найти физическое лицо в «любом документе», перебирая все ячейки подряд во всех строках и колонках. С точки зрения производительности это крайне неоптимальный путь, особенно на больших базах данных.

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

Разберем пример такой оптимизации:


// Определяем имя документа, с которым работаем (например, "ПриемНаРаботу")
ИмяДокумента = СсылкаНаДокумент.Метаданные().Имя;

// Массив для хранения имен колонок, содержащих физлиц
МассивЦелевыхКолонок = Новый Массив;

// Проверяем реквизиты шапки документа
Для Каждого Реквизит Из Метаданные.Документы[ИмяДокумента].Реквизиты Цикл
    Если Реквизит.Тип.СодержитТип(Тип("СправочникСсылка.ФизическиеЛица")) Тогда
        МассивЦелевыхКолонок.Добавить(Реквизит.Имя);
    КонецЕсли;
КонецЦикла;

// Теперь мы точно знаем, из каких реквизитов нужно читать данные,
// минуя полный перебор всех колонок подряд!
Для Каждого ИмяКолонки Из МассивЦелевыхКолонок Цикл
    Значение = СсылкаНаДокумент[ИмяКолонки];
    Если ЗначениеЗаполнено(Значение) Тогда
        Сообщить("Найдено физлицо: " + Значение);
    КонецЕсли;
КонецЦикла;

Этот подход не только защищает вас от ошибок обращения по неверным индексам, но и многократно ускоряет работу алгоритма, снижая нагрузку на сервер «1С:Предприятие».

← На главную