При разработке в среде «1С:Предприятие» каждый программист рано или поздно сталкивается с ошибкой «Значение индекса выходит за границы диапазона». Эта проблема возникает при попытке обратиться к элементу коллекции (массиву, таблице значений, соответствию или списку) по индексу, которого не существует. Разберем по шагам, почему возникает эта ошибка на примере кода автора, как правильно работать с индексами и как оптимизировать алгоритмы обхода коллекций.
Рассмотрим исходный код, который вызвал затруднение:
Для Счетчик = 1 По ТЗ.Колонки.Количество() Цикл
Если Тип("СправочникСсылка.ФизическиеЛица") = ТИПЗНЧ(Строка.Получить(Счетчик)) тогда
Сообщить(Строка.Получить(Счетчик));
КонецЕсли;
КонецЦикла
На первый взгляд логика автора кажется понятной: если у нас есть определенное количество колонок, мы хотим пробежать по ним от первой до последней. Однако в 1С, как и во многих других языках программирования, принята нулевая индексация. Это означает, что первый элемент любой коллекции всегда имеет индекс 0, а не 1.
Давайте проанализируем ситуацию на простом примере. Представим, что в нашей таблице значений ТЗ ровно 3 колонки. Метод ТЗ.Колонки.Количество() вернет число 3. Индексы колонок при этом распределятся следующим образом:
012Если мы запускаем цикл Для Счетчик = 1 По 3, то на первой же итерации (при значении 1) мы пропускаем самый первый элемент с индексом 0. На третьей итерации (при значении 3) программа пытается обратиться к элементу с индексом 3 через метод Строка.Получить(3). Но максимальный существующий индекс в коллекции равен 2. В этот момент платформа аварийно завершает выполнение кода и выдает ошибку: «Значение индекса выходит за границы диапазона».
Самый простой и очевидный способ исправить данную ошибку — скорректировать границы обхода в цикле со счетчиком. Индексация должна начинаться с нуля, а конечной точкой должно быть общее количество элементов минус единица.
Посмотрим на исправленный вариант классического цикла:
Для Счетчик = 0 По ТЗ.Колонки.Количество() - 1 Цикл
ТекущееЗначение = Строка.Получить(Счетчик);
Если Тип("СправочникСсылка.ФизическиеЛица") = ТипЗнч(ТекущееЗначение) Тогда
Сообщить(ТекущееЗначение);
КонецЕсли;
КонецЦикла
Этот код отработает без ошибок. Обратите внимание, что мы также оптимизировали код, получив значение один раз в переменную ТекущееЗначение, вместо того чтобы дважды вызывать метод Получить() для одного и того же индекса.
В современной разработке на платформе «1С:Предприятие 8» явное использование индексов и счетчиков для простого перебора элементов считается устаревшим стилем. Если вам не требуется изменять структуру коллекции в процессе обхода (например, удалять или добавлять колонки/строки), лучше всего использовать конструкцию Для Каждого ... Из ... Цикл.
В этом случае платформа берет на себя управление индексами, что полностью исключает человеческий фактор и возникновение ошибок выхода за границы диапазона. Перепишем наш алгоритм:
Для Каждого Колонка Из ТЗ.Колонки Цикл
ИмяКолонки = Колонка.Имя;
ЗначениеЯчейки = Строка[ИмяКолонки];
Если ТипЗнч(ЗначениеЯчейки) = Тип("СправочникСсылка.ФизическиеЛица") Тогда
Сообщить(ЗначениеЯчейки);
КонецЕсли;
КонецЦикла
Вместо метода Получить() здесь мы применили более лаконичный и быстрый синтаксис обращения к свойствам строки через квадратные скобки Строка[ИмяКолонки]. Это делает код более читаемым и производительным.
Стоит отметить, что хотя в примере автора идет работа со строкой Таблицы Значений (о чем справедливо упомянул один из участников обсуждения), для работы с обычными одномерными массивами в 1С предусмотрен удобный встроенный метод ВГраница().
Этот метод автоматически возвращает индекс самого последнего элемента массива (что эквивалентно формуле Количество() - 1). Если вы работаете именно с массивом, цикл будет выглядеть так:
МойМассив = Новый Массив;
// Заполнение массива...
Для Индекс = 0 По МойМассив.ВГраница() Цикл
Сообщить(МойМассив[Индекс]);
КонецЦикла;
Использование ВГраница() избавляет разработчика от необходимости вручную вычитать единицу и делает намерение кода более очевидным.
Если перед вами стоит задача не просто проанализировать данные, а удалить определенные элементы из коллекции в цикле, использование стандартного обхода «от 0 до Количество - 1» приведет к логическим ошибкам или падению системы.
При удалении элемента длина коллекции уменьшается, а индексы оставшихся элементов смещаются влево. В результате вы либо пропустите элементы, следующие за удаленным, либо выйдете за новые границы измененного диапазона. Для решения этой задачи всегда используйте **обратный цикл** (обход с конца):
// Пример удаления строк из таблицы значений с конца
Для Индекс = -(ТЗ.Количество() - 1) По 0 Цикл
ТекущийИндекс = Абс(Индекс);
СтрокаТаблицы = ТЗ[ТекущийИндекс];
Если СтрокаТаблицы.ФизическоеЛицо.Пустая() Тогда
ТЗ.Удалить(ТекущийИндекс);
КонецЕсли;
КонецЦикла;
Также можно использовать альтернативный вариант организации обратного шага, если это поддерживается синтаксисом конкретных обработчиков, но схема с использованием отрицательного счетчика и функции Абс() является наиболее универсальной для платформы 1С.
Давайте посмотрим шире на задачу автора. Он пытается найти физическое лицо в «любом документе», перебирая все ячейки подряд во всех строках и колонках. С точки зрения производительности это крайне неоптимальный путь, особенно на больших базах данных.
Вместо «слепого» перебора всех значений в памяти, мы можем заранее обратиться к структуре метаданных документа и выяснить, какие именно реквизиты имеют нужный нам тип данных. Это позволит проверять только конкретные целевые колонки.
Разберем пример такой оптимизации:
// Определяем имя документа, с которым работаем (например, "ПриемНаРаботу")
ИмяДокумента = СсылкаНаДокумент.Метаданные().Имя;
// Массив для хранения имен колонок, содержащих физлиц
МассивЦелевыхКолонок = Новый Массив;
// Проверяем реквизиты шапки документа
Для Каждого Реквизит Из Метаданные.Документы[ИмяДокумента].Реквизиты Цикл
Если Реквизит.Тип.СодержитТип(Тип("СправочникСсылка.ФизическиеЛица")) Тогда
МассивЦелевыхКолонок.Добавить(Реквизит.Имя);
КонецЕсли;
КонецЦикла;
// Теперь мы точно знаем, из каких реквизитов нужно читать данные,
// минуя полный перебор всех колонок подряд!
Для Каждого ИмяКолонки Из МассивЦелевыхКолонок Цикл
Значение = СсылкаНаДокумент[ИмяКолонки];
Если ЗначениеЗаполнено(Значение) Тогда
Сообщить("Найдено физлицо: " + Значение);
КонецЕсли;
КонецЦикла;
Этот подход не только защищает вас от ошибок обращения по неверным индексам, но и многократно ускоряет работу алгоритма, снижая нагрузку на сервер «1С:Предприятие».