Как программно определить, какой документ или объект вызвал процедуру общего модуля?

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

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

Способ 1. Использование встроенной функции ПолучитьСтекВызовов() (для платформы 8.3.21+)

Если ваша конфигурация работает на платформе версии 8.3.21 и выше, то задача решается максимально элегантно. Разработчики 1С добавили штатный метод, который позволяет получить всю цепочку вызовов в виде массива объектов. Проанализируем, как это работает на практике.

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


Стек = ПолучитьСтекВызовов();
// Индекс 0 — это текущая процедура
// Индекс 1 — процедура, которая вызвала текущую
Если Стек.Количество() > 1 Тогда
    ВызывающийОбъект = Стек[1];
    Сообщить("Вызов пришел из модуля: " + ВызывающийОбъект.Модуль);
    Сообщить("Имя вызывающей процедуры: " + ВызывающийОбъект.ИмяПроцедуры);
    Сообщить("Номер строки: " + ВызывающийОбъект.НомерСтроки);
КонецЕсли;

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

Способ 2. Явная передача контекста (архитектурный подход)

Согласно стандартам разработки фирмы «1С» и лучшим практикам БСП (Библиотеки стандартных подсистем), программный анализ стека для принятия бизнес-решений считается «плохим тоном». Правильнее всего — явно передать информацию об источнике в параметрах процедуры. Рассмотрим ситуацию: у нас есть общий модуль РаботаСДокументами и процедура РассчитатьСкидку.

Вместо того чтобы пытаться угадать источник, добавим параметр Источник:


Процедура РассчитатьСкидку(Знач Источник, ДругиеПараметры) Экспорт
    Если ТипЗнч(Источник) = Тип("ДокументОбъект.РеализацияТоваровУслуг") Тогда
        // Логика специально для реализации
    ИначеЕсли ТипЗнч(Источник) = Тип("ДокументОбъект.ЗаказПокупателя") Тогда
        // Логика для заказа
    КонецЕсли;
КонецПроцедуры

При вызове из модуля документа мы просто передаем ЭтотОбъект:

РаботаСДокументами.РассчитатьСкидку(ЭтотОбъект, ПараметрыСкидки);

Преимущества: код становится прозрачным, легко отлаживается и не зависит от версии платформы. Недостатки: если процедура вызывается из сотен мест, придется изменить сигнатуру вызова во всех точках.

Способ 3. Получение стека через генерацию исключения (для старых версий)

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


ТекстСтека = "";
Попытка
    ВызватьИсключение "ПолучениеСтека";
Исключение
    ТекстСтека = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;

// Теперь анализируем строку ТекстСтека
// В ней будет содержаться перечень модулей и номеров строк
Если Найти(ТекстСтека, "Документ.РеализацияТоваровУслуг.МодульОбъекта") > 0 Тогда
    // Вызов из реализации
КонецЕсли;

Внимание! Выясним причину, почему этот метод опасен:

  1. Производительность: Генерация исключения — крайне медленная операция для процессора. В циклах такой код использовать нельзя.
  2. Локализация: Формат строки стека может измениться при обновлении платформы или при смене региональных настроек (язык сообщений).

Способ 4. Использование ДополнительныеСвойства (при проведении)

Проанализируем ситуацию, когда вызов происходит глубоко внутри механизмов проведения документа. В таких случаях удобно использовать коллекцию ДополнительныеСвойства, которая есть у любого документа. Перед началом проведения мы можем записать туда признак инициатора, и он будет доступен во всех процедурах общего модуля, куда передается ссылка на объект или сам объект.

В модуле документа:


ЭтотОбъект.ДополнительныеСвойства.Вставить("ИнициаторВызова", "СпециальнаяОбработка");

В общем модуле:


Если Источник.ДополнительныеСвойства.Свойство("ИнициаторВызова") Тогда
    Если Источник.ДополнительныеСвойства.ИнициаторВызова = "СпециальнаяОбработка" Тогда
        // Особая логика
    КонецЕсли;
КонецЕсли;

Подведем итоги

Выбор метода зависит от ваших условий:

Помните, что чем более явным будет ваш код, тем проще его будет поддерживать вашим коллегам в будущем.

← На главную