Функция внутри процедуры 1С: Знач, ссылки, Возврат и контекст вызова

· 10 мин

Содержание

Чтобы вызвать функцию из процедуры 1С, напишите её имя со скобками и присвойте результат переменной: Сумма = РассчитатьСумму(Цена, Количество);. Если функция лежит в том же модуле, больше ничего не нужно. Если в другом модуле — у неё должно стоять слово Экспорт, а вызов пишется через имя модуля: ОбщийМодульРасчетов.РассчитатьСумму(Цена, Количество).

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

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

Разница одна, но принципиальная: функция возвращает значение, процедура — нет.

ПризнакПроцедураФункция
Ключевые словаПроцедура … КонецПроцедурыФункция … КонецФункции
Возврат значениянет, Возврат; только прерываетВозврат Значение;
Можно ли в выражениинетда
Можно ли вызвать отдельной строкойдада, результат отбрасывается

Последняя строка таблицы важна. Функцию можно вызвать «как процедуру», без присваивания: ЗаписатьВЖурнал(Текст);. Платформа выполнит её и просто выбросит результат. Обратное не работает : процедуру нельзя поставить справа от знака = — модуль не скомпилируется.

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

Порядок объявления в модуле не важен. В 1С 8.3 процедура в начале модуля может вызвать функцию, описанную в самом конце, — предварительное объявление, как в 7.7, не требуется.

Синтаксис вызова функции из процедуры и приём результата

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

&НаКлиенте
Процедура Рассчитать(Команда)
	
	Итог = СуммаСоСкидкой(Объект.Сумма, 5);
	Сообщить(СтрШаблон("Итого со скидкой: %1", Формат(Итог, "ЧДЦ=2; ЧГ=0")));
	
КонецПроцедуры

&НаКлиенте
Функция СуммаСоСкидкой(Сумма, ПроцентСкидки)
	
	Возврат Окр(Сумма * (100 - ПроцентСкидки) / 100, 2);
	
КонецФункции

При Объект.Сумма = 12 000 функция вернёт 11 400: 12 000 × 95 / 100.

Результат функции можно использовать сразу, без промежуточной переменной:

  • 📌 в условии: Если ЕстьОстаток(Товар) Тогда;
  • 📌 в выражении: Всего = НДС(Сумма) + Сумма;;
  • 📌 как параметр другой функции: Сообщить(ПредставлениеСуммы(СуммаСоСкидкой(Сумма, 5)));;
  • 📌 через точку, если функция возвращает объект или структуру: ПолучитьНастройки().Склад.

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

Возврат отдаёт ровно одно значение. Когда нужно два-три результата, есть два пути.

Первый — вернуть структуру:

Функция ОстатокИСебестоимость(Товар)
	
	Результат = Новый Структура("Количество, Сумма", 0, 0);
	// ... заполнение по данным запроса
	Возврат Результат;
	
КонецФункции

Процедура Проверить()
	
	Данные = ОстатокИСебестоимость(Товар);
	Если Данные.Количество = 0 Тогда
		Сообщить("Нет остатка");
	КонецЕсли;
	
КонецПроцедуры

Второй — заполнить параметры, переданные по ссылке, а функцией вернуть только признак успеха. Этот приём разберём в следующем разделе.

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

Передача параметров в функцию: по ссылке и через Знач

По умолчанию параметр процедуры или функции 1С передаётся по ссылке. Функция работает не с копией, а с той же переменной, что указана при вызове. Присвоили параметру новое значение внутри — изменилась и переменная в вызывающей процедуре.

Процедура Тест()
	
	Счетчик = 10;
	Увеличить(Счетчик);
	Сообщить(Счетчик); // 11
	
КонецПроцедуры

Функция Увеличить(Число)
	Число = Число + 1;
	Возврат Число;
КонецФункции

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

Функция Увеличить(Знач Число)
	Число = Число + 1;
	Возврат Число;
КонецФункции

Теперь после вызова Счетчик останется равным 10, а функция вернёт 11.

Тонкость с коллекциями и объектами

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

Что делает функция с параметромБез ЗначС Знач
Массив.Добавить(1)массив вызывающего меняетсямассив вызывающего тоже меняется
Массив = Новый Массиву вызывающего теперь новый массиву вызывающего старый массив
Число = Число + 1у вызывающего изменилосьу вызывающего прежнее

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

Когда передача по ссылке полезна

  • ✅ функция возвращает признак успеха, а данные отдаёт через параметр: Если НайтиКонтрагента(ИНН, Контрагент) Тогда;
  • ✅ нужно накопить текст ошибок в одной строке через несколько вызовов;
  • ⚠️ во всех остальных случаях это источник трудноуловимых ошибок: переменная «сама» меняется после вызова.

Хорошая привычка: ставить Знач у всех параметров-примитивов, которые функция не должна менять. Читатель кода сразу видит, какие параметры — входные.

Необязательные параметры и значения по умолчанию

Параметр становится необязательным, если в объявлении ему присвоено значение:

Функция ПредставлениеСуммы(Сумма, Валюта = "руб.", ДробнаяЧасть = Истина)
	
	ФорматнаяСтрока = ?(ДробнаяЧасть, "ЧДЦ=2", "ЧДЦ=0");
	Возврат СтрШаблон("%1 %2", Формат(Сумма, ФорматнаяСтрока), Валюта);
	
КонецФункции

Варианты вызова:

  1. ПредставлениеСуммы(1500) — оба необязательных параметра берутся по умолчанию.
  2. ПредставлениеСуммы(1500, "USD") — меняется только валюта.
  3. ПредставлениеСуммы(1500, , Ложь) — валюта по умолчанию, дробная часть отключена. Пропущенный параметр обозначают пустым местом между запятыми.

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

  • 📌 значением по умолчанию может быть только литерал: число, строка, дата, Истина, Ложь, Неопределено, Null. Выражение вроде ТекущаяДата() или Новый Массив там не допускается;
  • 📌 если нужен «пустой массив по умолчанию», объявите параметр = Неопределено и создайте массив внутри функции, когда параметр не передан;
  • 📌 необязательные параметры удобнее ставить в конец списка — иначе вызов превращается в ряд запятых.
Функция Отфильтровать(Список, Исключения = Неопределено)
	
	Если Исключения = Неопределено Тогда
		Исключения = Новый Массив;
	КонецЕсли;
	// ...
	
КонецФункции

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

Вызов функции из другого модуля, с клиента и на сервере

Пока функция и процедура живут в одном модуле и в одном контексте, вызов тривиален. Сложности начинаются, когда их разделяет граница модуля или граница «клиент — сервер».

Из другого модуля

У функции в объявлении должно быть слово Экспорт:

Функция СуммаСоСкидкой(Сумма, ПроцентСкидки) Экспорт

Как писать вызов, зависит от того, где функция находится:

Где лежит функцияКак вызвать
Общий модульИмяОбщегоМодуля.ИмяФункции()
Модуль менеджера документаДокументы.ИмяДокумента.ИмяФункции()
Модуль менеджера справочникаСправочники.ИмяСправочника.ИмяФункции()
Модуль объектаДокументОбъект.ИмяФункции(), где ДокументОбъект получен, например, через Ссылка.ПолучитьОбъект()
Тот же модульИмяФункции() без префикса, Экспорт не нужен

Общий модуль: какие флажки поставить

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

  • 🖥️ Клиент (управляемое приложение) — функцию можно вызвать с клиента, выполнится на клиенте;
  • 🗄️ Сервер — функция доступна из серверного кода;
  • 🔁 Вызов сервера — функцию серверного модуля можно вызвать с клиента, выполнится она на сервере;
  • 🔀 Клиент + Сервер без «Вызов сервера» — код компилируется в обоих местах и выполняется там, откуда вызван.

Если клиентская процедура вызывает функцию серверного модуля без флажка «Вызов сервера», модуль не скомпилируется: на клиенте такой общий модуль просто не виден, и проверка выдаст ошибку вида Переменная не определена (ИмяМодуля).

В модуле формы: директивы компиляции

В модуле управляемой формы перед каждой процедурой стоит директива:

ДирективаГде выполняетсяЧто доступно
&НаКлиентена клиентереквизиты формы, интерактивные действия
&НаСерверена серверереквизиты формы и база данных; форма передаётся на сервер целиком
&НаСервереБезКонтекстана серверетолько база и параметры; реквизиты формы недоступны
&НаКлиентеНаСервереБезКонтекстатам, откуда вызванатолько параметры

Клиентская процедура может вызвать серверную функцию напрямую:

&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
	
	Объект.Договор = ОсновнойДоговор(Объект.Контрагент);
	
КонецПроцедуры

&НаСервереБезКонтекста
Функция ОсновнойДоговор(Знач Контрагент)
	
	Возврат Контрагент.ДоговорПоУмолчанию;
	
КонецФункции

Обратное не работает: из &НаСервере вызвать &НаКлиенте нельзя. Если серверу нужно что-то сделать на клиенте, верните признак в результате функции и обработайте его в клиентской процедуре.

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

При вызове с клиента на сервер параметры сериализуются и передаются по сети. Параметры без Знач после завершения функции передаются обратно на клиент — так платформа сохраняет семантику передачи по ссылке. Это двойной трафик.

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

Через границу проходят только сериализуемые значения: примитивы, ссылки, массивы, структуры, соответствия, данные формы. Объект документа (ДокументОбъект) на клиент не передаётся — его там просто нет.

Асинхронные функции

Начиная с платформы 8.3.18 есть ключевые слова Асинх и Ждать. Асинхронная функция возвращает Обещание, а результат получают через Ждать:

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

Без Ждать в переменную попадёт объект обещания, а не ответ пользователя. Процедура, внутри которой есть Ждать, сама должна быть объявлена с Асинх.

Частые ошибки при вызове функции и как их исправить

Диагностика: выберите симптом

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

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

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

Одна функция — одна задача. Если в имени напрашивается «И» (ПроверитьИЗаписать), это две функции. Короткие функции проще вызывать из разных процедур и проще тестировать.

Имя — глагол или существительное по смыслу. Функции, которые возвращают значение, называйте по результату: ОсновнойДоговор(), ОстатокТовара(). Процедуры — по действию: ЗаполнитьТабличнуюЧасть().

Не вызывайте сервер в цикле. Классическая ошибка производительности:

// плохо: N серверных вызовов
Для Каждого Строка Из Объект.Товары Цикл
	Строка.Цена = ЦенаНаСервере(Строка.Номенклатура);
КонецЦикла;

При 200 строках это 200 обращений к серверу. Лучше собрать номенклатуру в массив, передать его одним вызовом и получить соответствие «товар → цена»:

// хорошо: один серверный вызов
СписокТоваров = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
	СписокТоваров.Добавить(Строка.Номенклатура);
КонецЦикла;

Цены = ЦеныНаСервере(СписокТоваров);
Для Каждого Строка Из Объект.Товары Цикл
	Строка.Цена = Цены.Получить(Строка.Номенклатура);
КонецЦикла;

Здесь ЦеныНаСервере — функция &НаСервереБезКонтекста с параметром Знач Товары, которая одним запросом выбирает цены и возвращает Соответствие.

Предпочитайте &НаСервереБезКонтекста. Директива &НаСервере тащит на сервер всю форму с реквизитами. Если функции нужны только два-три значения, передайте их параметрами.

Не злоупотребляйте Выполнить() и Вычислить(). Вызов функции по имени из строки работает, но компилятор не проверит имя, а в безопасном режиме и в веб-клиенте такой код ограничен. В типовых конфигурациях на БСП для этого есть общая функция ОбщегоНазначения.ВыполнитьМетодКонфигурации, которая проверяет имя перед вызовом.

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

Перед тем как вызвать функцию из другой процедуры
Выполнено: 0 / 6

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

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