Многие разработчики на платформе 1С сталкиваются с, на первый взгляд, нелогичным и неудобным ограничением: невозможно напрямую передать объект формы из клиентского кода (например, из общего клиентского модуля) в процедуру серверного общего модуля. Эта особенность заставляет писать много кода в модуле самой формы, что мешает его переиспользованию и усложняет поддержку. Давайте разберемся, почему это ограничение существует, в чем его технический смысл и как правильно структурировать код, чтобы обойти эту проблему, не нарушая архитектурных принципов платформы.
Чтобы понять причину ограничения, нужно проанализировать, как устроена работа управляемой формы в клиент-серверной архитектуре 1С. Управляемая форма — это не единый объект, а сущность, которая существует одновременно в двух местах: на клиенте и на сервере.
Когда вы из клиентского кода формы вызываете процедуру с директивой &НаСервере, происходит так называемый контекстный серверный вызов. Это очень важный и ресурсоемкий механизм. Платформа не просто передает "ссылку" на сервер. Она выполняет следующие действия:
Как видно из описания, это сложный процесс. Один из участников форума привел отличную аналогию: "контекстный вызов — это как разобрать/собрать весь лагерь, чтобы сходить за спичками". Платформа сознательно ограничивает возможность таких вызовов из любого места (например, из клиентских общих модулей), чтобы защитить систему от неконтролируемого падения производительности. Если бы каждый вызов серверного общего модуля с клиента приводил к передаче всего "тяжелого" контекста формы, система работала бы очень медленно.
Еще одна причина связана с жизненным циклом объектов. Как объясняли на форуме, передача сложных мутабельных объектов между клиентом и сервером — нетривиальная задача. Если бы мы передали форму на сервер, а в это время пользователь закрыл ее на клиенте, серверная ссылка стала бы невалидной. Управление такими ситуациями (подсчет ссылок, сборка мусора) значительно усложнило бы и замедлило бы платформу. Поэтому для простых объектов (Массив, Структура) при передаче на сервер создается их полная копия.
Посмотрим на пример, демонстрирующий это. При передаче массива в серверную процедуру изменяется его копия, а исходный массив на клиенте остается нетронутым.
&НаКлиенте
Процедура Команда1(Команда)
Массив = Новый Массив;
МассивНаКлиенте(Массив);
Сообщить(Массив.Количество()); // Выведет: 1
Массив = Новый Массив;
МассивНаСервере(Массив);
Сообщить(Массив.Количество()); // Выведет: 0
КонецПроцедуры
&НаКлиенте
Процедура МассивНаКлиенте(Знач Массив)
Массив.Добавить(42);
КонецПроцедуры
&НаСервере
Процедура МассивНаСервере(Знач Массив)
Массив.Добавить(42);
КонецПроцедуры
С формой так поступить нельзя — нельзя просто создать ее копию на сервере. Нужно найти существующий экземпляр и синхронизировать его. Эта синхронизация жестко привязана к вызову из модуля самой формы.
Несмотря на ограничение, выносить код в общие модули можно и нужно. Для этого используется стандартный и единственно верный шаблон: вызов общего модуля через процедуру-посредник в модуле формы. Давайте разберем на коде, что работает, а что нет.
Так не работает: прямой вызов из клиентского кода.
//=== Модуль формы ===
&НаКлиенте
Процедура Тест()
// Ошибка: Нельзя передать форму как параметр в серверный общий модуль
Сервер.Обработка(ЭтаФорма);
КонецПроцедуры
//=== Общий модуль Клиент ===
Процедура Обработка(Форма) Экспорт
// Ошибка: та же самая проблема
Сервер.Обработка(ЭтаФорма);
КонецПроцедуры
//=== Общий модуль Сервер ===
Процедура Обработка(Форма) Экспорт
//...
КонецПроцедуры
А вот так работает: через серверную процедуру-посредник в форме.
//=== Модуль формы ===
&НаКлиенте
Процедура Тест()
// 1. Вызываем серверный метод ВНУТРИ формы
ТестНаСервере();
КонецПроцедуры
&НаСервере
Процедура ТестНаСервере()
// 2. Из серверного кода формы передаем форму в общий модуль. Это уже разрешено.
Сервер.Обработка(ЭтаФорма);
КонецПроцедуры
//=== Общий модуль Сервер ===
Процедура Обработка(Форма) Экспорт
// Тут желаемая обработка формы
КонецПроцедуры
Этот способ является легитимным и предусмотрен архитектурой платформы. Вызов ТестНаСервере() инициирует штатный контекстный вызов, после которого на сервере мы имеем актуальный объект формы и можем передавать его в другие серверные модули.
Сама необходимость передавать всю форму в общий модуль часто является признаком не самой удачной архитектуры. Правильный подход — разделение бизнес-логики и логики интерфейса.
Если серверной процедуре не нужно знать обо всех элементах и реквизитах формы, а нужны только конкретные данные для выполнения расчетов или операций с базой данных, используйте вызовы &НаСервереБезКонтекста. В этом случае вы явно передаете только необходимые данные, что значительно быстрее и эффективнее.
Пример: нужно сформировать отчет в табличный документ на форме.
// === Модуль формы ===
&НаКлиенте
Процедура СформироватьОтчетПоЦенам()
ПараметрыЗапроса = Новый Структура;
// ... заполняем параметры ...
// Передаем только табличный документ и параметры, а не всю форму
СформироватьОтчетПоЦенамСервер(ТаблицаЦеныНоменклатуры, ПараметрыЗапроса);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура СформироватьОтчетПоЦенамСервер(ТаблицаОтчета, ПараметрыЗапроса)
// Вызываем общий модуль, который ничего не знает о форме
МегаПомощникПродажСервер.СформироватьОтчетПоЦенам(ТаблицаОтчета, ПараметрыЗапроса);
КонецПроцедуры
Бизнес-логика (в модуле МегаПомощникПродажСервер) не должна зависеть от интерфейса. Это позволяет повторно использовать ее в регламентных заданиях, других формах или внешних обработках.
Иногда нужно вынести в общий модуль именно логику работы с элементами формы, например, если у вас есть несколько похожих форм, где нужно одинаково настроить условное оформление или добавить элементы. В этом случае используется шаблон с процедурой-посредником.
Задача: в двух разных формах нужно программно добавить декорацию.
// === Модуль Формы 1 и Формы 2 ===
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Вызываем общий код для настройки элементов
ОбщийДляФормСервер.ДобавитьДекорацию(Элементы);
КонецПроцедуры
// === Общий модуль "ОбщийДляФормСервер" ===
Процедура ДобавитьДекорацию(Элементы) Экспорт
Декорация = Элементы.Добавить("Декорация1", Тип("ДекорацияФормы"));
Декорация.Заголовок = "Текст декорации...";
КонецПроцедуры
Обратите внимание, что здесь мы передаем не всю форму (ЭтаФорма), а только коллекцию ее элементов (Элементы). Это более правильный подход, так как общий модуль получает только то, что ему действительно нужно для работы.
В заключение: ограничение на передачу формы с клиента на сервер — это не ошибка и не недоработка, а осознанное архитектурное решение фирмы "1С", направленное на обеспечение производительности и стабильности приложений. Понимание механизма контекстных вызовов и использование правильных шаблонов проектирования (процедуры-посредники, бесконтекстные вызовы, разделение логики) позволяет писать чистый, поддерживаемый и эффективный код, не страдая от этого ограничения.