При разработке на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью реализовать один и тот же алгоритм, который должен выполняться как в контексте клиентского приложения, так и на стороне сервера. Типичный пример — расчет контрольной суммы, форматирование сложной строки или проверка корректности данных. Чтобы не дублировать код и не создавать две идентичные функции, мы можем воспользоваться специальными механизмами платформы. В этой статье мы подробно разберем, как реализовать «универсальные» функции в модуле управляемой формы и какие нюансы при этом важно учитывать.
Для решения поставленной задачи в управляемых формах предусмотрена специальная директива компиляции — &НаКлиентеНаСервереБезКонтекста. Разберем механизм ее работы. Когда мы помечаем процедуру или функцию этой директивой, платформа компилирует этот участок кода дважды: один экземпляр попадает в клиентский дистрибутив, а второй — в серверный.
Рассмотрим пример объявления такой функции:
&НаКлиентеНаСервереБезКонтекста
Функция ВычислитьСуммуСНДС(СуммаБезНДС, СтавкаНДС)
Возврат СуммаБезНДС * (1 + СтавкаНДС / 100);
КонецФункции
Проанализируем ситуацию: если мы вызовем эту функцию из клиентской процедуры &НаКлиенте, выполнение произойдет на стороне клиента без обращения к серверу. Если же вызов пойдет из серверного контекста &НаСервере, код отработает на сервере. Это позволяет нам писать максимально эффективный код, минимизируя лишние сетевые вызовы между клиентом и сервером.
Важнейшим аспектом использования данной директивы является приписка БезКонтекста. Это означает, что функция не имеет доступа к данным формы (реквизитам, элементам управления) напрямую через ЭтотОбъект или просто по имени реквизита. Выясним причину такого ограничения: поскольку код должен уметь выполняться на клиенте, он не может «видеть» серверные данные формы, и наоборот.
Чтобы обойти это ограничение, нам необходимо передавать все нужные данные в качестве аргументов. Посмотрим на примере, как правильно организовать передачу данных:
&НаКлиенте
Процедура КомандаРассчитать(Команда)
// Реквизит формы "Цена" и "Количество" передаем как параметры
ИтоговаяСумма = РассчитатьИтог(Объект.Цена, Объект.Количество);
КонецПроцедуры
&НаКлиентеНаСервереБезКонтекста
Функция РассчитатьИтог(Знач Цена, Знач Количество)
Возврат Цена * Количество;
КонецФункции
Обратите внимание, что если внутри функции вам нужно изменить данные формы, вы не сможете сделать это напрямую. Функция должна вернуть результат (например, структуру или массив), который вызывающая (контекстная) процедура уже применит к реквизитам формы.
Проанализируем сложный случай, упомянутый в обсуждении: использование ADO для подключения к внешним базам данных. В некоторых случаях драйверы ODBC могут быть установлены только на клиентских машинах или только на сервере. Использование &НаКлиентеНаСервереБезКонтекста позволяет использовать один и тот же код инициализации подключения.
Однако здесь кроется серьезная техническая ловушка, связанная с жизненным циклом объектов:
ADODB.Connection) не могут передаваться между клиентом и сервером. Они «не сериализуются». Если вы создали объект на сервере, вы не сможете вернуть его на клиент.Рассмотрим пример кода для работы с внешним соединением:
&НаКлиентеНаСервереБезКонтекста
Функция ПолучитьСоединениеADO(СтрокаПодключения)
Попытка
Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.ConnectionString = СтрокаПодключения;
Соединение.Open();
Возврат Соединение;
Исключение
ЗаписатьЛогОшибки(ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
Важный совет: Если вы используете такой подход, помните, что результат выполнения функции (объект ADO) должен оставаться в той же среде, где он был создан. Не пытайтесь передать его через границу «клиент-сервер».
Иногда внутри универсальной функции все же требуется небольшое различие в логике для разных сред. Для этого мы используем инструкции препроцессора #Если. Это позволяет сохранить общую структуру алгоритма, точечно меняя специфичные вызовы.
&НаКлиентеНаСервереБезКонтекста
Процедура ИнформироватьПользователя(ТекстСообщения)
#Если Сервер Тогда
// На сервере можем записать в журнал регистрации
ЗаписьЖурналаРегистрации("Событие", УровеньЖурналаРегистрации.Предупреждение,,, ТекстСообщения);
#Иначе
// На клиенте просто выводим сообщение
Сообщить(ТекстСообщения);
#КонецЕсли
КонецПроцедуры
Если логика, которую вы пишете, может понадобиться более чем в одной форме, профессиональным стандартом считается вынос такого кода в Общие модули. Чтобы добиться эффекта, аналогичного директиве &НаКлиентеНаСервереБезКонтекста, у общего модуля в свойствах должны быть установлены флаги:
В этом случае любая экспортная функция такого модуля будет доступна в обеих средах. Это делает архитектуру решения более чистой и упрощает повторное использование кода. Однако для небольших вспомогательных расчетов внутри конкретной обработки или формы директива &НаКлиентеНаСервереБезКонтекста остается отличным и быстрым инструментом.
Резюмируем: использование аналогов клиент-серверных функций в управляемой форме не только возможно, но и рекомендуется для оптимизации кода. Главное — помнить об отсутствии контекста и невозможности передачи несериализуемых объектов (таких как COM-соединения или ссылки на серверные объекты типа ДокументОбъект) между средами исполнения.