Почему в 1С изменение конечной границы цикла Для не влияет на количество итераций и как это исправить?

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

Каждый разработчик во встроенном языке платформы 1С:Предприятие (как в старых версиях 7.7, так и во всех современных редакциях 8.3) рано или поздно сталкивается с фундаментальными особенностями работы базовых синтаксических конструкций. Одной из таких особенностей является поведение цикла Для ... По ... Цикл при попытке динамически изменить его верхнюю границу непосредственно внутри тела выполнения. Рассмотрим подробно, почему это происходит, как устроена работа платформы "под капотом", и разберем проверенные архитектурные решения этой задачи.

Выясняем причину: как платформа 1С обрабатывает цикл Для

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

Проанализируем классический пример кода, демонстрирующий эту проблему:


йй = 5;
Для й = 0 По йй Цикл
    Если й = 2 Тогда
        йй = 10; // Попытка увеличить количество итераций до 10
    КонецЕсли;
    Сообщить("Текущий шаг счетчика: " + й + ", Значение переменной границы: " + йй);
КонецЦикла;

Если запустить данный фрагмент кода, мы увидим в окне сообщений, что цикл выполнится ровно 6 раз (для значений счетчика от 0 до 5) и завершится. Переменная йй действительно примет значение 10 на третьем шаге, но цикл все равно прекратит работу, как только счетчик й превысит 5. Почему так происходит?

Дело в оптимизации компилятора и правилах выполнения встроенного языка 1С. Платформа производит однократное вычисление границ цикла:

  1. Перед началом выполнения самого первого шага цикла система обращается к выражению, стоящему после ключевого слова По.
  2. Платформа вычисляет это выражение (в нашем случае считывает значение переменной йй, равное 5) и сохраняет его во временный системный буфер (кэш), недоступный программисту напрямую.
  3. На каждой последующей итерации система сравнивает значение управляющей переменной й не с самой переменной йй, а со значением из этого системного кэша.
  4. Любые перезаписи переменной йй внутри тела цикла никак не влияют на сохраненную платформой константу завершения.

Что происходит при ручном изменении счетчика цикла?

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

Однако мы категорически не рекомендуем прибегать к такому подходу по следующим причинам:

Сравнение работы циклов с другими языками программирования

Поведение цикла Для в 1С аналогично поведению оператора for ... to ... do в таких языках, как Pascal. Там верхний предел также фиксируется жестко в момент инициализации цикла.

В отличие от них, в C-подобных языках (C, C++, C#, Java, JavaScript) классический цикл for (int i = 0; i < limit; i++) работает по иному принципу. В них условие i < limit является логическим выражением, которое полноценно вычисляется заново на старте абсолютно каждого шага. Если изменить переменную limit внутри тела такого цикла, то граница сдвинется динамически. Во встроенном языке 1С аналогичного поведения от оператора Для добиться невозможно.

Решение проблемы: Способ 1. Использование универсального цикла Пока

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

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


й = 0;
йй = 5;

Пока й <= йй Цикл
    Если й = 2 Тогда
        йй = 10; // Граница успешно сдвигается динамически!
    КонецЕсли;
    
    Сообщить("Текущий шаг счетчика: " + й + ", Новое значение границы: " + йй);
    
    // Обязательно увеличиваем счетчик вручную в конце итерации
    й = й + 1; 
КонецЦикла;

Проанализируем этот код. На шаге, когда й равно 2, мы переопределяем переменную йй значением 10. На следующем витке цикла платформа заново вычислит условие й <= йй (3 <= 10) и продолжит выполнение. Цикл отработает корректно и завершится строго на значении 10.

Решение проблемы: Способ 2. Правильный обход и изменение коллекций (удаление элементов)

Типичным сценарием, где разработчики совершают ошибку с фиксированной границей цикла Для, является обход коллекции (например, строк ТаблицаЗначений или элементов Массив) с одновременным удалением элементов по условию.

Рассмотрим пример ошибочного кода:


КоличествоСтрок = ТаблицаТоваров.Количество();
Для Индекс = 0 По КоличествоСтрок - 1 Цикл
    Строка = ТаблицаТоваров[Индекс];
    Если Строка.Цена = 0 Тогда
        ТаблицаТоваров.Удалить(Строка); // Ошибка! Количество строк уменьшилось
    КонецЕсли;
КонецЦикла;

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

Чтобы избежать этой проблемы, разберем безопасный паттерн обхода коллекции с конца с использованием цикла Пока:


Индекс = ТаблицаТоваров.Количество() - 1;

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

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

Решение проблемы: Способ 3. Использование Для каждого для стабильных коллекций

В случаях, когда вам нужно просто перебрать коллекцию (массив, таблицу значений, соответствие, список значений) без динамического изменения ее структуры (без добавления или удаления элементов в процессе обхода), стандарты разработки 1С предписывают использовать специализированную конструкцию Для каждого ... Из ... Цикл.

Посмотрим на пример:


Для Каждого ТекущаяСтрока Из ТаблицаТоваров Цикл
    // Чтение данных и изменение реквизитов внутри строки
    ТекущаяСтрока.СтатусОбработки = Истина;
КонецЦикла;

Этот способ работает быстрее, синтаксически чище, не требует от разработчика ручного управления переменными-итераторами и защищает от случайных ошибок работы с индексами.

Современное развитие механизмов циклов в экосистеме 1С

Стоит отметить, что в новых технологиях фирмы 1С, таких как среда разработки 1С:Предприятие.Элемент и язык сценариев 1С:Исполнитель, синтаксис циклов был значительно расширен и модернизирован. В частности, появилась возможность прямого указания шага обхода и направления:


для Индекс = 100 вниз по 1
для Индекс = 1 по 50 шаг 2

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

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

← На главную