Функции, Возврат и результат вызова во встроенном языке 1С

· 10 мин

Содержание

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

Возвращать значение умеют только функции. Процедура выполняет действия и ничего не отдаёт. Встроенный язык 1С:Предприятия 8.3 устроен здесь одинаково для всех конфигураций: правила ниже работают и в БП 3.0, и в ЗУП 3.1, и в самописной базе.

Как устроен возврат значения: синтаксис и правила

Минимальная функция выглядит так:

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

// вызов
Результат = Удвоить(21); // Результат = 42

Что происходит при вызове:

  1. Платформа передаёт в функцию параметр Число со значением 21.
  2. Выполняется выражение после Возврат — получается 42.
  3. Работа функции сразу прекращается, код ниже Возврат не выполняется.
  4. Значение 42 подставляется в выражение на месте вызова.

Ключевые правила, которые стоит держать в голове:

  • 🔹 Возврат завершает функцию немедленно. Всё, что ниже, в этой ветке не выполнится.
  • 🔹 Возврат может стоять в нескольких местах. Обычно это проверки в начале функции и основной результат в конце.
  • 🔹 Нет Возврат — результат Неопределено. Ошибки при этом не будет, что и делает ситуацию опасной.
  • 🔹 Тип результата не объявляется. Одна и та же функция может вернуть число в одной ветке и строку в другой. Язык это позволяет, но читать такой код тяжело.
  • 🔹 Вызов можно встроить в выражение: Если ЕстьОстаток(Товар) Тогда, Итог = Цена(Товар) * Количество.

Функцию с ключевым словом Экспорт можно вызывать из других модулей: ОбщегоНазначения.ИмяФункции() в общем модуле, Документы.Реализация.ИмяФункции() в модуле менеджера.

Функция или процедура: в чём разница для результата

Синтаксически они похожи, но ведут себя по-разному.

ПризнакФункцияПроцедура
Ключевые словаФункция … КонецФункцииПроцедура … КонецПроцедуры
Возвращает значениеДа, через Возврат Значение;Нет
Оператор ВозвратС выражениемБез выражения: Возврат; — просто выход
Вызов в выраженииХ = Имя() — можноНельзя, ошибка компиляции модуля
Вызов отдельной строкойМожно, результат отброситсяОбычный способ вызова

Два момента, о которых часто спорят новички.

Можно ли вызвать функцию как процедуру? Да. Строка ЗаписатьВЖурнал(Текст); без присваивания допустима, даже если ЗаписатьВЖурнал — функция. Результат просто потеряется. Иногда так и задумано, но если функция возвращает признак успеха, игнорировать его — плохая идея.

Может ли процедура что-то «вернуть»? Формально нет. На практике результат из процедуры передают через параметры. По умолчанию параметры в 1С передаются по ссылке: если процедура присвоит параметру новое значение, оно окажется в переменной вызывающего кода. Так работают типовые обработчики событий:

Процедура ПередЗаписью(Отказ)
	Если Не ЗначениеЗаполнено(Контрагент) Тогда
		Отказ = Истина; // «ответ» платформе через параметр
	КонецЕсли;
КонецПроцедуры

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

Какого типа бывает результат функции

Вернуть можно значение любого типа, который существует в контексте выполнения. На практике чаще всего встречаются такие.

ТипПримерКогда уместен
Число, Строка, Дата, БулевоВозврат Истина;Проверки, расчёты, форматирование
НеопределеноВозврат Неопределено;«Ничего не нашли»
Ссылка на объектСправочники.Номенклатура.НайтиПоКоду("00-001")Поиск элемента, подбор по условию
СтруктураНовый Структура("Сумма, НДС", 100, 20)Нужно вернуть несколько значений
Массивсписок ссылок, строкНабор однотипных элементов
ТаблицаЗначенийЗапрос.Выполнить().Выгрузить()Табличные данные на сервере
Соответствиеключ → значениеБыстрый поиск по ключу
ОбъектДокумент.ПолучитьОбъект()Дальнейшее изменение и запись

Как вернуть несколько значений сразу

Возврат принимает одно выражение. Если нужно отдать сумму, скидку и текст ошибки, их упаковывают в Структура:

Функция РассчитатьСкидку(Сумма, ПроцентСкидки)
	Результат = Новый Структура("Скидка, КОплате", 0, Сумма);
	Если ПроцентСкидки <= 0 Тогда
		Возврат Результат;
	КонецЕсли;
	Результат.Скидка = Окр(Сумма * ПроцентСкидки / 100, 2);
	Результат.КОплате = Сумма - Результат.Скидка;
	Возврат Результат;
КонецФункции

Расчёт = РассчитатьСкидку(1000, 7);
// Расчёт.Скидка = 70, Расчёт.КОплате = 930

Структура удобнее массива: к полю обращаются по имени (Расчёт.Скидка), а не по номеру (Расчёт[0]), и через полгода код читается без комментариев.

Второй вариант — выходные параметры по ссылке, как в процедуре ПередЗаписью. Функция возвращает Булево (получилось или нет), а данные кладёт в параметр. Этот приём использует сама платформа, о нём ниже.

Как обрабатывать результат вызова без сюрпризов

Получили значение — проверьте его, прежде чем использовать. Особенно если функция ищет что-то в базе.

Проверка на заполненность

Универсальная проверка — ЗначениеЗаполнено(). Она возвращает Ложь для Неопределено, пустой ссылки, нуля, пустой строки и пустой даты.

Товар = Справочники.Номенклатура.НайтиПоКоду(Код);
Если Не ЗначениеЗаполнено(Товар) Тогда
	Сообщить("Товар с кодом " + Код + " не найден");
	Возврат;
КонецЕсли;

НайтиПоКоду и НайтиПоНаименованию при неудаче возвращают пустую ссылку, а не Неопределено. Сравнение Если Товар = Неопределено тут не сработает никогда — условие всегда будет ложным.

Проверка типа

Если функция в разных ветках отдаёт разные типы, сверяйте тип явно:

Значение = ПолучитьНастройку("ЛимитСкидки");
Если ТипЗнч(Значение) = Тип("Число") Тогда
	Лимит = Значение;
Иначе
	Лимит = 0;
КонецЕсли;

Защитные возвраты в начале функции

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

Функция ЦенаСНДС(Цена, СтавкаНДС)
	Если Цена <= 0 Тогда
		Возврат 0;
	КонецЕсли;
	Возврат Окр(Цена * (100 + СтавкаНДС) / 100, 2);
КонецФункции

Для цены 500 и ставки 20 получится 600 — проверьте: 500 × 120 / 100 = 600.

Типичные ошибки с результатом функций

Эти ошибки не дают сообщений при компиляции и всплывают уже у пользователя.

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

Где платформа сама возвращает значения: стандартные механизмы

Многие встроенные методы — тоже функции. Знать, что именно они отдают при неудаче, полезнее, чем помнить синтаксис.

МетодЧто возвращаетЕсли не нашёл
Справочники.Х.НайтиПоКоду()СсылкуПустую ссылку
Массив.Найти(Значение)Индекс элементаНеопределено
ТаблицаЗначений.Найти()Строку таблицыНеопределено
Структура.Свойство(Ключ, Значение)БулевоЛожь, значение не меняется
СтрНайти(Строка, Подстрока)Позицию символа0
Запрос.Выполнить()РезультатЗапросаПустой результат, проверяется Пустой()

Структура.Свойство — возврат через параметр

Этот метод показывает оба способа сразу: результатом функции служит признак Истина/Ложь, а само значение уходит во второй параметр.

Контрагент = Неопределено;
Если Параметры.Свойство("Контрагент", Контрагент) И ЗначениеЗаполнено(Контрагент) Тогда
	Объект.Контрагент = Контрагент;
КонецЕсли;

Результат диалога и закрытия формы

В управляемом интерфейсе модальные окна по умолчанию отключены, поэтому ответ пользователя приходит не как результат функции, а в процедуру-обработчик через ОписаниеОповещения:

&НаКлиенте
Процедура Удалить(Команда)
	Оповещение = Новый ОписаниеОповещения("ПослеОтвета", ЭтотОбъект);
	ПоказатьВопрос(Оповещение, "Удалить строку?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры

&НаКлиенте
Процедура ПослеОтвета(Результат, ДополнительныеПараметры) Экспорт
	Если Результат = КодВозвратаДиалога.Да Тогда
		// удаляем
	КонецЕсли;
КонецПроцедуры

С платформы 8.3.18 есть асинхронный вариант: процедура помечается Асинх, а ответ ждут оператором Ждать — Ответ = Ждать ВопросАсинх("Удалить строку?", РежимДиалогаВопрос.ДаНет);. Код снова читается сверху вниз, как обычный вызов функции.

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

Повторное использование возвращаемых значений

У общего модуля есть свойство «Повторное использование возвращаемых значений» с вариантами «На время вызова» и «На время сеанса». Платформа запоминает результат функции для одинаковых параметров и при повторном вызове отдаёт сохранённое значение, не выполняя код снова.

Так кэшируют редко меняющиеся вещи: настройки учёта, константы, права пользователя. Кэш хранится ограниченное время, а сбросить его досрочно можно методом ОбновитьПовторноИспользуемыеЗначения().

Практические примеры: клиент, сервер, запросы

Функция на сервере, вызов с клиента

Значение, которое серверная функция возвращает на клиент, передаётся по сети. Поэтому тип должен существовать на клиенте: примитивы, ссылки, Структура, Массив, Соответствие подходят. ТаблицаЗначений, РезультатЗапроса, объекты документов на тонкий клиент не передаются.

&НаКлиенте
Процедура ПроверитьОстаток(Команда)
	Остаток = ОстатокНаСервере(Объект.Номенклатура);
	Сообщить("Остаток: " + Формат(Остаток, "ЧН=0; ЧГ=0"));
КонецПроцедуры

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

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

Если таблицу всё же нужно передать, её превращают в массив структур или кладут во временное хранилище: Адрес = ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор); — и возвращают строку Адрес.

Функция-проверка для условия

Функция ДокументМожноИзменять(ДатаДокумента, ДатаЗапрета)
	Возврат Не ЗначениеЗаполнено(ДатаЗапрета) Или ДатаДокумента > ДатаЗапрета;
КонецФункции

Функция возвращает Булево, и её вызов читается как обычная фраза: Если ДокументМожноИзменять(Дата, ДатаЗапрета) Тогда. Логика в одном месте, а не размазана по пяти модулям.

Тернарный оператор как короткий возврат

Когда результат зависит от одного условия, ?() экономит строки:

Функция ТекстСтатуса(Проведен)
	Возврат ?(Проведен, "Проведён", "Черновик");
КонецФункции

Как сделать код быстрее и чище с помощью возвращаемых значений

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

  • ✅ Один серверный вызов вместо нескольких. Каждый вызов с клиента на сервер — сетевой запрос. Если нужны остаток, цена и единица измерения, верните Структура за один вызов, а не дёргайте три функции подряд. Вызов серверной функции в цикле на клиенте — частая причина «тормозов» формы.
  • ✅ Сохраняйте результат в переменную. Если ПолучитьЦену(Товар) > 0 Тогда Сумма = ПолучитьЦену(Товар) * Количество — два обращения к базе. Присвойте цену переменной один раз.
  • ✅ Возвращайте ссылку, а не объект. ПолучитьОбъект() читает из базы весь объект с табличными частями. Если нужен только реквизит, получите его запросом или через ссылку.
  • ✅ Кэшируйте стабильные данные. Настройки, которые читаются в каждом документе, вынесите в общий модуль с повторным использованием.
  • ✅ Одна функция — одна задача и один тип результата. Функция НайтиКонтрагента возвращает ссылку или пустую ссылку. Не надо смешивать в ней ссылку, строку с текстом ошибки и Ложь.
  • ✅ Защитные возвраты вместо вложенных Если. Три проверки в начале с Возврат читаются легче, чем три уровня вложенности.

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

Проверьте себя
Что вернёт функция, если выполнение дошло до КонецФункции без оператора Возврат?
Что вернёт Справочники.Номенклатура.НайтиПоКоду(), если элемент не найден?
Как вернуть из функции сумму и НДС одновременно?

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

Можно ли вернуть значение из обработчика события формы?
Нет, обработчики событий — процедуры. Результат передают через параметры, например Отказ = Истина или СтандартнаяОбработка = Ложь.
Чем `Возврат;` в процедуре отличается от `Возврат` в функции?
В процедуре это просто досрочный выход. В функции после Возврат указывают выражение, которое станет результатом вызова.
Можно ли вернуть из функции другую функцию?
Ссылок на функции во встроенном языке нет. Близкий приём — вернуть ОписаниеОповещения или имя метода строкой, но второй вариант требует Выполнить() и небезопасен.
Почему серверная функция вернула на клиент пустое значение вместо таблицы?
Тип ТаблицаЗначений недоступен на тонком клиенте. Верните массив структур или адрес во временном хранилище.
Обязательно ли объявлять тип возвращаемого значения?
Нет, язык динамический. Тип можно указать в комментарии к функции по стандартам разработки — такие описания понимает контекстная подсказка в 1C:EDT.