Многие разработчики 1С задаются справедливым вопросом: почему при использовании объекта ОписаниеОповещения платформа жестко требует указывать модификатор Экспорт для процедуры-обработчика? На первый взгляд кажется, что это нарушает принципы инкапсуляции, заставляя «вытаскивать наружу» внутренние механизмы и скрытую логику клиентских модулей или форм. Разберем по шагам, почему платформа ведет себя именно так, и посмотрим, как современные версии 1С позволяют обойти это архитектурное ограничение.
Проанализируем ситуацию с точки зрения архитектуры ядра платформы. Встроенный язык 1С не поддерживает классические указатели на функции, которые есть в C++ или других языках низкого уровня. Связь между асинхронным событием и обработчиком происходит через передачу обычного строкового имени метода в конструктор объекта ОписаниеОповещения.
Когда асинхронная операция завершается (например, пользователь закрыл диалоговое окно или завершился вызов к серверу), обратный вызов (callback) инициируется непосредственно из ядра платформы. Более того, при работе через веб-клиент сигнал о завершении вообще возвращается со стороны браузера. В этот момент ядро помещает задание в очередь потока встроенного языка и пытается выполнить код метода.
Так как этот вызов происходит «снаружи» контекста вашего модуля (инициатором выступает ядро, а не соседняя процедура вашего кода), система подчиняется строгим правилам инкапсуляции. Чтобы получить доступ к процедуре из внешнего контекста, она должна быть публичной. Именно поэтому ядру требуется модификатор Экспорт. Если его не указать, система просто не увидит скрытую процедуру и выдаст исключение: «Не найден экспортируемый метод».
Если вы разрабатываете конфигурацию в режиме совместимости с версиями платформы ниже 8.3.18, обойти это требование полностью не получится. Однако мы можем минимизировать нарушение инкапсуляции и не делать все обработчики экспортными.
Рассмотрим классический подход с созданием одной универсальной экспортной процедуры-маршрутизатора. Вы создаете всего один экспортный метод в модуле, который выступает "входной дверью" для оповещений платформы, а уже из него вызываете любые скрытые методы. Посмотрим на пример:
&НаКлиенте
Процедура ИнициироватьДействие()
Оповещение = Новый ОписаниеОповещения("ЕдиныйОбработчикОповещений", ЭтотОбъект, "Действие_Удаление");
ПоказатьВопрос(Оповещение, "Продолжить операцию?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
// Единственный экспортный метод для связи с ядром платформы
&НаКлиенте
Процедура ЕдиныйОбработчикОповещений(Результат, ДополнительныеПараметры) Экспорт
Если ДополнительныеПараметры = "Действие_Удаление" Тогда
СкрытаяВнутренняяЛогика(Результат);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура СкрытаяВнутренняяЛогика(Результат)
// Внутренняя логика, полностью недоступная извне модуля
Если Результат = КодВозвратаДиалога.Да Тогда
Сообщить("Удаление выполнено");
КонецЕсли;
КонецПроцедуры
Выясним интересную деталь, которую разработчики обнаружили опытным путем. На некоторых версиях платформы (в частности, релизах 8.3.12–8.3.15) можно было заметить следующее: если передать в ОписаниеОповещения имя серверной процедуры, она могла успешно отработать и без указания модификатора Экспорт.
Однако важно понимать, что это является недокументированной особенностью работы серверного контекста и, скорее всего, недочетом проверок платформы. Для клиентских процедур, где и выполняется абсолютное большинство интерактивных оповещений, Экспорт требуется жестко и безальтернативно. Опираться на серверную "лазейку" не рекомендуется, так как в любой момент поведение может измениться.
Боль, связанная с необходимостью размазывать логику по экспортным процедурам-обработчикам, к счастью, осталась в прошлом. Начиная с версии 1С:Предприятие 8.3.18, в платформе появился современный механизм асинхронного программирования, аналогичный концепции Promise/Async/Await из современных языков программирования (например, JavaScript или C#).
Теперь вам вообще не нужно использовать ОписаниеОповещения и создавать экспортные методы. Достаточно добавить ключевое слово Асинх к вашей процедуре и использовать оператор Ждать перед асинхронными функциями. Платформа ввела новые функции с постфиксом "Асинх", такие как ВопросАсинх или ПредупреждениеАсинх.
Рассмотрим подробнее, как лаконично выглядит современный код без потери инкапсуляции:
&НаКлиенте
Асинх Процедура ВыполнитьДействиеСовременно()
// Выполнение кода логически приостановится на этой строке до получения ответа
Результат = Ждать ВопросАсинх("Продолжить операцию?", РежимДиалогаВопрос.ДаНет);
// Как только пользователь нажмет кнопку, код продолжит выполнение
Если Результат = КодВозвратаДиалога.Да Тогда
// Вызываем скрытые методы формы/модуля без всяких Экспортов
СкрытаяВнутренняяЛогика();
КонецЕсли;
КонецПроцедуры
Проанализируем преимущества такого подхода: код пишется визуально линейно внутри одной процедуры. Компилятор сам "под капотом" разбивает его на нужные фрагменты, оперируя специальным типом данных Обещание, и скрывает от разработчика всю сложность низкоуровневых коллбэков. И самое важное — никакие промежуточные методы не нужно делать экспортными, сохраняя идеальную структуру и безопасность вашего программного интерфейса.