При разработке расширений в системе «1С:Предприятие» программисты часто сталкиваются с необходимостью кастомизации типовых механизмов. Типичная задача — дополнить новыми строками или колонками таблицу значений, которая формируется внутри функции общего модуля. Однако при попытке напрямую обратиться к локальной переменной заимствованной функции возникает ошибка компиляции: «Переменная не определена».
В этой статье мы подробно разберем, почему возникает эта ошибка, как работает область видимости переменных в расширениях и какими способами можно решить эту задачу без полного дублирования исходного кода конфигурации.
Для начала проанализируем ситуацию с точки зрения архитектуры платформы «1С:Предприятие». Локальные переменные (объявленные внутри процедуры или функции явно с помощью ключевого слова Перем или созданные неявно в процессе присвоения значений) существуют исключительно в стеке вызова конкретного метода. Как только выполнение метода завершается, эти переменные уничтожаются, а выделенная под них память освобождается.
Когда мы пишем метод расширения с аннотацией &Вместо, платформа создает фактически отдельный метод, который замещает собой оригинальный. Даже если мы вызываем оригинальный код с помощью конструкции ПродолжитьВызов(), этот вызов происходит в изолированном контексте. Мы получаем только результат работы функции, но не имеем прямого доступа к ее внутренним «кухонным» процессам и локальным переменным.
Рассмотрим простую и наглядную аналогию, которая поможет закрепить этот принцип. Представим себе две стандартные процедуры в обычном модуле:
Функция ИнициализироватьТЗ()
Результат = Новый ТаблицаЗначений;
Результат.Колонки.Добавить("ПерваяКолонка");
Возврат Результат;
КонецФункции
Процедура МояПроцедура()
МояТЗ = ИнициализироватьТЗ();
// Как мы будем обращаться к таблице значений здесь?
КонецПроцедуры
Очевидно, что внутри метода МояПроцедура() мы не можем написать Результат.Добавить(), так как переменная Результат была локальной для функции ИнициализироватьТЗ(). Мы обязаны обращаться к объекту через переменную МояТЗ, в которую был записан возвращаемый результат. Тот же самый принцип работает и при написании методов в расширении конфигурации.
Если вы работаете на платформе «1С:Предприятие» версии ниже 8.3.15, то единственным верным и безопасным способом изменения возвращаемого значения без дублирования кода является перехват результата выполнения оригинального метода.
Поскольку ТаблицаЗначений относится к ссылочным типам данных, при передаче ее в качестве возвращаемого значения передается ссылка на объект в памяти. Это означает, что мы можем получить эту таблицу, дополнить ее строками или колонками, и эти изменения сохранятся в самом объекте. Дублировать исходный код функции при этом не требуется.
Разберем по шагам, как должен выглядеть правильный код в модуле расширения:
&Вместо("ПолучитьТаблицуВидовАктивовИОбязательств")
Функция Расш_ПолучитьТаблицуВидовАктивовИОбязательств()
// Шаг 1. Вызываем оригинальную функцию и получаем готовую таблицу значений
ТаблицаВидовАктивовИОбязательств = ПродолжитьВызов();
// Шаг 2. Модифицируем полученную таблицу (добавляем новую строку)
НоваяСтрока = ТаблицаВидовАктивовИОбязательств.Добавить();
НоваяСтрока.ВидАктиваОбязательства = ПредопределенноеЗначение("Перечисление.ВидыАктивовИОбязательств.НашНовыйВид");
// Шаг 3. Возвращаем измененную таблицу в вызывающий алгоритм
Возврат ТаблицаВидовАктивовИОбязательств;
КонецФункции
Этот способ крайне элегантен, так как оригинальный код функции полностью выполняется на стороне основной конфигурации. Если при очередном обновлении типовой конфигурации логика заполнения таблицы изменится, ваше расширение продолжит корректно работать, просто добавляя строку в конец уже обновленной таблицы.
В процессе обсуждения подобных задач у начинающих разработчиков часто возникает вопрос: «А почему бы не использовать аннотацию &После, ведь она должна выполняться после основного кода?».
Здесь действует жесткое ограничение самой платформы 1С:
&Перед и &После могут быть применены только к процедурам.&Вместо) с явным использованием вызова ПродолжитьВызов().Начиная с версии платформы 8.3.15, у разработчиков появился мощный инструмент — аннотация &ИзменениеИКонтроль (в интерфейсе конфигуратора она доступна при выборе действия «Вызывать вместо (с контролем)»). Этот механизм идеально подходит для случаев, когда вам действительно необходимо вмешаться «внутрь» алгоритма и задействовать локальные переменные оригинального метода.
При использовании этого подхода платформа заимствует весь текст оригинальной функции, но позволяет модифицировать его точечно с помощью специальных инструкций препроцессора расширений: #Вставить и #Удалить. При этом у вас сохраняется полный доступ ко всем локальным переменным оригинального метода.
Посмотрим на пример реализации нашей задачи с использованием контроля изменений:
&ИзменениеИКонтроль("ПолучитьТаблицуВидовАктивовИОбязательств")
Функция Расш_ПолучитьТаблицуВидовАктивовИОбязательств()
// Здесь располагается заимствованный код оригинальной функции
ТаблицаВидовАктивовИОбязательств = Новый ТаблицаЗначений;
ТаблицаВидовАктивовИОбязательств.Колонки.Добавить("ВидАктиваОбязательства");
// Заполнение стандартных строк...
#Вставить
// Мы находимся внутри контекста оригинального метода!
// Локальная переменная ТаблицаВидовАктивовИОбязательств здесь полностью доступна напрямую
НоваяСтрока = ТаблицаВидовАктивовИОбязательств.Добавить();
НоваяСтрока.ВидАктиваОбязательства = ПредопределенноеЗначение("Перечисление.ВидыАктивовИОбязательств.НашНовыйВид");
#КонецВставки
Возврат ТаблицаВидовАктивовИОбязательств;
КонецФункции
Преимущества использования &ИзменениеИКонтроль:
При работе с заимствованными функциями в расширениях 1С всегда анализируйте версию платформы и сложность вносимых изменений:
&Вместо, получайте результат через ПродолжитьВызов(), модифицируйте его и возвращайте обратно. Это избавит вас от необходимости копировать тонны исходного кода.&ИзменениеИКонтроль. Это наиболее современный, безопасный и поддерживаемый стандарт разработки в системе «1С:Предприятие».