Почему не выполняется код внутри функции Выполнить и как это исправить

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

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

Анализ контекста и области видимости переменных

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

Рассмотрим ситуацию, когда код внутри Выполнить разбит на логические блоки (например, несколько запросов и циклов). Если результат вычисления должен быть возвращен в основную программу, крайне важно инициализировать переменную-результат заранее и обеспечивать её корректное изменение на каждом этапе алгоритма. Проанализируем пример, где автор столкнулся с тем, что код работал только частями. Это происходило из-за того, что переменная Результат не «проходила» сквозь все блоки кода согласованно. Как только логика была выстроена так, что промежуточные значения записывались в одну и ту же переменную, доступную в контексте вызова, всё заработало.

Проблема синтаксиса и «тихих» ошибок

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

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


ТекстСкрипта = "
|Запрос = Новый Запрос;
|Запрос.Текст = ""ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура ГДЕ Наименование = """"Товар"""" "";
|РезультатЗапроса = Запрос.Выполнить();
|";
Выполнить(ТекстСкрипта);

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

Пошаговая инструкция по отладке кода в Выполнить

Если ваш скрипт внутри Выполнить не работает, выполним следующие шаги для поиска неисправности:

  1. Удаление блока Попытка...Исключение. Часто разработчики оборачивают вызов Выполнить в Попытка, чтобы избежать аварийного завершения работы программы. На время отладки это нужно убрать. Система сама выбросит исключение и укажет на строку с ошибкой, что позволит понять причину (неверный синтаксис, обращение к несуществующему методу и т.д.).
  2. Использование Сообщить() или логов. Поскольку точки останова внутри строки Выполнить не работают, добавим вывод промежуточных значений. Проанализируем, доходит ли выполнение до нужного нам цикла или условия.
  3. Выгрузка во внешнюю обработку. Это лучший способ проверить сложную логику. Скопируем текст, который мы передаем в Выполнить, и вставим его в обычную процедуру внешней обработки. Платформа сразу подсветит ошибки синтаксиса, а отладчик позволит пройти по шагам.

Реализация надежного алгоритма вычислений

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


Попытка
    Результат = 0; // Инициализируем переменную заранее
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |   рбсВидыДоговоровЗаполнениеРеквизитовДоговора.Комментарий
    |ИЗ
    |   Справочник.рбсВидыДоговоров.ЗаполнениеРеквизитовДоговора КАК рбсВидыДоговоровЗаполнениеРеквизитовДоговора
    |ГДЕ
    |   рбсВидыДоговоровЗаполнениеРеквизитовДоговора.Ссылка = &Ссылка
    |   И рбсВидыДоговоровЗаполнениеРеквизитовДоговора.РеквизитДоговора.Наименование = ""ЧасовВМес""";
    
    Запрос.УстановитьПараметр("Ссылка", Объект.ВидДоговора.Ссылка);
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    ЧасовВМес = 0;
    Пока Выборка.Следующий() Цикл
        СтрокаКомментария = Выборка.Комментарий;
        ЧислоИзСтроки = "";
        // Извлекаем цифры из строки комментария
        Для Индекс = 1 По СтрДлина(СтрокаКомментария) Цикл
            Сим = Сред(СтрокаКомментария, Индекс, 1);
            Если Сим >= "0" И Сим <= "9" Тогда
                ЧислоИзСтроки = ЧислоИзСтроки + Сим;
            КонецЕсли;
        КонецЦикла;
        
        Попытка
            ЧасовВМес = Число(ЧислоИзСтроки);
        Исключение
            ЧасовВМес = 0;
            Сообщить("Неверно задано значение ЧасовВМес в виде договора");
        КонецПопытки;
    КонецЦикла;

    Если ЧасовВМес > 0 Тогда
        Запрос2 = Новый Запрос;
        Запрос2.Текст = 
        "ВЫБРАТЬ
        |   рбсДоговорПодпискаИТС.ДатаНачала,
        |   рбсДоговорПодпискаИТС.ДатаОкончания
        |ИЗ
        |   Документ.рбсДоговор.ПодпискаИТС КАК рбсДоговорПодпискаИТС
        |ГДЕ
        |   рбсДоговорПодпискаИТС.Ссылка = &Ссылка
        |   И рбсДоговорПодпискаИТС.ДатаНачала = &ДатаНачала";
        
        Запрос2.УстановитьПараметр("Ссылка", Объект.Ссылка);
        Запрос2.УстановитьПараметр("ДатаНачала", Параметры.ДатаПодпискиИТС);
        
        РезультатЗапроса2 = Запрос2.Выполнить();
        Выборка2 = РезультатЗапроса2.Выбрать();
        
        Если Выборка2.Следующий() Тогда
            // Вычисляем разницу в месяцах
            КоличествоМесяцев = (Год(Выборка2.ДатаОкончания) - Год(Выборка2.ДатаНачала)) * 12 
                                + Месяц(Выборка2.ДатаОкончания) - Месяц(Выборка2.ДатаНачала) 
                                + Окр((День(Выборка2.ДатаОкончания) - День(Выборка2.ДатаНачала)) / 30);
            
            Результат = КоличествоМесяцев * ЧасовВМес;
        Иначе
            Сообщить("Проверьте заполнение ИТС периода");
        КонецЕсли;
    Иначе
        Сообщить("Проверьте значение ЧасовВМес в виде договора");
    КонецЕсли;
    
Исключение
    Результат = "Проверьте значение ЧасовВМес и периоды ИТС";
    Сообщить(ОписаниеОшибки());
КонецПопытки;

Альтернатива: Использование внешних обработок

Разберем более профессиональный подход, который упоминался в обсуждении. Использование Выполнить часто считается плохой практикой (anti-pattern) из-за проблем с безопасностью, производительностью и отладкой. Если задача состоит в том, чтобы хранить «формулы» или «алгоритмы» в справочнике, лучше использовать механизм внешних обработок.

Проанализируем преимущества этого метода:

Схема реализации следующая: в справочнике создается реквизит с типом ХранилищеЗначения. Туда записывается файл внешней обработки (.epf). В нужный момент код извлекает обработку из хранилища, сохраняет во временный файл и подключает её программно:


ИмяФайла = ПолучитьИмяВременногоФайла("epf");
ДвоичныеДанные = СправочникОбъект.ХранилищеОбработки.Получить();
ДвоичныеДанные.Записать(ИмяФайла);

ОбработкаОбъект = ВнешниеОбработки.Создать(ИмяФайла, Ложь);
Результат = ОбработкаОбъект.ВыполнитьРасчет(Параметры);

Когда использовать Вычислить() вместо Выполнить()

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

ЧислоЧасов = Вычислить("Константы.НормаЧасов.Получить() * 1.5");

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

Важный совет: всегда проверяйте входные данные. Если в строку для Выполнить или Вычислить попадут данные, введенные пользователем, это может привести к «инъекции» произвольного кода. Всегда используйте УстановитьБезопасныйРежим(Истина) перед выполнением динамического кода, если вы не уверены в его источнике на 100%.

← На главную