1C-CodeОсновы 1С → Циклы: повторить для каждого

Циклы: повторить для каждого

Три вида циклов, накопление итога и главная ошибка производительности в 1С.

Глава 9 из 36 · чтение примерно 24 минут

Цикл — это повторение. Пройти по всем строкам документа, по всем товарам в списке, по всем контрагентам с долгом.

В 1С три вида циклов, и выбирают их не по вкусу, а по тому, что известно заранее.

Три вида и когда какой

ЦиклКогда применятьКак выглядит
Для Каждого … Изобход коллекции — самый частыйДля Каждого СтрокаФайла Из Товары Цикл
Для … Поизвестно число повторенийДля К = 1 По 10 Цикл
Покачисло шагов заранее неизвестноПока Остаток > 0 Цикл

Для Каждого: обход коллекции

Самый частый цикл в 1С. Он сам достаёт очередной элемент — номера считать не нужно, выйти за границы невозможно.

Итог по строкам документа

Результат = 0;
Для Каждого Строка Из Документ.Товары Цикл
	Результат = Результат + Строка.Сумма;
КонецЦикла;
Сообщить(Итог);
ЧастьЧто означает
Итог = 0переменную заводят ДО цикла — внутри она обнулялась бы каждый раз
Строкаимя придумываете вы; на каждом шаге в ней очередной элемент
Итог = Итог + …накопление: взять прежнее значение и добавить новое
КонецЦикла;закрывает цикл, с точкой с запятой
Сообщить послевне цикла — иначе сообщение выведется на каждом шаге

Накопитель заводят до цикла

Это самая частая ошибка новичка в циклах, и она в одну строку.

Если написать Результат = 0; внутри цикла, накопитель будет обнуляться на каждом шаге — и в конце останется сумма только последней строки.

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

Упражнение 1. Сумма массива

Напишите функцию Сумма(Массив), возвращающую сумму всех чисел. Проверка передаёт массив из 10, 20, 30 и ждёт 60. Для пустого массива ответ должен быть 0.

Решается в тренажёре: код запускается и проверяется сразу.

Для … По: когда важен номер

Для К = 1 По 10 Цикл повторит десять раз, а переменная К будет по очереди принимать значения от 1 до 10.

Этот цикл нужен там, где важен сам номер: график из двенадцати платежей, разбор строки по символам, повтор попытки.

Если верхняя граница меньше нижней, цикл не выполнится ни разу. Для К = 5 По 1 — ноль шагов, без ошибки и без предупреждения.

Упражнение 2. Строка из звёздочек

Напишите Звёздочки(Сколько), возвращающую строку из указанного числа звёздочек. Для 5*****. Для нуля — пустая строка.

Решается в тренажёре: код запускается и проверяется сразу.

Пока: до выполнения условия

Пока Условие Цикл повторяет, пока условие истинно. Число шагов заранее неизвестно — оно зависит от того, что происходит внутри.

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

Внутри Пока обязано меняться то, что в условии

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

Пока Остаток > 0 Цикл
	Отгрузить(Партия);
	Остаток = Остаток - Партия;   // без этой строки — вечный цикл
КонецЦикла;

Забыть уменьшение легко: строка выглядит служебной, и её случайно переносят внутрь условия или удаляют при правке.

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

Прервать и Продолжить

Прервать выходит из цикла целиком. Продолжить пропускает остаток текущего шага и переходит к следующему.

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

✓ Прервать — выйти совсем

Для Каждого СтрокаФайла Из Товары Цикл
	Если СтрокаФайла.Количество = 0 Тогда
		ЕстьПустые = Истина;
		Прервать;
	КонецЕсли;
КонецЦикла;

Нашли одну — дальше искать незачем. Остальные строки не просматриваются.

✕ Продолжить — пропустить шаг

Для Каждого СтрокаФайла Из Товары Цикл
	Если СтрокаФайла.Количество = 0 Тогда
		Продолжить;
	КонецЕсли;
	Результат = Результат + СтрокаФайла.Сумма;
КонецЦикла;

Пустые строки пропускаются, остальные обрабатываются. Цикл идёт до конца.

Возврат из цикла — это нормально

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

Для Каждого СтрокаФайла Из Товары Цикл
	Если СтрокаФайла.Товар = Искомый Тогда
		Возврат СтрокаФайла.Количество;
	КонецЕсли;
КонецЦикла;
Возврат 0;   // не нашли — сюда дойдём только после всего цикла

Строка после цикла — это ответ «не нашли». Её забывают чаще всего, и тогда функция возвращает Неопределено, а падает уже вызывающий код — далеко от настоящей причины.

Упражнение 3. Найти первое отрицательное

Напишите ПервоеОтрицательное(Массив): вернуть первое отрицательное число, а если таких нет — 0. Проверка передаёт 5, 3, −2, −7 и ждёт −2.

Решается в тренажёре: код запускается и проверяется сразу.

Главная ошибка производительности: запрос в цикле

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

✓ Запрос внутри цикла

Для Каждого СтрокаФайла Из Товары Цикл
	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ … ГДЕ Товар = &Т";
	Запрос.УстановитьПараметр("Т", СтрокаФайла.Товар);
	Цена = Запрос.Выполнить()…;
КонецЦикла;

Сто строк — сто запросов. На тестовой базе из трёх строк это незаметно.

✕ Один запрос до цикла

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ … ГДЕ Товар В (&Список)";
Запрос.УстановитьПараметр("Список", Товары.ВыгрузитьКолонку("Товар"));
Цены = Запрос.Выполнить()…;

Для Каждого СтрокаФайла Из Товары Цикл
	Цена = Цены[СтрокаФайла.Товар];
КонецЦикла;

Один запрос на всё, дальше — обращения к готовой коллекции в памяти.

Почему это не находят на разработке

На тестовой базе документ состоит из трёх строк. Три запроса — это доли секунды, всё летает.

У клиента в документе триста строк, а база в сто раз больше. Проведение начинает занимать минуты, и виноватым оказывается «медленный сервер».

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

Это же касается обращений через точку: СтрокаФайла.Товар.Артикул внутри цикла — тоже чтение из базы на каждом шаге, просто не выглядит как запрос.

Не меняйте коллекцию, пока идёте по ней

Удалять элементы из массива внутри Для Каждого по этому же массиву нельзя. Обход собьётся: часть элементов окажется пропущена, потому что после удаления они сдвинулись.

Ошибки при этом не будет — просто часть данных молча не обработается.

Как делают правильно: сначала обходят и собирают в отдельный список то, что надо удалить, и только потом удаляют. Либо идут циклом Для К = Массив.Количество() - 1 По 0 — с конца, где сдвиг уже неважен.

Упражнение 4. Сколько строк без количества

Напишите ПустыхСтрок(Массив): сколько элементов массива равны нулю. Проверка передаёт 5, 0, 3, 0, 0 и ждёт 3.

Решается в тренажёре: код запускается и проверяется сразу.

Упражнение 5. Максимум своими руками

Напишите Наибольшее(Массив): найти наибольшее число. Для пустого массива вернуть 0. Проверка передаёт 3, 17, 8 и ждёт 17.

Решается в тренажёре: код запускается и проверяется сразу.

Цикл внутри цикла

Второй по частоте способ написать медленный код — после запроса в цикле — это цикл внутри цикла.

Выглядит он безобидно: для каждой строки заказа найти её цену в прайсе. Считается он так: десять строк заказа на тысячу позиций прайса — десять тысяч сравнений. Сто строк на десять тысяч позиций — миллион.

Дело не в том, что вложенные циклы запрещены. Дело в том, что работа перемножается, и на реальных данных это видно, а на трёх строках при разработке — нет.

✓ Цикл в цикле

Для Каждого Строка Из Строки Цикл
	Для Каждого Позиция Из Прайс Цикл
		Если Позиция.Товар = Строка.Товар Тогда
			Итог = Итог + Строка.Количество * Позиция.Цена;
			Прервать;
		КонецЕсли;
	КонецЦикла;
КонецЦикла;

Прайс перебирается заново для КАЖДОЙ строки. Работы — строки × позиции.

✕ Индекс до цикла

Цена = Новый Соответствие;
Для Каждого Позиция Из Прайс Цикл
	Цена.Вставить(Позиция.Товар, Позиция.Цена);
КонецЦикла;

Для Каждого Строка Из Строки Цикл
	Итог = Итог + Строка.Количество * Цена[Строка.Товар];
КонецЦикла;

Прайс пройден ОДИН раз. Дальше цена берётся сразу, без перебора. Работы — строки плюс позиции.

Один и тот же приём во всех трёх случаях

Запрос в цикле, цикл в цикле, поиск по таблице в цикле — это одна ошибка в трёх видах, и лечится она одинаково: подготовить данные до цикла.

Один запрос вместо N. Одно соответствие вместо перебора. Один проход по прайсу вместо прохода на каждую строку.

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

Упражнение 5. Сумма заказа по прайсу

Напишите СуммаЗаказа(Строки, Прайс). Строки — структуры с полями Товар и Количество, Прайс — структуры с Товар и Цена. Верните сумму заказа, не вкладывая цикл в цикл: сначала разложите прайс в соответствие. Ожидается 3540.

Решается в тренажёре: код запускается и проверяется сразу.

Бесконечный цикл

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

Три причины, и все одинаково простые: забыли увеличить счётчик; увеличивают не ту переменную, что в условии; условие зависит от того, что меняется только внутри Если, которое не срабатывает.

Как выходить: в конфигураторе — остановить отладку; в рабочей базе — завершать сеанс. Как не попадать: при сомнении добавить счётчик-предохранитель и выйти по Прервать, если шагов стало больше разумного.

Сто тысяч строк одним циклом

Обработка, которая правит все документы за год, написанная одним циклом, наткнётся на три стены: транзакция растёт и держит блокировки, память забивается прочитанными объектами, а упасть на девяносто девятой тысяче — значит потерять всё сделанное.

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

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

Что дальше

Циклу нужно что-то, по чему идти. Следующая глава — коллекции: массив, соответствие, структура и таблица значений. Там же станет понятно, откуда берётся Цены[СтрокаФайла.Товар] из примера про запрос до цикла.

Как закрепить

Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.

Открыть эту главу в тренажёре

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