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