Возврат значения из функции во встроенном языке 1С
Содержание
Значение из функции забирает оператор Возврат: он прекращает выполнение и отдаёт результат в точку вызова. На стороне вызова результат присваивают переменной — Итог = МояФункция(Параметр); — или подставляют прямо в выражение. Если функция завершилась без Возврат, она вернёт Неопределено, и это самая частая причина пустого результата.
Ниже — как устроен возврат значения в коде 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;
КонецФункции
Три приёма, которые тут важны:
- Пустой результат агрегата. Если по условию нет ни одной записи,
СУММА()вернётNull, а не ноль. Поэтому результат проверяют:?(Выборка.Сумма = Null, 0, Выборка.Сумма). - Гарантированный возврат. В конце стоит
Возврат 0;— функция всегда отдаёт число, даже когда выборка пустая и первыйЕслине сработал. - Параметры запроса, а не склейка строки. Значения подставляются через
УстановитьПараметр, что защищает от ошибок типов и работает быстрее.
Проверить себя на основных правилах возврата:
Типичные ошибки при возврате значения
Отдельно — промахи, которые не ловятся на этапе проверки синтаксиса и всплывают уже в работе:
- 🔴 Функция вернула
Неопределено. Есть ветка кода, которая не доходит доВозврат. Проверьте, что значение возвращается из всех ветокЕсли, а не только из основной. - 🟠 Путают процедуру и функцию. Процедура не возвращает значение, присвоить её результат нельзя — будет ошибка «Обращение к процедуре как к функции». Нужен результат — объявляйте
Функция. - 🟠 Испорченная таблица или структура. Передали коллекцию в функцию, та добавила или удалила строки — изменения ушли и во внешнюю переменную.
Значтут не спасает. ДелайтеСкопировать(), если оригинал менять нельзя. - 🟠
Вычислитьвместо нормального кода. Разбор строки медленный и небезопасный. Используйте его, только когда выражение действительно неизвестно заранее. - 🟡
Nullпринимают за ноль. Агрегатные функции запроса и незаполненные поля возвращаютNull, а не0и не пустую строку. Арифметика сNullдаётNull— обрабатывайте явно.
Частые вопросы
Чем функция отличается от процедуры в 1С?
Возврат, и её результат можно присвоить переменной. Процедура значение не возвращает — её вызывают ради действия, а не ради результата.Можно ли выйти из функции досрочно?
Возврат в любом месте тела сразу завершает функцию и отдаёт указанное значение. Это удобно для ранних проверок: не прошло условие — вернули результат и вышли.Почему функция возвращает Неопределено?
КонецФункции, не встретив ни одного Возврат с конкретным значением. Чаще всего забыта ветка Если, в которой возврат не прописан.Как вернуть несколько значений?
Структуру или Массив с нужными полями. Либо передают результат через параметры: они по умолчанию работают по ссылке.В чём разница между Вычислить и Выполнить?
Вычислить вычисляет выражение и возвращает его значение. Выполнить исполняет операторы и ничего не возвращает — результат нужно присвоить переменной внутри самой строки кода.