Возврат значения из функции во встроенном языке 1С

· 9 мин

Содержание

Значение из функции забирает оператор Возврат: он прекращает выполнение и отдаёт результат в точку вызова. На стороне вызова результат присваивают переменной — Итог = МояФункция(Параметр); — или подставляют прямо в выражение. Если функция завершилась без Возврат, она вернёт Неопределено, и это самая частая причина пустого результата.

Ниже — как устроен возврат значения в коде 1С 8.3, чем функция отличается от процедуры, как передавать данные через параметры, вызывать функции динамически, перехватывать ошибки и не считать одно и то же по десять раз.

Оператор Возврат и присваивание результата

Функция отличается от процедуры тем, что обязана отдавать значение. Объявляют её парой Функция … КонецФункции, а результат возвращают оператором Возврат:

Функция СуммаСНДС(Сумма, Ставка = 20) Экспорт
    Возврат Сумма + Сумма * Ставка / 100;
КонецФункции

Вызов и получение значения:

Итог = СуммаСНДС(1000);        // 1200
Итог = СуммаСНДС(1000, 10);    // 1100

Ключевое слово Экспорт открывает функцию для вызова из других модулей. Без него функция видна только внутри своего модуля. Значение по умолчанию (Ставка = 20) подставляется, когда аргумент при вызове опущен.

Где именно выполнится функция, зависит от директивы компиляции перед ней. &НаСервере и &НаСервереБезКонтекста выполняют код на сервере, &НаКлиенте — на клиенте. Функция общего модуля подчиняется флагам самого модуля (Клиент, Сервер, Вызов сервера). Это важно для возврата значения: с клиента получить результат серверной функции напрямую можно только у общего модуля с флагом «Вызов сервера», иначе будет ошибка компиляции.

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

Функция может возвращать значение любого типа: число, строку, булево, ссылку, объект, коллекцию. Чтобы отдать несколько значений за один вызов, собирают их в Структуру или Массив:

Функция РазложитьФИО(СтрокаФИО) Экспорт
    Части = СтрРазделить(СтрокаФИО, " ", Ложь);
    Результат = Новый Структура("Фамилия, Имя, Отчество");
    Результат.Фамилия  = ?(Части.Количество() > 0, Части[0], "");
    Результат.Имя      = ?(Части.Количество() > 1, Части[1], "");
    Результат.Отчество = ?(Части.Количество() > 2, Части[2], "");
    Возврат Результат;
КонецФункции

На стороне вызова поля читаются по имени: ФИО = РазложитьФИО(Строка); Фамилия = ФИО.Фамилия;.

Когда значение возвращают через параметры

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

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

Вызов возвращает сразу два результата через переменные:

Сумма = 0;
Кол = 0;
РассчитатьИтоги(ТаблицаСтрок, Сумма, Кол);

Чтобы, наоборот, защитить переданную переменную от изменений, параметр объявляют с ключевым словом Знач — тогда внутрь попадёт копия значения:

Функция Удвоить(Знач Число)
    Число = Число * 2;   // меняется только локальная копия
    Возврат Число;
КонецФункции

Передача по ссылке и примитивные типы ведут себя неочевидно. Число, строку, дату и булево функция меняет только у себя, если нет Знач-ссылки на внешнюю переменную. А вот коллекции (Массив, Структура, ТаблицаЗначений) передаются как ссылка на один и тот же объект: добавили строку в таблицу внутри — она добавилась и снаружи, даже при Знач. Знач копирует саму ссылку, но не содержимое коллекции.

Практический вывод: если функция не должна портить переданную ей таблицу или структуру, делайте внутри копию через Скопировать(), а не надейтесь на Знач.

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

Динамический вызов: Вычислить и Выполнить

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

Вычислить(<Строка>) вычисляет выражение и возвращает его результат:

Выражение = "Сумма * Ставка / 100";
Сумма = 1000;
Ставка = 20;
НДС = Вычислить(Выражение);   // 200

Выполнить(<Строка>) исполняет один или несколько операторов, но ничего не возвращает — результат нужно уложить в переменную прямо в строке кода:

Код = "Результат = Сумма + 100;";
Сумма = 50;
Результат = 0;
Выполнить(Код);               // Результат = 150

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

Вычислить и Выполнить работают в контексте того места, где вызваны, и видят его локальные переменные. На сервере в безопасном режиме и в ряде контекстов эти операторы ограничены. Не передавайте в них строки, собранные из пользовательского ввода, — это дыра в безопасности. Там, где можно обойтись обычным кодом, обходитесь без них: разбор строки медленнее прямого вызова.

Оператор Новый стоит рядом в этой теме, но решает другую задачу — он создаёт экземпляр объекта (Новый Массив, Новый Запрос), а не вызывает функцию. Динамически подобрать тип создаваемого объекта помогает конструкция Новый(ИмяТипаСтрокой).

Если нужно вызвать по имени одну из нескольких заранее известных функций, безопаснее обойтись без Выполнить: соберите соответствие «имя → действие» через Если/ИначеЕсли или через Структуру-диспетчер. Код станет предсказуемым, а платформа проверит его синтаксис на этапе компиляции, чего со строкой в Выполнить не происходит.

Что делать, если функция завершилась ошибкой

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

Попытка
    Курс = ПолучитьКурсВалюты("USD");
Исключение
    Курс = 0;
    ЗаписьЖурналаРегистрации("Курсы",
        УровеньЖурналаРегистрации.Ошибка, , ,
        ОписаниеОшибки());
КонецПопытки;

Текст ошибки берут двумя способами:

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

Если ошибку нужно обработать частично и передать выше, её перевозбуждают голым оператором ВызватьИсключение внутри блока Исключение:

Попытка
    ОбработатьДокумент(Ссылка);
Исключение
    ОткатитьТранзакцию();
    ВызватьИсключение;   // пробросить дальше, не теряя стек
КонецПопытки;

Собственную ошибку поднимают так: ВызватьИсключение "Не заполнен контрагент";.

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

Результат = ПрочитатьФайл(Путь);
Если НЕ Результат.Успех Тогда
    Сообщить(Результат.ТекстОшибки);
    Возврат;
КонецЕсли;
Данные = Результат.Значение;

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

Не прячьте исключение в пустой блок Исключение. Молчаливый перехват маскирует настоящую причину сбоя — как минимум пишите текст в журнал регистрации.

Чтобы функция не считала одно и то же дважды

Когда функция тяжёлая (запрос к базе, обращение к сервису) и вызывается многократно с одними аргументами, результат кешируют.

Штатный механизм платформы — свойство модуля «Повторное использование возвращаемых значений». Оно доступно у общих модулей и модулей менеджеров объектов. Значения:

РежимКогда результат переиспользуется
Не использоватьКаждый вызов считается заново (по умолчанию)
На время вызоваПока не завершится текущий серверный вызов
На время сеансаПока жив сеанс пользователя

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

«Ленивое вычисление» — когда значение считается только при первом обращении — в 1С делают вручную через модульную переменную:

Перем КешНастроек;

Функция Настройки() Экспорт
    Если КешНастроек = Неопределено Тогда
        КешНастроек = ПрочитатьНастройкиИзБазы();
    КонецЕсли;
    Возврат КешНастроек;
КонецФункции

Кеш «На время сеанса» опасен для меняющихся данных: пользователь не увидит свежее значение, пока не перезайдёт. Для изменяемых справочников берите «На время вызова» или сбрасывайте кеш явно.

Практический пример: функция для данных отчёта

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

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

    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        Возврат ?(Выборка.Сумма = Null, 0, Выборка.Сумма);
    КонецЕсли;
    Возврат 0;
КонецФункции

Три приёма, которые тут важны:

  1. Пустой результат агрегата. Если по условию нет ни одной записи, СУММА() вернёт Null, а не ноль. Поэтому результат проверяют: ?(Выборка.Сумма = Null, 0, Выборка.Сумма).
  2. Гарантированный возврат. В конце стоит Возврат 0; — функция всегда отдаёт число, даже когда выборка пустая и первый Если не сработал.
  3. Параметры запроса, а не склейка строки. Значения подставляются через УстановитьПараметр, что защищает от ошибок типов и работает быстрее.

Проверить себя на основных правилах возврата:

Проверьте себя
Что вернёт функция, если выполнение дошло до `КонецФункции`, не встретив `Возврат`?
Как вернуть из функции сразу два значения?
Какой оператор вычисляет выражение из строки и возвращает результат?

Типичные ошибки при возврате значения

Отдельно — промахи, которые не ловятся на этапе проверки синтаксиса и всплывают уже в работе:

  • 🔴 Функция вернула Неопределено. Есть ветка кода, которая не доходит до Возврат. Проверьте, что значение возвращается из всех веток Если, а не только из основной.
  • 🟠 Путают процедуру и функцию. Процедура не возвращает значение, присвоить её результат нельзя — будет ошибка «Обращение к процедуре как к функции». Нужен результат — объявляйте Функция.
  • 🟠 Испорченная таблица или структура. Передали коллекцию в функцию, та добавила или удалила строки — изменения ушли и во внешнюю переменную. Знач тут не спасает. Делайте Скопировать(), если оригинал менять нельзя.
  • 🟠 Вычислить вместо нормального кода. Разбор строки медленный и небезопасный. Используйте его, только когда выражение действительно неизвестно заранее.
  • 🟡 Null принимают за ноль. Агрегатные функции запроса и незаполненные поля возвращают Null, а не 0 и не пустую строку. Арифметика с Null даёт Null — обрабатывайте явно.

Частые вопросы

Чем функция отличается от процедуры в 1С?
Функция обязана возвращать значение через оператор Возврат, и её результат можно присвоить переменной. Процедура значение не возвращает — её вызывают ради действия, а не ради результата.
Можно ли выйти из функции досрочно?
Да. Оператор Возврат в любом месте тела сразу завершает функцию и отдаёт указанное значение. Это удобно для ранних проверок: не прошло условие — вернули результат и вышли.
Почему функция возвращает Неопределено?
Выполнение дошло до КонецФункции, не встретив ни одного Возврат с конкретным значением. Чаще всего забыта ветка Если, в которой возврат не прописан.
Как вернуть несколько значений?
Прямого синтаксиса нет — возвращают Структуру или Массив с нужными полями. Либо передают результат через параметры: они по умолчанию работают по ссылке.
В чём разница между Вычислить и Выполнить?
Вычислить вычисляет выражение и возвращает его значение. Выполнить исполняет операторы и ничего не возвращает — результат нужно присвоить переменной внутри самой строки кода.