Как правильно искать объекты, получать константы и перечисления в управляемых формах 1С:Предприятия?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При переходе от обычных форм к управляемым в 1С:Предприятии мы сталкиваемся с фундаментальными изменениями в архитектуре приложения. Если раньше платформа могла неявно выполнять многие операции на сервере, скрывая от нас детали клиент-серверного взаимодействия, то в управляемых формах этот процесс становится явным и требует нашего прямого участия. Это кажущееся усложнение на самом деле является значительным упрощением в разработке и поддержке, позволяя нам создавать более производительные, масштабируемые и кроссплатформенные решения.

Давайте подробно разберем, как нам следует подходить к поиску объектов, получению констант и перечислений, а также к организации клиент-серверного взаимодействия в целом, чтобы наш код был эффективным, поддерживаемым и соответствовал современным практикам.

Основная концепция: Строгое разделение клиента и сервера

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

  1. Клиентская часть (тонкий клиент, веб-клиент, мобильный клиент) отвечает исключительно за визуализацию данных и интерактивное взаимодействие с пользователем. Все, что связано с отображением элементов формы, реакцией на клики, вводом данных, выдачей предупреждений и получением ввода от пользователя — это его зона ответственности. Мы должны стремиться минимизировать объем кода на клиенте, так как клиентские устройства (особенно веб-клиенты) могут быть менее производительными, а интерпретатор встроенного языка в браузере работает медленнее.

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

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

Директивы компиляции: Где выполняется код?

Для управления тем, где будет выполняться наш код (на клиенте или на сервере), в 1С используются специальные директивы компиляции. Давайте рассмотрим их подробнее:

  1. &НаКлиенте: Эта директива указывает, что код, написанный после нее, будет выполняться на клиентской стороне. В этом контексте доступны элементы, параметры и реквизиты формы, а также вызовы серверных функций (через их экспортные методы).

  2. &НаСервере: Код с этой директивой выполняется на сервере. При вызове серверной процедуры или функции с &НаСервере, весь контекст формы (все реквизиты формы, включая ее табличные части, если они являются реквизитами) передается с клиента на сервер, а затем обратно. Это может быть весьма ресурсоемко, особенно для "тяжелых" форм с большим количеством данных, и негативно сказываться на производительности.

  3. &НаСервереБезКонтекста: Эта директива также указывает на выполнение кода на сервере, но с одним очень важным отличием: вызов происходит без передачи контекста формы. Это наиболее оптимальный вариант для большинства серверных вызовов, когда нам не нужны данные непосредственно из формы, а требуется просто выполнить какую-то операцию с базой данных или произвести расчет. Используйте этот подход по максимуму для общих функций, которые просто получают или обрабатывают данные, так как он значительно оптимизирует производительность, уменьшая объем передаваемого по сети трафика.

Как "правильно" искать объекты и получать данные?

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

Избегаем обращения через точку и считывания всего объекта

Как уже отмечалось, прямое обращение вида Контрагент.ИНН на клиенте невозможно. Однако даже на сервере такой подход (если Контрагент является ссылкой, а не уже прочитанным объектом) крайне нежелателен без явного считывания. При таком обращении платформа будет вынуждена неявно считать весь объект из базы данных, включая все его реквизиты, а также все табличные части. Это может быть очень неэффективно, особенно если вам нужен всего один или пара реквизитов из большого объекта.

Используем функции БСП для получения реквизитов по ссылке

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

  1. ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита): Эта функция позволяет получить значение одного конкретного реквизита объекта, передав его ссылку и строковое имя реквизита. Она оптимизирована для чтения минимального объема данных.

  2. ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, МассивИменРеквизитов): Если вам нужны значения нескольких реквизитов одного объекта, эта функция является предпочтительной. Мы передаем ей ссылку на объект и массив строковых имен требуемых реквизитов. В результате функция вернет структуру, ключами которой будут имена реквизитов, а значениями — их значения. Это позволяет получить все необходимые данные за один серверный вызов.

Если нам нужна эта функциональность на клиенте (например, чтобы отобразить ИНН контрагента при выборе его в поле ввода), мы обязательно создаем обертку в серверном общем модуле или модуле формы с директивой &НаСервереБезКонтекста и вызываем её из клиентского кода. Это обеспечивает минимальный трафик и максимальную производительность.

Рассмотрим пример получения ИНН контрагента:

Предположим, у нас есть общий модуль МоиФункцииСервер с установленным свойством "Вызов сервера", в котором мы можем размещать серверные функции без контекста.


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

А затем вызываем эту функцию из клиентской процедуры в модуле формы:


// В модуле формы
// &НаКлиенте
Процедура ПриКоммандеПолучитьИНН()
    // Предположим, РеквизитФормыКонтрагент - это реквизит формы типа Ссылка.Справочник.Контрагенты
    Если ЗначениеЗаполнено(РеквизитФормыКонтрагент) Тогда
        ИНН = МоиФункцииСервер.ПолучитьИННКонтрагентаНаСервереБезКонтекста(РеквизитФормыКонтрагент);
        Сообщить("ИНН контрагента: " + ИНН);
    Иначе
        Сообщить("Контрагент не выбран.");
    КонецЕсли;
КонецПроцедура

А вот как мы можем получить сразу несколько реквизитов за один серверный вызов:


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

Вызов с клиента будет выглядеть так:


// В модуле формы
// &НаКлиенте
Процедура ПриКоммандеПолучитьНесколькоРеквизитов()
    Если ЗначениеЗаполнено(РеквизитФормыКонтрагент) Тогда
        СтруктураРеквизитов = МоиФункцииСервер.ПолучитьОсновныеРеквизитыКонтрагентаНаСервереБезКонтекста(РеквизитФормыКонтрагент);
        Если СтруктураРеквизитов <> Неопределено Тогда
            Сообщить("ИНН: " + СтруктураРеквизитов.ИНН);
            Сообщить("Наименование полное: " + СтруктураРеквизитов.НаименованиеПолное);
            Сообщить("Телефон: " + СтруктураРеквизитов.Телефон);
        Иначе
            Сообщить("Не удалось получить реквизиты контрагента.");
        КонецЕсли;
    Иначе
        Сообщить("Контрагент не выбран.");
    КонецЕсли;
КонецПроцедуры

Использование запросов для сложного получения данных

Для более сложных сценариев, когда нам нужно получить данные из нескольких таблиц, выполнить агрегирование, применить сложные фильтры или объединить информацию из разных источников, мы используем **запросы к базе данных**. Запросы, как вы уже догадались, **всегда выполняются на сервере**. Результат запроса (как правило, ТаблицаЗначений, Структура или Массив структур) затем передается обратно на клиент.

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

Получение констант и перечислений

  1. Константы: Значения констант, как и другие данные из информационной базы, необходимо получать **на сервере**. Для этого используйте стандартный метод Константы.ИмяКонстанты.Получить(). Если вам нужно значение константы на клиенте (например, для настройки видимости элементов формы), оберните этот вызов в серверную функцию без контекста, аналогично примерам с реквизитами контрагента.

  2. Перечисления: Сами по себе перечисления (например, Перечисления.ВидыКонтактнойИнформации.Телефон) представляют собой фиксированный набор значений метаданных и доступны на клиенте напрямую. Вы можете использовать их для сравнений или передачи в параметры. Однако, если вы работаете со ссылками на перечисления, которые хранятся как реквизиты объектов данных (например, реквизит объекта типа "Перечисление"), то их значения будут доступны только после получения самого объекта или его реквизитов с сервера, как описано выше.

Оптимизация клиент-серверного взаимодействия: сокращаем "прыжки"

Одна из главных причин снижения производительности в управляемых формах — это чрезмерное количество вызовов сервера (так называемые "прыжки" или "roundtrips"), особенно если каждый такой вызов передает большой объем данных. Наша задача — **минимизировать количество таких вызовов и объем передаваемых данных**.

  1. Объединяйте действия: Вместо того чтобы выполнять три отдельных вызова сервера для получения трех разных частей информации, постарайтесь организовать один серверный вызов, который соберет все необходимые данные и вернет их в виде структуры или таблицы значений. Это значительно сократит задержки, связанные с передачей данных по сети.

  2. Используйте &НаСервереБезКонтекста: Мы уже обсудили эту директиву, но стоит еще раз подчеркнуть ее важность. Это самый эффективный способ взаимодействия с сервером, когда не требуется передача всей формы. Используйте его везде, где это возможно.

  3. Где выполнять вычисления?

    • Если вычисления легкие, не требуют доступа к базе данных или другим серверным ресурсам, и их результат нужен только для отображения на клиенте, их можно выполнять **на клиенте**. Это снизит нагрузку на сервер.
    • Если же вычисления тяжелые, ресурсоемкие, или их результат зависит от актуальных данных базы, то их место **на сервере**. Сервер, как правило, обладает большей вычислительной мощностью.
    При этом важно помнить, что, как отмечено в дискуссии, чрезмерное количество "прыжков" с клиента на сервер и обратно часто сильнее влияет на общее быстродействие приложения, чем сами вычисления. Всегда взвешивайте стоимость вызова сервера против стоимости локальных вычислений.

Современный подход: Асинхронные вызовы с "Обещаниями" (Async/Await)

В более поздних версиях платформы 1С (начиная с 8.3.18) был введен мощный механизм асинхронных вызовов, использующий конструкции Асинх и Ждать (аналогичные Async/Await в JavaScript). Этот механизм значительно упрощает написание асинхронного кода и повышает отзывчивость интерфейса, поскольку не блокирует основной поток выполнения клиента в ожидании ответа от сервера.

  1. Обещание (Promise): Это центральный тип, который представляет собой обертку для результата асинхронной операции, который может быть получен не сразу. Асинхронная функция всегда возвращает Обещание.

  2. Асинх (Async): Это условно-ключевое слово, используемое для объявления функции или процедуры как асинхронной. Код внутри такой функции может содержать оператор Ждать.

  3. Ждать (Await): Оператор, который используется внутри асинхронных процедур/функций для ожидания завершения Обещания. При этом выполнение текущей асинхронной функции приостанавливается, а управление возвращается в вызывающий код, что позволяет интерфейсу оставаться отзывчивым. Как только Обещание наполняется значением, выполнение асинхронной функции продолжается с того места, где она была приостановлена.

Этот подход приходит на смену устаревшему механизму ОписаниеОповещения, делая асинхронный код более читаемым, последовательным и менее подверженным ошибкам логики. Он особенно важен для операций, которые могут занимать длительное время, например, сложные запросы к базе данных, обмен данными с внешними системами или длительные вычисления, выполняемые на сервере. Использование Асинх/Ждать позволяет избежать зависаний интерфейса и улучшить пользовательский опыт. Для практической реализации длительных серверных процессов без блокировки формы подойдёт обработка с фоновым выполнением операций и индикацией прогресса.

Рассмотрим пример использования Асинх/Ждать для выполнения длительной операции:


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

А так мы вызываем эту асинхронную функцию из клиентской процедуры в модуле формы:


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

Общие рекомендации и изучение типовых решений

Чтобы эффективно работать с управляемыми формами, мы должны придерживаться следующих практических рекомендаций:

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

  2. Изучайте типовые конфигурации: Разработчики 1С предоставляют отличные примеры реализации клиент-серверного взаимодействия в своих типовых решениях. Обратите особое внимание на конфигурации Бухгалтерия предприятия 3.0 или Розница 2.x. Хотя они не всегда идеальны, они демонстрируют хорошие практики и концепции клиент-серверного взаимодействия, а также структуру кода, которой следует придерживаться.

  3. Минимизируйте код на клиенте: Чем меньше логики и вычислений выполняется на клиенте, тем быстрее и стабильнее будет работать приложение, особенно в веб-клиенте. Ограничьте клиентский код только задачами визуализации и интеракции.

  4. Разделяйте код на мелкие функции: Избегайте "стиля акына" (огромных процедур на тысячи строк, в которых намешаны все операции). Выделяйте логику в отдельные, небольшие, понятные и тестируемые функции и процедуры. Это значительно упрощает отладку, сопровождение и модификацию кода.

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

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

← На главную