Процедура в 1С ничего не возвращает: как всё-таки получить из неё результат

· 9 мин

Содержание

Процедура в 1С не возвращает значение по определению: Возврат в ней пишут без выражения, а строка Возврат Результат; внутри процедуры не пройдёт синтаксический контроль. Чтобы вернуть значение из процедуры 1С, есть два честных пути: превратить её в функцию или отдать результат через параметр, который платформа по умолчанию передаёт по ссылке.

Частое заблуждение — что «вернуть из процедуры» можно только через глобальную переменную. На практике глобальные переменные нужны редко. В 9 случаях из 10 хватает функции или параметра-приёмника, а для нескольких значений — структуры.

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

Встроенный язык 1С различает два вида подпрограмм:

  • 🔹 Процедура (Процедура … КонецПроцедуры) выполняет действия. Её вызывают отдельной инструкцией: ЗаполнитьТабличнуюЧасть();.
  • 🔹 Функция (Функция … КонецФункции) вычисляет и возвращает значение через Возврат Значение;. Её можно поставить в выражение: Сумма = РассчитатьСумму(Документ);.

Процедуру нельзя использовать в выражении. Запись Х = МояПроцедура(); вызовет ошибку компиляции модуля. Обратное допустимо: функцию можно вызвать как процедуру, результат просто отбросится.

Отсюда первый и самый простой способ. Если процедура нужна ради результата — переделайте её в функцию:

// Было
Процедура ПосчитатьСкидку(Сумма)
	Скидка = Сумма * 0.05;
КонецПроцедуры

// Стало
Функция ПосчитатьСкидку(Сумма)
	Скидка = Сумма * 0.05;
	Возврат Скидка;
КонецФункции

Старые вызовы вида ПосчитатьСкидку(1000); после замены продолжат работать: функцию разрешено вызывать без присваивания. Поэтому перевод процедуры в функцию почти никогда ничего не ломает.

В функции оператор Возврат должен стоять на каждой ветке. Если выполнение дошло до КонецФункции без Возврат, функция вернёт Неопределено. Это частый источник «пустых» результатов.

Ранний выход из процедуры

В процедуре Возврат; без значения тоже полезен — он прерывает выполнение:

Процедура ОбработатьСтроку(Строка)
	Если Строка.Количество = 0 Тогда
		Возврат;
	КонецЕсли;
	// основная логика
КонецПроцедуры

Это не возврат результата, а выход. Путать эти две вещи не стоит.

Параметры по ссылке: как процедура меняет переменную вызывающего кода

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

Процедура ПолучитьОстаток(Номенклатура, Остаток)
	Остаток = 0;
	// ... запрос к регистру ...
	Остаток = 125;
КонецПроцедуры

// Вызов
Остаток = 0;
ПолучитьОстаток(Товар, Остаток);
Сообщить(Остаток); // 125

Параметр Остаток работает как «выходной». Вызывающий код заводит переменную, процедура её заполняет.

Когда передача по ссылке не срабатывает

Изменение вернётся в вызывающий код, только если в параметр передали переменную. Если передать литерал или выражение, платформа создаст временное значение, и результат потеряется:

Что передалиВернётся ли изменённое значение
Переменную: Процедура(Х)✅ Да
Литерал: Процедура(0)❌ Нет
Выражение: Процедура(Х + 1)❌ Нет
Свойство объекта: Процедура(Объект.Сумма)Не рассчитывайте на это — передайте сам объект или переменную

Ключевое слово Знач

Слово Знач перед параметром запрещает передачу по ссылке:

Процедура Пересчитать(Знач Количество, Знач Цена, Сумма)
	Количество = Количество * 2; // снаружи не изменится
	Сумма = Количество * Цена;   // вернётся вызывающему коду
КонецПроцедуры

Хорошая практика — помечать Знач все входные параметры. Тогда по сигнатуре сразу видно, какие параметры только читаются, а какие служат для возврата.

Ссылочные типы: структура, массив, таблица значений

С коллекциями есть тонкость. Структура, Массив, ТаблицаЗначений, Соответствие — объекты. В переменной лежит ссылка на объект, а не сам объект. Поэтому:

  • ✅ изменение содержимого коллекции видно снаружи даже при Знач: Массив.Добавить(1), Структура.Вставить("Итог", 10);
  • ❌ присвоение параметру нового объекта при Знач снаружи не видно: Массив = Новый Массив внутри процедуры не заменит массив вызывающего кода.
Процедура ЗаполнитьОшибки(Знач СписокОшибок)
	СписокОшибок.Добавить("Не заполнен контрагент"); // видно снаружи
	СписокОшибок = Новый Массив;                     // снаружи не видно
КонецПроцедуры

Параметры по ссылке при вызове сервера

В управляемом приложении это важно для производительности. Когда клиент вызывает серверную процедуру (&НаСервере или экспортную процедуру общего модуля с флажком «Вызов сервера»), значения параметров сериализуются и уходят на сервер. Параметры без Знач после вызова передаются обратно на клиент — так клиент получает изменения.

Если параметр нужен только на вход, а это большая таблица или длинная строка, пометьте его Знач. Обратной передачи не будет, лишний трафик уйдёт.

На сервер и обратно можно передать только сериализуемые значения. Объект документа (ДокументОбъект) так не передать — используйте ссылку, данные формы или структуру с нужными полями.

Возврат нескольких значений из процедуры через структуру

Когда нужно вернуть три-четыре значения, цепочка выходных параметров становится нечитаемой: Рассчитать(Док, Сумма, НДС, СуммаБезНДС, ЕстьОшибки). Удобнее собрать результат в структуру.

Вариант с функцией:

Функция РассчитатьИтоги(Документ)
	Результат = Новый Структура("Сумма, НДС, ЕстьОшибки", 0, 0, Ложь);
	Для Каждого Строка Из Документ.Товары Цикл
		Результат.Сумма = Результат.Сумма + Строка.Сумма;
		Результат.НДС = Результат.НДС + Строка.СуммаНДС;
	КонецЦикла;
	Возврат Результат;
КонецФункции

Итоги = РассчитатьИтоги(ДокументОбъект);
Сообщить(СтрШаблон("Сумма: %1, НДС: %2", Итоги.Сумма, Итоги.НДС));

Вариант с процедурой, если сигнатуру менять нельзя (например, это обработчик события):

Процедура ПроверитьЗаполнение(Документ, Результат)
	Результат.Вставить("ЕстьОшибки", Не ЗначениеЗаполнено(Документ.Контрагент));
КонецПроцедуры

Результат = Новый Структура;
ПроверитьЗаполнение(ДокументОбъект, Результат);

💡 Структуру создавайте до вызова и с заранее объявленными ключами. Тогда код, который читает Результат.ЕстьОшибки, не упадёт, если процедура вышла раньше времени.

Так устроены и типовые обработчики платформы. В ПередЗаписью(Отказ) и ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты) платформа передаёт параметры, а обработчик «возвращает» решение, присваивая Отказ = Истина. Это тот же механизм передачи по ссылке.

Переменные модуля и глобальные значения

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

Где хранитьОбъявлениеГде видно
Переменная модуля формы&НаКлиенте Перем ИмяПеременной; в начале модуля формыВо всех клиентских процедурах этой формы
Реквизит формыВ редакторе формы, вкладка реквизитовНа клиенте и в серверных вызовах формы, сохраняется между вызовами
Переменная модуля объектаПерем ИмяПеременной Экспорт; в модуле объектаВ обработчиках этого экземпляра объекта
Глобальная переменная клиентаПерем ИмяПеременной Экспорт; в модуле управляемого приложенияВо всём клиентском коде сеанса
Параметр сеансаОбъект метаданных «Параметр сеанса»На сервере в течение сеанса

В общих модулях объявлять переменные нельзя — там допустимы только процедуры и функции.

Почему глобальные переменные — крайний вариант

  • 🧩 Неочевидная связь: по вызову Рассчитать() не видно, что процедура меняет что-то вне себя.
  • 🔄 Значение остаётся от прошлого вызова. Забыли сбросить — получили чужой результат.
  • 🖥️ Переменные модуля формы с &НаКлиенте не видны в &НаСервере. Серверный вызов формы получает реквизиты формы, но не клиентские переменные.

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

Вернуть результат через исключение: когда это уместно

Исключение — способ сообщить вызывающему коду, что процедура не смогла выполнить работу. Результат здесь — текст ошибки, а не данные.

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

Попытка
	ПровестиОплату(Платёж);
Исключение
	ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
	Сообщить(ТекстОшибки);
КонецПопытки;

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

Использовать исключение как обычный канал данных — плохая идея:

  • ❌ Попытка … Исключение медленнее простой проверки условия;
  • ❌ внутри транзакции исключение, перехваченное без повторного вызова, оставляет транзакцию в состоянии, когда дальнейшая запись невозможна;
  • ❌ коды вида ВызватьИсключение "OK:125" и разбор текста в Исключение превращают код в головоломку.

Ловите исключения там, где можете осмысленно обработать ошибку. Пустой блок Исключение КонецПопытки; прячет реальные сбои.

Асинхронный код и фоновые задания: результат приходит позже

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

Описание оповещения

Модальные окна в веб-клиенте и современном интерфейсе не используются. Вместо Ответ = Вопрос(...) процедура передаёт ОписаниеОповещения — указание, какую процедуру вызвать с результатом:

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

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

Свою процедуру можно построить так же. Принимаете параметр ОповещениеОЗавершении, а в конце вызываете ВыполнитьОбработкуОповещения(ОповещениеОЗавершении, Результат). Результат попадёт в первый параметр процедуры-обработчика.

В актуальных версиях платформы 8.3 есть и вторая модель — Асинх Функция и оператор Ждать. Асинхронная функция возвращает Обещание, а Ждать получает из него значение. Код выглядит почти как синхронный.

Фоновое задание и временное хранилище

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

  1. В основном сеансе получите адрес: Адрес = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);.
  2. Передайте адрес фоновому заданию в массиве параметров.
  3. В процедуре задания запишите итог: ПоместитьВоВременноеХранилище(Результат, АдресХранилища);.
  4. После завершения задания прочитайте его: Результат = ПолучитьИзВременногоХранилища(Адрес);.

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

Как вернуть результат в вашем случае?

Сравнение способов вернуть значение

СпособКогда применятьРиски
Функция с ВозвратОдно значение, синхронный вызовЗабытый Возврат на одной из веток даёт Неопределено
Параметр по ссылкеОбработчики событий, нельзя менять вид подпрограммыПередали выражение вместо переменной — результат потерян
СтруктураНесколько значенийОпечатка в имени ключа всплывёт только при выполнении
Переменная модуляКэш на время жизни формы или объектаУстаревшее значение, неявные связи, не видна на сервере
ИсключениеОшибка, после которой работу продолжать нельзяМедленно, ломает транзакцию, если неправильно перехватить
Оповещение, АсинхОтвет пользователя, асинхронные методыРезультат приходит в другую процедуру — сложнее отладка
Временное хранилищеФоновые заданияНужно контролировать время жизни адреса

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

  • ⚠️ Возврат Х; в процедуре. Модуль не скомпилируется. Замените Процедура на Функция и КонецПроцедуры на КонецФункции.
  • ⚠️ Функция возвращает Неопределено. В одной из веток Если нет Возврат. Поставьте Возврат в конце функции со значением по умолчанию.
  • ⚠️ Выходной параметр помечен Знач. Процедура меняет копию, снаружи пусто. Уберите Знач у параметров, через которые возвращаете результат.
  • ⚠️ Клиентская переменная пустая в серверной процедуре. Переменные &НаКлиенте на сервер не уходят. Передайте значение параметром или храните в реквизите формы.
  • ⚠️ Изменения пропали после вызова сервера. Серверная процедура меняла параметр с Знач или получила копию свойства. Возвращайте результат функцией.
  • ⚠️ Одна и та же структура для нескольких вызовов в цикле. Ключи от прошлой итерации остаются. Создавайте Новый Структура на каждой итерации или сбрасывайте поля явно.

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

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