Вызов функций во встроенном языке 1С:Предприятия 8.3 — от простой строки кода до вызова сервера
Содержание
Функцию в 1С вызывают по имени со скобками и присваивают результат переменной: Сумма = РассчитатьСкидку(Цена, Процент);. Если функция лежит в другом модуле, перед именем пишут имя общего модуля или менеджера объекта через точку (РаботаСЦенами.РассчитатьСкидку(...)), а в самом модуле у неё должно стоять ключевое слово Экспорт.
Всё остальное — детали, на которых и спотыкаются: в каком контексте выполняется код (клиент или сервер), как передаются параметры, что вернётся при ошибке. Правила одинаковы для всей платформы 8.3 и не зависят от конфигурации: в БП, ЗУП и УТ код пишется одинаково.
Как устроен вызов функции в коде 1С
Функция во встроенном языке отличается от процедуры одним: она возвращает значение через оператор Возврат.
Функция РассчитатьСкидку(Цена, Процент)
Возврат Цена * Процент / 100;
КонецФункции
// вызов
Скидка = РассчитатьСкидку(1500, 10); // Скидка = 150
Что нужно помнить о синтаксисе:
- 🔹 Скобки обязательны даже без параметров:
Результат = ТекущийПользователь();. - 🔹 Функцию можно вызвать как процедуру, без присваивания:
РассчитатьСкидку(1500, 10);. Результат просто отбросится. - 🔹 Наоборот нельзя: процедуру нельзя поставить справа от
=или внутрь выражения — модуль не скомпилируется. - 🔹 Результат функции можно сразу использовать в выражении:
Если ЭтоАдминистратор() Тогда,Итог = Сумма - РассчитатьСкидку(Сумма, 5);. - 🔹 Имя регистронезависимо:
рассчитатьскидку()иРассчитатьСкидку()— один и тот же вызов. Но пишите так, как объявлено: так проще искать по модулю.
В локальном модуле (тот же модуль формы или объекта) функция видна и без Экспорт. Порядок объявления не важен: можно вызвать функцию, описанную ниже по тексту модуля.
Функция или процедура: что выбрать
| Задача | Что писать |
|---|---|
| Нужно получить результат (сумму, ссылку, флаг) | Функция с Возврат Значение; |
| Нужно только выполнить действие (записать, заполнить) | Процедура |
| Нужно вернуть несколько значений | Функция, возвращающая Структура |
| Нужно изменить переданный объект «на месте» | Процедура с параметром без Знач |
Вызов функции из другого модуля 1С
Снаружи модуля функция доступна только с ключевым словом Экспорт после списка параметров:
Функция ЦенаНоменклатуры(Номенклатура, Дата) Экспорт
// ...
Возврат Цена;
КонецФункции
Дальше синтаксис зависит от того, где функция лежит.
| Где объявлена функция | Как вызвать |
|---|---|
Общий модуль РаботаСЦенами | РаботаСЦенами.ЦенаНоменклатуры(Ссылка, ТекущаяДата()) |
| Общий модуль с флагом «Глобальный» | Без имени модуля: ЦенаНоменклатуры(Ссылка, ТекущаяДата()) |
| Модуль менеджера справочника | Справочники.Номенклатура.ЦенаНоменклатуры(Ссылка, Дата) |
| Модуль объекта документа | ДокументОбъект.ЦенаНоменклатуры(...), где ДокументОбъект = Ссылка.ПолучитьОбъект() |
| Модуль другой открытой формы | Форма.ИмяФункции(), где Форма получена через ПолучитьФорму() или ОткрытьФорму() |
| Внешняя обработка | Через объект обработки: Обработка = ВнешниеОбработки.Создать(ИмяФайла); Обработка.ИмяФункции() |
Модуль менеджера — хорошее место для функций, которые не требуют конкретного объекта: «найти по коду», «получить реквизиты по умолчанию». Модуль объекта — для того, что работает с полями этого объекта.
Свойства общего модуля
Общий модуль открывают в конфигураторе: ветка Общие → Общие модули. В палитре свойств есть флажки, которые решают, где функцию вообще можно вызвать:
- 🖥️ Клиент (управляемое приложение) — функции доступны в клиентском коде.
- 🗄️ Сервер — функции выполняются на сервере, с клиента напрямую не видны.
- 🔁 Вызов сервера — серверный модуль, функции которого можно вызывать с клиента.
- 🔌 Внешнее соединение — доступ при подключении через COM-соединение.
- 🌐 Глобальный — функции вызываются без имени модуля.
- 🔒 Привилегированный — код выполняется без проверки прав текущего пользователя.
Если флаг «Вызов сервера» не установлен, а вы пишете вызов в клиентской процедуре, конфигуратор при проверке модуля выдаст Процедура или функция с указанным именем не определена.
Вызов функции по имени из строки
Иногда имя функции известно только во время выполнения: например, оно хранится в настройках. Для этого есть Вычислить() для функций и Выполнить() для процедур:
ИмяМетода = "РаботаСЦенами.ЦенаНоменклатуры";
Цена = Вычислить(ИмяМетода + "(Номенклатура, Дата)");
Никогда не подставляйте в Вычислить() и Выполнить() строку, которую ввёл пользователь или которая пришла из файла — это готовая дыра для выполнения чужого кода. В типовых конфигурациях на БСП вместо этого используют ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяМетода, Параметры): он проверяет, что имя метода корректно, и выполняет его в безопасном режиме, где это возможно. Получить общий модуль по имени помогает ОбщегоНазначения.ОбщийМодуль("РаботаСЦенами").
Параметры и возвращаемое значение
По ссылке и по значению
По умолчанию параметр передаётся по ссылке: если функция присвоит ему новое значение, оно изменится и у вызывающего кода.
Функция Удвоить(Число)
Число = Число * 2;
Возврат Число;
КонецФункции
А = 5;
Б = Удвоить(А); // Б = 10, и А тоже = 10
Ключевое слово Знач перед параметром отключает это поведение: функция работает с копией.
Функция Удвоить(Знач Число)
Число = Число * 2;
Возврат Число;
КонецФункции
А = 5;
Б = Удвоить(А); // Б = 10, А осталось 5
Знач защищает только саму переменную. Если передать таблицу значений или структуру, функция всё равно может добавить в неё строки или поменять поля — изменится исходная коллекция. Нельзя только заменить её целиком на другую.
Необязательные параметры
Значение по умолчанию задают прямо в объявлении. Такие параметры ставят в конец списка:
Функция Представление(Сумма, Валюта = "руб.", Знаков = 2)
Возврат Формат(Сумма, "ЧДЦ=" + Формат(Знаков, "ЧГ=0")) + " " + Валюта;
КонецФункции
Представление(1250.5); // "1 250,50 руб."
Представление(1250.5, "USD"); // "1 250,50 USD"
Представление(1250.5, , 0); // средний параметр пропущен запятой
Пропущенный параметр без значения по умолчанию получит Неопределено. Это частый источник ошибок, поэтому в начале функции такие параметры полезно проверять.
Несколько значений на выходе
Возврат возвращает одно значение. Если нужно отдать три — соберите их в структуру:
Функция ИтогиДокумента(ДокументСсылка)
Итоги = Новый Структура("Сумма, НДС, Количество", 0, 0, 0);
Для Каждого Строка Из ДокументСсылка.Товары Цикл
Итоги.Сумма = Итоги.Сумма + Строка.Сумма;
Итоги.НДС = Итоги.НДС + Строка.СуммаНДС;
Итоги.Количество = Итоги.Количество + Строка.Количество;
КонецЦикла;
Возврат Итоги;
КонецФункции
Итоги = ИтогиДокумента(Ссылка);
Сообщить("Сумма: " + Формат(Итоги.Сумма, "ЧДЦ=2"));
Если в функции несколько веток, каждая должна заканчиваться Возврат. Если выполнение дойдёт до КонецФункции без него, функция вернёт Неопределено.
Клиент и сервер: вызов функции из формы
В управляемом приложении код формы делится директивами компиляции. От директивы зависит, откуда функцию можно вызвать и что ей доступно.
| Директива | Где выполняется | Доступ к данным формы | Когда ставить |
|---|---|---|---|
&НаКлиенте | На клиенте | Да | Работа с элементами формы, диалоги |
&НаСервере | На сервере | Да, форма передаётся на сервер | Нужно изменить реквизиты формы и прочитать базу |
&НаСервереБезКонтекста | На сервере | Нет | Нужно только прочитать что-то из базы |
&НаКлиентеНаСервереБезКонтекста | И там, и там | Нет | Вспомогательные расчёты без обращения к базе |
Типичная связка в модуле формы:
&НаКлиенте
Процедура НоменклатураПриИзменении(Элемент)
Объект.Цена = ЦенаНаСервере(Объект.Номенклатура, Объект.Дата);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ЦенаНаСервере(Знач Номенклатура, Знач Дата)
Возврат РаботаСЦенами.ЦенаНоменклатуры(Номенклатура, Дата);
КонецФункции
Что здесь сделано правильно:
- Клиентский обработчик не обращается к базе сам, а вызывает серверную функцию.
- Функция без контекста: на сервер уходят два значения, а не вся форма.
- Параметры объявлены с
Знач. Без него параметры, переданные по ссылке, после серверного вызова передаются обратно на клиент — лишний трафик.
Что можно передавать между клиентом и сервером
Через границу клиент-сервер проходят только сериализуемые значения: примитивы (строка, число, дата, булево), ссылки на объекты базы, Структура, Массив, Соответствие из таких значений. Объекты вроде ДокументОбъект, ТаблицаЗначений или Запрос на клиент не передаются. Таблицу перед передачей превращают в массив структур или помещают во временное хранилище через ПоместитьВоВременноеХранилище().
Асинхронные функции
Начиная с платформы 8.3.18 клиентские функции можно объявить с ключевым словом Асинх, а результат ждать оператором Ждать. Это заменяет громоздкое ОписаниеОповещения для диалогов:
&НаКлиенте
Асинх Процедура УдалитьСтроку(Команда)
Ответ = Ждать ВопросАсинх("Удалить строку?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
// удаление
КонецЕсли;
КонецПроцедуры
Асинхронная функция возвращает не значение, а Обещание. Без Ждать вы получите сам объект обещания, а не результат.
Когда вызов функции заканчивается ошибкой
Если функция вызывает исключение, выполнение прерывается и ошибка поднимается к вызывающему коду. Чтобы перехватить её, используют конструкцию Попытка … Исключение … КонецПопытки:
Попытка
Курс = ПолучитьКурсВалюты(Валюта, Дата);
Исключение
Инфо = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("Расчёт курса", УровеньЖурналаРегистрации.Ошибка, , ,
ОбработкаОшибок.ПодробноеПредставлениеОшибки(Инфо));
Курс = 1;
КонецПопытки;
Свою ошибку внутри функции поднимают через ВызватьИсключение:
Функция ЦенаНоменклатуры(Номенклатура, Дата) Экспорт
Если Не ЗначениеЗаполнено(Номенклатура) Тогда
ВызватьИсключение "Не указана номенклатура для расчёта цены";
КонецЕсли;
// ...
КонецФункции
Объект ОбработкаОшибок появился в платформе 8.3.17. В более старых версиях используйте ОписаниеОшибки() или ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()).
Не оборачивайте в Попытка каждую строку. Перехватывайте ошибку там, где знаете, что с ней делать: подставить значение, показать понятное сообщение, записать в журнал. Пустой блок Исключение без записи в журнал прячет проблему, и её потом невозможно найти.
Скорость: как вызывать функции без потерь
Сам по себе вызов функции почти бесплатен. Медленным его делает то, что происходит внутри и вокруг.
Серверные вызовы в цикле. Каждый вызов сервера с клиента — отдельный сетевой обмен. 200 строк табличной части с вызовом сервера на каждой — 200 обменов. Соберите данные в массив и обработайте одним вызовом.
// Плохо: вызов сервера на каждой строке
Для Каждого Строка Из Объект.Товары Цикл
Строка.Цена = ЦенаНаСервере(Строка.Номенклатура, Объект.Дата);
КонецЦикла;
// Лучше: один вызов на все строки
Номенклатура = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
Номенклатура.Добавить(Строка.Номенклатура);
КонецЦикла;
Цены = ЦеныНаСервере(Номенклатура, Объект.Дата); // Соответствие: Номенклатура -> Цена
Для Каждого Строка Из Объект.Товары Цикл
Строка.Цена = Цены.Получить(Строка.Номенклатура);
КонецЦикла;
Цикл по строкам на клиенте быстрый: он не обращается к серверу. Серверная функция ЦеныНаСервере делает один запрос с условием В (&Номенклатура) и возвращает соответствие.
Запрос внутри функции, которую зовут в цикле. Та же проблема, только на сервере: 1000 вызовов — 1000 запросов к базе. Решение то же — один запрос на весь набор.
Повторное использование возвращаемых значений. У общего модуля есть свойство «Повторное использование возвращаемых значений» со значениями «На время вызова» и «На время сеанса». Платформа кэширует результат функции для одинаковых параметров. Это подходит для редко меняющихся данных: настройки, константы, права пользователя. Функции такого модуля должны возвращать неизменяемые значения, например ФиксированнаяСтруктура или ФиксированныйМассив.
Контекстный серверный вызов. &НаСервере в форме тянет на сервер все данные формы. В форме документа с большой табличной частью это заметно. Где форма не нужна, ставьте &НаСервереБезКонтекста.
Найти, какая функция тормозит, помогает замер производительности: в конфигураторе при отладке меню Отладка → Замер производительности. Он показывает время и число выполнений каждой строки кода.
Частые вопросы
Можно ли вызвать клиентскую функцию с сервера?
Как вызвать функцию из внешней обработки в своей конфигурации?
ВнешниеОбработки.Создать() и вызовите экспортную функцию её модуля объекта: Обработка.ИмяФункции(). Код выполняется на сервере, поэтому создавать объект обработки нужно в серверной процедуре.