Как сложить числа из разных итераций цикла и избежать ошибки преобразования типов в 1С

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

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

Постановка задачи и анализ базовой логики

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

Посмотрим на классическую ошибку, которую совершают программисты при попытке решить эту задачу:


Для Каждого СтрокаТаблицы Из СписокСтрок Цикл
    // Какая-то логика вычислений...
    СуммаВРублях = СтрокаТаблицы.Сумма * КурсВалюты;
    
    // Попытка сложить значения
    ВсегоСумма = ВсегоСумма + СуммаВРублях; 
КонецЦикла;

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

Причина ошибки "Преобразование к числу не может быть выполнено"

В платформе 1С:Предприятие используется динамическая типизация. Если переменная не была предварительно объявлена и ей не было присвоено начальное значение, то при первом обращении к ней система считает её тип равным Неопределено.

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

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

Рассмотрим правильный базовый шаблон:


// 1. Инициализируем переменную-накопитель числом 0 ДО входа в цикл
ВсегоСумма = 0; 

Для Каждого СтрокаТаблицы Из СписокСтрок Цикл
    СуммаВРублях = СтрокаТаблицы.Сумма * КурсВалюты;
    
    // 2. Теперь сложение отработает корректно на любой итерации
    ВсегоСумма = ВсегоСумма + СуммаВРублях; 
КонецЦикла;

Разбор скрытых багов в коде автора темы

Проанализируем исходный код, предоставленный автором на форуме. Хотя после переноса инициализации переменной сумма3 = 0 за пределы цикла его сиюминутная проблема решилась, данный код содержит ряд критических ошибок, опечаток и уязвимостей, которые обязательно приведут к сбоям при изменении данных в базе.

Выделим ключевые проблемные места:

  1. Опасная опечатка в имени переменной цикла. Цикл объявлен для переменной ттс: Для Каждого ттс из ИНН_ Цикл. Однако внутри тела цикла автор обращается к переменной тс: СуммаВРублях = тс.Сумма2; и Если тс.ДоговорКонтрагентаВалютаВзаиморасчетов.... Если переменная тс случайно была определена где-то выше во внешнем контексте, код выполнится, но выдаст абсолютно неверные математические расчеты. Если же она не определена, программа упадет с ошибкой.
  2. Обращение через точку к реквизитам без проверки на заполнение. Строка тс.ДоговорКонтрагентаВалютаВзаиморасчетов.Код = "978" — это потенциальная "мина замедленного действия". Если у контрагента не выбран договор или у договора не заполнена валюта, свойство вернет значение Неопределено или пустую ссылку. Попытка получить свойство .Код у пустого объекта вызовет фатальную ошибку: "Поле объекта не обнаружено (Код)".
  3. Дублирование тяжелых операций поиска. В коде дважды вызывается метод Остатки.НайтиСтроки(...) с одинаковыми параметрами — в начале цикла и в самом конце. Это избыточная операция, которая сильно замедляет работу системы, особенно если коллекция Остатки содержит большое количество записей.
  4. Опечатки в именах областей макета. В одном месте используется Секция2.Параметры..., а в ветке Иначе внезапно появляется Секция.Параметры.Сумма1 = 0;. Отсутствие единого именования переменных неизбежно приведет к ошибкам компиляции.

Пишем безопасный и оптимизированный код по шагам

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

Шаг 1. Инициализация переменных и защита от деления/умножения на ноль. Перед циклом определим переменную для накопления итога и подготовим значение курса валюты, защитив код от возможных пустых ссылок.


ИтоговаяСумма = 0;

// Безопасно получаем курс евро. Если курс не задан или равен нулю, используем 1 во избежание логических ошибок
КурсЕвроЗначение = ?(КурсЕвро <> Неопределено И КурсЕвро.Курс <> 0, КурсЕвро.Курс, 1);

Шаг 2. Оптимизация поиска строк. Чтобы не перегружать процессор постоянным созданием структуры поиска и вызовом метода НайтиСтроки на каждой итерации, мы вынесем создание структуры и выполним поиск строк ровно один раз за одну итерацию в самом начале цикла.


Для Каждого ттс Из ИНН_ Цикл
    
    // Создаем структуру поиска один раз за итерацию
    ПараметрыПоиска = Новый Структура("Контрагент, ИНН, Сумма1, Сумма2", ттс.Контрагент, ттс.ИНН, ттс.Сумма1, ттс.Сумма2);
    Стр_Осн = Остатки.НайтиСтроки(ПараметрыПоиска);
    
    Для Каждого стр Из Стр_Осн Цикл
        стр.ВыводитьСтроку = Ложь;
    КонецЦикла;

Шаг 3. Обработка условий обхода и защита от пустых договоров. Реализуем проверку заполнения договора контрагента перед тем, как обращаться к его коду валюты.


Если ттс.Сумма1 = 0 И ттс.Сумма2 = 0 Тогда 
    Продолжить; 
КонецЕсли;
    
Секция2.Параметры.Контрагент = ттс.Contragent; // используем корректную переменную ттс
Секция2.Параметры.Сумма1     = ттс.Сумма1;
    
// Защита от пустых значений договоров:
КодВалюты = "";
Если ЗначениеЗаполнено(ттс.ДоговорКонтрагентаВалютаВзаиморасчетов) Тогда
    КодВалюты = ттс.ДоговорКонтрагентаВалютаВзаиморасчетов.Код;
КонецЕсли;

Шаг 4. Расчет суммы в рублях и безопасное суммирование. Заменим потенциально опасные значения типа Null на числовое выражение и аккуратно прибавим результат к нашей итоговой переменной.


    Если КодВалюты = "978" Тогда 
        СуммаВРублях = ттс.Сумма1 * КурсЕвроЗначение;
    Иначе
        Секция2.Параметры.Сумма1 = 0; // Исправлена опечатка (было Секция)
        СуммаВРублях             = ттс.Сумма1;
    КонецЕсли;
    
    Секция2.Параметры.Сумма2 = СуммаВРублях;
    
    // Предотвращаем падение при сложении, если СуммаВРублях вернула Null или Неопределено
    ЧисловоеЗначениеСуммы = ?(СуммаВРублях = Неопределено Или СуммаВРублях = Null, 0, Число(СуммаВРублях));
    
    // Накапливаем итоговое значение
    ИтоговаяСумма = ИтоговаяСумма + ЧисловоеЗначениеСуммы;
    
    // Выводим готовую секцию в результирующий табличный документ
    // ТабличныйДокумент.Вывести(Секция2);
    
КонецЦикла;

Совет по улучшению архитектуры в УТ 10.3

Рассмотренный метод отлично решает задачу "в лоб", однако в профессиональной разработке на платформе 1С 8.3 (особенно в конфигурациях уровня "Управление торговлей 10.3") подобные расчеты, конвертации валют и сопоставление остатков крайне не рекомендуется выполнять непосредственно в цикле обхода табличной части.

Гораздо более правильным, производительным и профессиональным подходом является проведение всех расчетов на уровне СУБД с помощью механизма запросов 1С. Вы можете соединить таблицу остатков с курсами валют (используя виртуальную таблицу РегистрСведений.КурсыВалют.СрезПоследних) прямо в запросе. В этом случае на выходе вы получите уже готовую, рассчитанную в рублях таблицу данных, по которой останется лишь быстро пройтись простым циклом вывода без лишних проверок и дополнительных транзакций к базе данных.

← На главную