«Индекс находится за границами диапазона»: где искать ошибку в коде 1С и как её закрыть

· 10 мин

Содержание

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

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

Как платформа 1С проверяет индекс и когда выбрасывает исключение

Любая индексированная коллекция в 1С хранит количество элементов. При обращении Коллекция[N] платформа сравнивает N с этим количеством. Если N меньше нуля или больше либо равен количеству, выполнение прерывается исключением.

Что важно знать о механике:

  • 🔢 Нумерация с нуля. Допустимые индексы — от 0 до Количество() - 1. У пустой коллекции допустимых индексов нет вообще.
  • 📏 Проверка в момент обращения. Ошибка возникает не при создании массива, а в той строке, где идёт чтение или запись по индексу.
  • 🧱 Одинаково для разных типов. Тот же текст ошибки дают Массив, ФиксированныйМассив, ТаблицаЗначений, СписокЗначений, табличные части документов и коллекции данных формы.
  • 🧭 Методы с индексом тоже проверяют границы. Удалить(Индекс), Установить(Индекс, Значение), Получить(Индекс) с неверным номером дают ту же ошибку.

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

Табличная часть документа — частая ловушка. Поле НомерСтроки начинается с 1, а индекс строки — с 0. Строка с НомерСтроки = 3 лежит по индексу 2.

Типичные сценарии: где в циклах и обращениях возникает выход за границу

Ниже — ситуации, которые дают эту ошибку на практике. Все примеры на встроенном языке 1С 8.3.

1. Граница цикла на единицу больше

Самый частый случай. Разработчик пишет верхнюю границу как количество элементов:

// Ошибка: на последнем шаге Индекс = Количество()
Для Индекс = 0 По Товары.Количество() Цикл
    Сумма = Сумма + Товары[Индекс].Сумма;
КонецЦикла;

Правильно — Количество() - 1. Для пустой коллекции граница станет -1, и цикл просто не выполнится ни разу, без ошибки.

Для Индекс = 0 По Товары.Количество() - 1 Цикл
    Сумма = Сумма + Товары[Индекс].Сумма;
КонецЦикла;

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

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

В 1С выражение верхней границы цикла Для ... По вычисляется один раз, до первого шага. Если внутри цикла удалять элементы, коллекция укорачивается, а граница остаётся прежней. На последних шагах индекс выходит за пределы.

// Ошибка: было 5 элементов, после удаления их меньше, а цикл идёт до 4
Для Индекс = 0 По Массив.Количество() - 1 Цикл
    Если Массив[Индекс] = 0 Тогда
        Массив.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

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

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

Код ожидает «хотя бы одну строку» и берёт её сразу:

  • 📄 РезультатЗапроса.Выгрузить()[0] — запрос ничего не нашёл, таблица пустая.
  • 🔍 ТЗ.НайтиСтроки(Отбор)[0] — подходящих строк нет, возвращается пустой массив.
  • ✂️ СтрРазделить(Строка, ";")[2] — во входной строке меньше трёх частей.

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

4. Пакет запросов с неверным номером результата

Запрос.ВыполнитьПакет() возвращает массив результатов по каждому запросу пакета, включая запросы, которые помещают данные во временные таблицы. Добавили в начало пакета ещё один запрос ПОМЕСТИТЬ — и все номера сдвинулись. Обращение Результаты[3] при четырёх запросах в пакете ещё работает, при трёх — уже ошибка.

5. Индекс, сохранённый «на потом»

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

Как найти строку кода, где возникает ошибка индекса

Начинать нужно с подробного текста ошибки. В нём платформа указывает модуль и номер строки, например {Документ.РеализацияТоваровУслуг.МодульОбъекта(214)}.

Шаги для пользователя типовой конфигурации

  1. В окне ошибки откройте подробный текст и скопируйте его целиком, вместе со стеком вызовов.
  2. Посмотрите имя модуля. ВнешняяОбработка или ВнешнийОтчет — проблема во внешней обработке. Имя расширения в стеке — проблема в расширении конфигурации.
  3. Откройте журнал регистрации: в БП 3.0 и ЗУП 3.1 это Администрирование → Обслуживание, ссылка «Журнал регистрации». Отберите события с уровнем «Ошибка» за время сбоя.
  4. Передайте текст и стек тому, кто сопровождает базу. Если ошибка в типовом модуле без доработок, проверьте, установлен ли актуальный релиз: такие ошибки обычно исправляют в обновлениях.

Не правьте код типовой конфигурации на рабочей базе без резервной копии. Снятие конфигурации с поддержки усложняет все будущие обновления. Доработки делайте в расширении.

Отладка в Конфигураторе

Программист находит причину за несколько минут:

  1. Откройте базу в Конфигураторе и запустите отладку: меню Отладка → Начать отладку (клавиша F5).
  2. Включите остановку по ошибке: меню Отладка → Остановка по ошибке. Отладчик остановится прямо на строке с исключением.
  3. Повторите действие, которое вызывает сбой.
  4. В точке остановки откройте вычисление выражения (Shift+F9) и посмотрите значения индекса и Коллекция.Количество().
  5. Если ошибка в цикле на тысячах строк, поставьте точку останова с условием, например Индекс >= Товары.Количество() - 1, чтобы не листать шаги вручную.

Пошаговое выполнение: F10 — шаг с обходом процедуры, F11 — шаг с заходом внутрь. Стек вызовов покажет, откуда пришли неверные данные.

Для кода, который выполняется на сервере 1С:Предприятия (клиент-серверный вариант), на сервере должна быть разрешена отладка. Без этого отладчик остановится только в клиентском коде.

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

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

Правила, которые закрывают почти все сценарии из списка выше.

Проверяйте количество перед обращением по индексу.

Строки = ТЗ.НайтиСтроки(Новый Структура("Номенклатура", Товар));
Если Строки.Количество() > 0 Тогда
    Цена = Строки[0].Цена;
Иначе
    Цена = 0;
КонецЕсли;

Удаляйте с конца. Фиксируем количество в переменной до цикла, идём от последнего индекса к нулевому. Удаление элемента сдвигает только те, что правее, а их мы уже прошли.

Количество = Массив.Количество();
Для Сч = 1 По Количество Цикл
    Индекс = Количество - Сч;
    Если Массив[Индекс] = 0 Тогда
        Массив.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

Проверка на массиве из 5 элементов: индексы идут 4, 3, 2, 1, 0 — все в допустимых границах при любом числе удалений.

Не обращайтесь к фиксированному индексу результата разбора строки.

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

Номера результатов пакета держите рядом с текстом запроса. Заведите переменную с номером, например НомерРезультатаОстатки = 2, и комментируйте, какой запрос ей соответствует. Ещё надёжнее — выносить нужный результат в последний запрос пакета и брать его как Результаты[Результаты.Количество() - 1].

ПодходКогда применятьРиск
Для Каждого ... ИзНужен только перебор, без удаленияНет выхода за границу, но удалять внутри нельзя
Для Индекс = 0 По Количество() - 1Нужен номер элементаОшибка, если коллекция меняется в цикле
Обратный цикл с зафиксированным количествомУдаление элементов по условиюНужно аккуратно считать индекс
НайтиСтроки + удаление по строкамТаблица значений, удаление по отборуНужна проверка, что строки найдены

Попытка-Исключение: когда она уместна при ошибке индекса

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

Перехват оправдан в других случаях:

  • 📥 загрузка внешних данных, где битая строка не должна останавливать обработку всего файла;
  • 🔁 регламентные и фоновые задания, где ошибку нужно записать и перейти к следующему объекту;
  • 🧪 вызов чужого кода, который вы не можете исправить сразу.

Пример с записью в журнал регистрации:

Для Каждого СтрокаФайла Из СтрокиФайла Цикл
    Попытка
        ЗагрузитьСтроку(СтрокаФайла);
    Исключение
        ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
        ЗаписьЖурналаРегистрации("Загрузка прайса",
            УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
    КонецПопытки;
КонецЦикла;

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

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

Таблицы значений, списки значений и табличные части: что учесть

У каждого типа коллекции свои способы обойтись без индекса вовсе.

Таблица значений

  • 🔎 Для поиска строки используйте Найти(Значение, "Колонка") — он возвращает строку или Неопределено, а не падает.
  • 📋 НайтиСтроки(Структура) возвращает массив; перед [0] проверяйте его количество.
  • 🗑️ Удаляйте найденные строки по ссылке на строку: ТЗ.Удалить(Строка). Перебор идёт по массиву найденных строк, а не по самой таблице, поэтому сдвиг индексов не мешает.
  • 🔢 Индекс известной строки даёт метод ТЗ.Индекс(Строка). Не вычисляйте его по номеру строки или порядку добавления.
УдаляемыеСтроки = ТЗ.НайтиСтроки(Новый Структура("Количество", 0));
Для Каждого Строка Из УдаляемыеСтроки Цикл
    ТЗ.Удалить(Строка);
КонецЦикла;

Колонки тоже коллекция: ТЗ.Колонки[5] при пяти колонках вызовет ту же ошибку. Обращайтесь к колонкам по имени.

Список значений

  • 🔎 НайтиПоЗначению(Значение) возвращает элемент или Неопределено.
  • 🧷 Индекс(Элемент) даёт номер элемента; по нему безопасно вызывать Удалить.
  • ⚠️ Если список заполнили на сервере и передали на клиент, порядок элементов сохраняется, но после СортироватьПоЗначению() или СортироватьПоПредставлению() прежние индексы уже другие.

Табличная часть документа и формы

  • 🔢 Объект.Товары[Индекс] использует индекс с нуля; НомерСтроки — с единицы.
  • 🆔 В коллекции данных формы строку надёжнее искать по идентификатору: ПолучитьИдентификатор() у строки и НайтиПоИдентификатору(Идентификатор) у коллекции. Идентификатор не меняется при удалении других строк, в отличие от индекса.
  • 👆 Текущую строку берите через свойство ТекущиеДанные элемента формы и проверяйте на Неопределено: пользователь мог не выбрать ни одной строки.
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
    Возврат;
КонецЕсли;

Как не допустить ошибку индекса в новом коде

Несколько привычек, которые снижают число таких сбоев:

  • ✅ Перебор без индекса по умолчанию. Для Каждого везде, где номер элемента не нужен.
  • ✅ Граница цикла только Количество() - 1. Одна запись, без вариантов.
  • ✅ Любое [0] — после проверки количества. Особенно для результатов запросов и НайтиСтроки.
  • ✅ Тест на пустых данных. Перед сдачей доработки проведите документ с пустой табличной частью и сформируйте отчёт за период без движений.
  • ✅ Тест на граничных размерах. Одна строка, две строки, строка с последним индексом. Ошибка «на единицу» проявляется именно здесь.
  • ✅ Проверка конфигурации. В Конфигураторе запускайте Конфигурация → Проверка конфигурации с расширенной проверкой. Выход за границы она не ловит, но находит смежные проблемы: недоступные переменные, вызовы несуществующих методов.

Если доработок много, полезно договориться в команде о единой схеме удаления элементов из коллекций и проверять её на код-ревью. Ошибки этого типа однообразны, и их легко заметить глазами.

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

Чем отличается эта ошибка от «Поле объекта не обнаружено»?
Индекс за границами — обращение к несуществующему номеру элемента. «Поле объекта не обнаружено» — обращение к несуществующему свойству или колонке по имени. Причины и исправление разные.
Может ли ошибка возникнуть из-за данных, а не из-за кода?
Код всё равно виноват: он не проверил данные. Но запускает сбой именно состояние базы — пустая табличная часть, документ без строк, файл импорта с недостающими полями.
Помогут ли тестирование и исправление информационной базы?
Нет. Это ошибка встроенного языка, а не повреждение таблиц базы. Тестирование и исправление её не устранит.
Почему ошибка есть у меня, а у коллеги с тем же релизом нет?
Чаще всего различаются данные или подключённые расширения и внешние обработки. Сравните список расширений и попробуйте повторить действие на том же документе.
Можно ли создать массив нужного размера заранее?
Да: Новый Массив(10) создаст массив из 10 элементов со значением Неопределено, индексы от 0 до 9. Обращение [10] всё равно даст ошибку.