Вызов функций во встроенном языке 1С:Предприятия 8.3 — от простой строки кода до вызова сервера

· 10 мин

Содержание

Функцию в 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"));

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

Клиент и сервер: вызов функции из формы

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

ДирективаГде выполняетсяДоступ к данным формыКогда ставить
&НаКлиентеНа клиентеДаРабота с элементами формы, диалоги
&НаСервереНа сервереДа, форма передаётся на серверНужно изменить реквизиты формы и прочитать базу
&НаСервереБезКонтекстаНа сервереНетНужно только прочитать что-то из базы
&НаКлиентеНаСервереБезКонтекстаИ там, и тамНетВспомогательные расчёты без обращения к базе

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

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

&НаСервереБезКонтекста
Функция ЦенаНаСервере(Знач Номенклатура, Знач Дата)
    Возврат РаботаСЦенами.ЦенаНоменклатуры(Номенклатура, Дата);
КонецФункции

Что здесь сделано правильно:

  1. Клиентский обработчик не обращается к базе сам, а вызывает серверную функцию.
  2. Функция без контекста: на сервер уходят два значения, а не вся форма.
  3. Параметры объявлены с Знач. Без него параметры, переданные по ссылке, после серверного вызова передаются обратно на клиент — лишний трафик.

Что можно передавать между клиентом и сервером

Через границу клиент-сервер проходят только сериализуемые значения: примитивы (строка, число, дата, булево), ссылки на объекты базы, Структура, Массив, Соответствие из таких значений. Объекты вроде ДокументОбъект, ТаблицаЗначений или Запрос на клиент не передаются. Таблицу перед передачей превращают в массив структур или помещают во временное хранилище через ПоместитьВоВременноеХранилище().

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

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

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

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

Где объявить функцию, которую нужно вызвать из формы?

Когда вызов функции заканчивается ошибкой

Если функция вызывает исключение, выполнение прерывается и ошибка поднимается к вызывающему коду. Чтобы перехватить её, используют конструкцию Попытка … Исключение … КонецПопытки:

Попытка
    Курс = ПолучитьКурсВалюты(Валюта, Дата);
Исключение
    Инфо = ИнформацияОбОшибке();
    ЗаписьЖурналаРегистрации("Расчёт курса", УровеньЖурналаРегистрации.Ошибка, , ,
        ОбработкаОшибок.ПодробноеПредставлениеОшибки(Инфо));
    Курс = 1;
КонецПопытки;

Свою ошибку внутри функции поднимают через ВызватьИсключение:

Функция ЦенаНоменклатуры(Номенклатура, Дата) Экспорт
    Если Не ЗначениеЗаполнено(Номенклатура) Тогда
        ВызватьИсключение "Не указана номенклатура для расчёта цены";
    КонецЕсли;
    // ...
КонецФункции

Объект ОбработкаОшибок появился в платформе 8.3.17. В более старых версиях используйте ОписаниеОшибки() или ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()).

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

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

Скорость: как вызывать функции без потерь

Сам по себе вызов функции почти бесплатен. Медленным его делает то, что происходит внутри и вокруг.

Серверные вызовы в цикле. Каждый вызов сервера с клиента — отдельный сетевой обмен. 200 строк табличной части с вызовом сервера на каждой — 200 обменов. Соберите данные в массив и обработайте одним вызовом.

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

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

Цикл по строкам на клиенте быстрый: он не обращается к серверу. Серверная функция ЦеныНаСервере делает один запрос с условием В (&Номенклатура) и возвращает соответствие.

Запрос внутри функции, которую зовут в цикле. Та же проблема, только на сервере: 1000 вызовов — 1000 запросов к базе. Решение то же — один запрос на весь набор.

Повторное использование возвращаемых значений. У общего модуля есть свойство «Повторное использование возвращаемых значений» со значениями «На время вызова» и «На время сеанса». Платформа кэширует результат функции для одинаковых параметров. Это подходит для редко меняющихся данных: настройки, константы, права пользователя. Функции такого модуля должны возвращать неизменяемые значения, например ФиксированнаяСтруктура или ФиксированныйМассив.

Контекстный серверный вызов. &НаСервере в форме тянет на сервер все данные формы. В форме документа с большой табличной частью это заметно. Где форма не нужна, ставьте &НаСервереБезКонтекста.

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

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

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