Зачем нужен вызов общего модуля по ссылке через ОбщегоНазначения.ОбщийМодуль() в 1С?

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

При изучении типовых конфигураций (например, УТ 11, ERP 2, КА 2 или БП 3) разработчики часто сталкиваются с конструкциями динамического вызова общих модулей. Вместо привычного прямого обращения к методу модуля, код выглядит следующим образом:


МодульВерсионированиеОбъектов = ОбщегоНазначения.ОбщийМодуль("ВерсионированиеОбъектов");
Используется = МодульВерсионированиеОбъектов.ЕстьПравоЧтенияИнформацииОВерсияхОбъектов();

На первый взгляд, это кажется избыточным усложнением кода. Зачем совершать лишнее действие и получать ссылку на модуль через функцию ОбщегоНазначения.ОбщийМодуль(), если можно сразу написать ВерсионированиеОбъектов.ЕстьПравоЧтенияИнформацииОВерсияхОбъектов()? Давайте подробно разберем, какую архитектурную задачу решает этот прием, какие ограничения он накладывает и как правильно с ним работать.

---

Причина №1: Обеспечение модульности и предотвращение ошибок компиляции (слабая связанность)

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

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

Динамическое получение модуля решает эту проблему за счет механизма «экранирования»:

  1. Имя модуля передается в функцию в виде обычной текстовой строки: "ВерсионированиеОбъектов". Для компилятора 1С это просто строка, он не проверяет физическое наличие такого общего модуля в метаданных на этапе синтаксического контроля.
  2. Перед вызовом методов непостоянной подсистемы разработчики сначала проверяют ее доступность в текущей конфигурации с помощью функции ОбщегоНазначения.ПодсистемаСуществует().
  3. Если подсистема существует, модуль безопасно извлекается и вызывается его метод. Если подсистемы нет — код обходит этот блок выполнения, не вызывая ошибок компиляции и исполнения.

---

Причина №2: Как устроена функция получения модуля изнутри?

Проанализируем примерную реализацию функции ОбщийМодуль() в модуле ОбщегоНазначения. Она позволяет динамически вычислять и возвращать контекст требуемого модуля:


Функция ОбщийМодуль(Имя) Экспорт
    Если Метаданные.ОбщиеМодули.Найти(Имя) <> Неопределено Тогда
        Модуль = Вычислить(Имя); 
    ИначеЕсли СтрЧислоВхождений(Имя, ".") = 1 Тогда
        Возврат СерверныйМодульМенеджера(Имя);
    Иначе
        Модуль = Неопределено;
    КонецЕсли;
    
    Если ТипЗнч(Модуль) <> Тип("ОбщийМодуль") Тогда
        ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
            НСтр("ru = 'Общий модуль ""%1"" не найден.'"), Имя);
    КонецЕсли;
    
    Возврат Модуль;
КонецФункции

Функция использует встроенный метод платформы Вычислить() для преобразования строки с именем модуля в реальный объект типа ОбщийМодуль. Если модуль физически отсутствует в конфигурации, функция вызовет понятное исключение, а не приведет к аварийному падению платформы с системной ошибкой.

---

Причина №3: Безопасность кода и прохождение аудита в «1С:Фреш»

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

Метод БСП ОбщегоНазначения.ОбщийМодуль() спроектирован так, чтобы гарантировать безопасность:

---

Решение проблемы с потерей навигации (F12) и подсказок в Конфигураторе

Главное неудобство динамического вызова для программиста — в Конфигураторе перестает работать переход к объявлению метода по клавише F12, а также пропадает контекстная подсказка (T9) для параметров функций.

Рассмотрим, какими способами можно вернуть удобство разработки.

Вариант 1. Использование обходных инструкций препроцессора (для классического Конфигуратора)

Для того чтобы «обмануть» парсер Конфигуратора и заставить его видеть тип переменной, можно использовать некомпилируемый блок препроцессора:


МодульВерсионированиеОбъектов = ОбщегоНазначения.ОбщийМодуль("ВерсионированиеОбъектов");
#Если Сервер И НЕ Сервер Тогда
    МодульВерсионированиеОбъектов = ВерсионированиеОбъектов;    
#КонецЕсли

Как это работает: Условие Сервер И НЕ Сервер логически никогда не выполнится, поэтому данный блок полностью исключается компилятором при исполнении кода на сервере. Однако синтаксический анализатор Конфигуратора считывает этот блок при построении дерева зависимостей. Для Конфигуратора переменная МодульВерсионированиеОбъектов становится жестко связанной с общим модулем ВерсионированиеОбъектов, благодаря чему снова начинает работать переход по F12 и контекстная подсказка параметров.

Вариант 2. Разработка в среде 1C:EDT (Enterprise Development Tools)

Если вы ведете разработку в современной среде 1C:EDT, то использовать хаки с препроцессором #Если Сервер И НЕ Сервер Тогда не рекомендуется и даже вредно по следующим причинам:

---

Критически важные нюансы использования: Производительность и передача контекста

Использование динамического вызова накладывает на разработчика серьезную ответственность. Разберем важные правила оптимизации кода.

Ограничение производительности при работе в циклах

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

Категорически запрещено вызывать получение общего модуля внутри циклов (например, при обработке строк табличной части документа или при обходе выборки результата запроса)!

Рассмотрим пример правильной оптимизации кода:


// НЕПРАВИЛЬНО: Приводит к падению производительности на больших таблицах
Для Каждого Строка Из ТаблицаТоваров Цикл
    // На каждой итерации заново ищется и вычисляется общий модуль!
    ОбщегоНазначения.ОбщийМодуль("РаботаСНоменклатурой").ЗаполнитьРеквизиты(Строка);
КонецЦикла;

// ПРАВИЛЬНО: Получаем ссылку один раз ДО начала цикла
Если ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.РаботаСНоменклатурой") Тогда
    МодульРаботаСНоменклатурой = ОбщегоНазначения.ОбщийМодуль("РаботаСНоменклатурой");
    
    Для Каждого Строка Из ТаблицаТоваров Цикл
        // Быстрое обращение к методу через готовую ссылку на модуль
        МодульРаботаСНоменклатурой.ЗаполнитьРеквизиты(Строка);
    КонецЦикла;
КонецЕсли;

Ограничение на передачу между Клиентом и Сервером

Полученный через функцию объект типа ОбщийМодуль не является сериализуемым. Это значит, что вы не можете передать переменную, содержащую ссылку на модуль, в качестве параметра при вызове серверного метода с клиента или вернуть её с сервера на клиент.

Попытка передать переменную типа ОбщийМодуль через границу «клиент-сервер» вызовет неустранимую ошибку выполнения: «Отсутствует отображение для типа 'ОбщийМодуль'».

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

---

Влияние на рефакторинг и поиск ссылок в конфигурации

При использовании динамического связывания стандартный глобальный поиск по конфигурации в Конфигураторе не сможет сопоставить вызовы методов, если имя модуля или метода передается строкой. Это может затруднить рефакторинг (например, при переименовании методов) и привести к появлению неиспользуемого «мертвого» кода.

Для минимизации этой проблемы при масштабной разработке рекомендуется использовать современные инструменты анализа кода (такие как 1C:EDT или внешние статические анализаторы), которые умеют строить полный граф зависимостей конфигурации, распознавая вызовы даже через функцию ОбщегоНазначения.ОбщийМодуль().

← На главную