Как исправить бесконечный цикл и правильно рассчитать график периодических событий в 1С?

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

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

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

Постановка задачи и исходные данные

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

  1. КоличествоДнейВМесяце — общая длительность расчетного периода в днях (например, 30 или 31 день).
  2. ДеньНачалаМесяца — день недели, на который выпадает 1-е число месяца (число от 1 до 7, где 1 — понедельник, а 7 — воскресенье).
  3. ДеньПервогоПриема — число месяца, с которого начинается прием препарата (например, с 15-го числа).
  4. ЧастотаПриема — интервал между приемами в днях (например, каждые 3 дня).
  5. ПриемСПервогоДня — булево свойство, указывающее, нужно ли делать первый прием непосредственно в указанный стартовый день (Истина) или следует сделать первый шаг и сместить прием на величину частоты (Ложь).
  6. КоличествоПротеинаВБудни и КоличествоПротеинаВВыходные — дозировка приема в граммах в зависимости от дня недели.

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

Разбор архитектурных ошибок автора задачи

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

Ошибка №1: Застревание на одном дне недели. Изначально автор использовал два вложенных цикла. Внешний цикл обходил коллекцию дней недели (Для каждого эл из ДеньНачала), а внутренний цикл (Пока ДеньПервогоПриёма < КоличествоДнейВМесяце) занимался расчетом дней приема. Программа находила первый день начала месяца, заходила во внутренний цикл Пока и выполняла его до конца месяца. Однако, поскольку этот цикл крутился внутри одной и той же итерации внешнего цикла, переменная дня недели эл.Значение оставалась неизменной. В результате программа выводила график, в котором все числа месяца соответствовали одному дню недели, например, только понедельнику.

Ошибка №2: Бесконечный цикл при перемене мест. Пытаясь решить первую проблему, автор поменял циклы местами и добавил условие сопоставления внутри. Логика сломалась в строке инкремента индекса дня недели: ДеньНачалаМесяца = ДеньНачалаМесяца + ЧастотаПриёма;. Так как переменная ДеньНачалаМесяца постоянно увеличивалась на величину шага, она быстро превысила число 7. Условие Если эл.Ключ = ДеньНачалаМесяца больше никогда не выполнялось, а значит, строка с увеличением даты приема ДеньПервогоПриёма = ДеньПервогоПриёма + ЧастотаПриёма перестала вызываться. Значение переменной ДеньПервогоПриёма «застыло» на месте, условие выхода из цикла Пока никогда не достигалось, и программа уходила в бесконечный цикл, намертво блокируя работу системы.

Оптимальное решение: избавляемся от вложенных циклов

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

Давайте выясним, как работает эта формула:

НомерДняНедели = (ДеньНачалаМесяца + ТекущийДень - 2) % 7 + 1;

Разберем ее по шагам на конкретном примере. Представим, что месяц начинается в четверг (ДеньНачалаМесяца = 4). Нам нужно определить день недели для 6-го числа этого месяца (ТекущийДень = 6):

  1. Складываем стартовый день и текущую дату месяца: 4 + 6 = 10.
  2. Вычитаем 2 для корректного сдвига индекса под остаток от деления: 10 - 2 = 8.
  3. Находим остаток от деления полученного числа на 7 (количество дней в неделе): в 1С за это отвечает оператор %. Получаем 8 % 7 = 1.
  4. Прибавляем 1 для возврата к привычному интервалу дней недели (от 1 до 7): 1 + 1 = 2 (вторник).

Проверим вручную: если 1-е число — четверг, то 2-е — пятница, 3-е — суббота, 4-е — воскресенье, 5-е — понедельник, 6-е — вторник. Формула работает абсолютно верно и мгновенно!

Реализация алгоритма на языке 1С

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


&НаКлиенте
Процедура ВывестиГрафик(Команда)
    
    // 1. Проверим заполнение ключевого реквизита поиска
    Если Не ЗначениеЗаполнено(ПоискПоФИО) Тогда
        ПоказатьПредупреждение(, "Пожалуйста, заполните поле поиска по ФИО!");
        Возврат;
    КонецЕсли;
    
    // 2. Поиск человека в табличной части (эмулируем поиск)
    СтрокаЧеловека = ТаблицаЛюдей.Найти(ПоискПоФИО, "ФИО");
    Если СтрокаЧеловека = Неопределено Тогда
        Сообщить("Данный человек не найден в таблице!");
        Возврат;
    КонецЕсли;
    
    // 3. Заполним соответствие наименований дней недели
    ДниНедели = Новый Соответствие;
    ДниНедели.Вставить(1, "понедельник");
    ДниНедели.Вставить(2, "вторник");
    ДниНедели.Вставить(3, "среда");
    ДниНедели.Вставить(4, "четверг");
    ДниНедели.Вставить(5, "пятница");
    ДниНедели.Вставить(6, "суббота");
    ДниНедели.Вставить(7, "воскресенье");
    
    // 4. Загрузим параметры расчета
    Частота = СтрокаЧеловека.ЧастотаПриема;
    
    // Защита от бесконечного цикла: частота должна быть строго больше нуля
    Если Частота <= 0 Тогда
        ПоказатьПредупреждение(, "Частота приема должна быть больше 0 дней!");
        Возврат;
    КонецЕсли;
    
    // Определяем стартовую дату приема с учетом флага "Прием с первого дня"
    ТекущийДень = СтрокаЧеловека.ДеньПервогоПриема;
    Если Не СтрокаЧеловека.ПриемСПервогоДня Тогда
        ТекущийДень = ТекущийДень + Частота;
    КонецЕсли;
    
    ОбщийВесПротеина = 0;
    ВсегоДнейПриема   = 0;
    
    Сообщить("--- График приема протеина для: " + СтрокаЧеловека.ФИО + " ---");
    
    // 5. Основной цикл расчета по дням месяца (в один проход)
    Пока ТекущийДень <= КоличествоДнейВМесяце Цикл
        
        // Математический расчет дня недели без использования вложенных циклов
        НомерДняНедели = (ДеньНачалаМесяца + ТекущийДень - 2) % 7 + 1;
        ИмяДняНедели = ДниНедели.Получить(НомерДняНедели);
        
        // Определение дозировки в зависимости от типа дня (будний или выходной)
        Если НомерДняНедели = 6 Или НомерДняНедели = 7 Тогда
            ПротеинСегодня = КоличествоПротеинаВВыходные;
        Иначе
            ПротеинСегодня = КоличествоПротеинаВБудни;
        КонецЕсли;
        
        // Выводим информацию по текущему дню приема
        Сообщить(Строка(ТекущийДень) + " число (" + ИмяДняНедели + "): " + Строка(ПротеинСегодня) + "гр.");
        
        // Накапливаем статистику
        ОбщийВесПротеина = ОбщийВесПротеина + ПротеинСегодня;
        ВсегоДнейПриема   = ВсегоДнейПриема + 1;
        
        // Делаем шаг цикла вперед на указанную частоту приема
        ТекущийДень = ТекущийДень + Частота;
        
    КонецЦикла;
    
    // 6. Выводим суммарные итоги расчета
    Сообщить("--------------------------------------------------");
    Сообщить("1. Общее количество принятого протеина: " + ОбщийВесПротеина + " гр.");
    Сообщить("2. Количество приемов в днях: " + ВсегоДнейПриема);
    
КонецПроцедуры

Преимущества предложенного подхода

Проанализируем преимущества использования математического расчета вместо вложенных циклов:

← На главную