При разработке в среде «1С:Предприятие 8.3» программисты часто сталкиваются с ошибкой компиляции вида «Переменная не определена (ПланыВидовХарактеристик)» (или аналогичной для справочников, документов и других менеджеров объектов) при попытке обратиться к метаданным из общего модуля. Давайте подробно разберем, почему возникает эта ошибка, как устроена контекстная компиляция в 1С, и как правильно решать подобные задачи с использованием современных стандартов разработки и Библиотеки стандартных подсистем (БСП).
Первым делом проанализируем, почему платформа выдает ошибку на стандартную строку кода, которая отлично работает в модуле менеджера или модуле объекта. Причина кроется в свойствах общего модуля, в котором пишется код. Если в свойствах общего модуля одновременно установлены флаги «Клиент (управляемое приложение)» и «Сервер», компилятор 1С пытается проверить синтаксис модуля для обоих контекстов.
На тонком клиенте (в режиме управляемого приложения) глобальный контекст сильно ограничен из-за соображений безопасности и производительности. Платформа не позволяет напрямую обращаться к менеджерам баз данных (таким как ПланыВидовХарактеристик, Справочники, Документы, РегистрыСведений и т.д.) с клиента. Клиентская часть просто «не знает» об их существовании, что и приводит к ошибке «Переменная не определена» на этапе компиляции клиентской части общего модуля.
Важно помнить, что директивы компиляции вида &НаСервере или &НаКлиенте в общих модулях не используются — они предназначены исключительно для модулей форм и команд. В общих модулях управление контекстом выполняется с помощью инструкций препроцессора.
Если общий модуль обязательно должен быть доступен и на клиенте, и на сервере, нам необходимо явно указать компилятору, какие участки кода должны исполняться только на сервере. Для этого мы оборачиваем наше обращение к серверным объектам в специальные инструкции препроцессора.
Давайте рассмотрим, как должен выглядеть код в этом случае:
#Если Сервер Или ТолстыйКлиентОбычноеПриложение Тогда
Попытка
КальяннаяСмесь = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоРеквизиту("Имя", "КальяннаяСмесь");
Если Не КальяннаяСмесь.Пустая() Тогда
НайденнаяСтрока = СтрокаТЧ.Номенклатура.ДополнительныеРеквизиты.Найти(КальяннаяСмесь, "Свойство");
Если НайденнаяСтрока <> Неопределено Тогда
Если НайденнаяСтрока.Значение = Истина Тогда
СтрокаПозицииЧека.Наименование = "Кальянная смесь";
КонецЕсли;
КонецЕсли;
КонецЕсли;
Исключение
// Обработка исключения при необходимости
КонецПопытки;
#КонецЕсли
При такой конструкции компилятор пропустит данный кусок кода при компиляции под тонкий клиент, и ошибка исчезнет. Однако прямое обращение к метаданным через НайтиПоРеквизиту имеет ряд серьезных недостатков, о которых мы поговорим далее.
В современных конфигурациях, таких как «Управление торговлей 11.4», «ERP Управление предприятием» или «Комплексная автоматизация», используется Библиотека стандартных подсистем (БСП). Разработчики БСП уже предусмотрели готовые и оптимизированные механизмы для работы со свойствами и дополнительными реквизитами.
Прямой поиск элемента плана видов характеристик по внутреннему имени (реквизиту Имя) считается плохим тоном. Имя дополнительного реквизита, созданного пользователем в пользовательском режиме, генерируется автоматически (например, Номенклатура_Общие_Цвет) и может не совпасть с жестко прописанной строкой в коде. Кроме того, поиск по реквизиту чувствителен к регистру букв.
Для безопасного и быстрого получения значений дополнительных реквизитов рекомендуется использовать экспортные функции общего модуля УправлениеСвойствами. Посмотрим на практический пример использования БСП:
#Если Сервер Или ТолстыйКлиентОбычноеПриложение Тогда
// Используем кэшированные и оптимизированные механизмы БСП
Если УправлениеСвойствами.ЗначениеСвойства(СтрокаТЧ.Номенклатура, "КальяннаяСмесь") = Истина Тогда
СтрокаПозицииЧека.Наименование = "Кальянная смесь";
КонецЕсли;
#КонецЕсли
Функция УправлениеСвойствами.ЗначениеСвойства самостоятельно найдет нужное свойство, определит его значение для конкретного объекта номенклатуры и вернет результат. Это избавляет разработчика от необходимости писать громоздкие конструкции с проверками на пустые ссылки и Неопределено.
Если код, представленный выше, выполняется внутри цикла (например, при обходе строк табличной части документа продаж или чека ККМ), то использование методов НайтиПоРеквизиту или даже одиночных вызовов функций БСП приведет к катастрофическому падению производительности. Это классическая проблема «N+1 запрос к базе данных».
Проанализируем ситуацию: если в чеке 50 строк, система выполнит 50 отдельных микрозапросов к СУБД для поиска свойства по каждой номенклатуре. Правильным и профессиональным решением будет получение всех необходимых данных одним запросом перед началом цикла.
Рассмотрим пример комплексного решения этой задачи:
// 1. Собираем массив всей номенклатуры из табличной части для пакетной обработки
МассивНоменклатуры = Новый Массив;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
МассивНоменклатуры.Добавить(СтрокаТЧ.Номенклатура);
КонецЦикла;
// Исключаем дубликаты для оптимизации запроса
ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивНоменклатуры);
// 2. Одним запросом выбираем значения нужного доп. реквизита для всего массива номенклатуры
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| НоменклатураДополнительныеРеквизиты.Ссылка КАК Номенклатура,
| НоменклатураДополнительныеРеквизиты.Значение КАК Значение
|ИЗ
| Справочник.Номенклатура.ДополнительныеРеквизиты КАК НоменклатураДополнительныеРеквизиты
|ГДЕ
| НоменклатураДополнительныеРеквизиты.Ссылка В(&МассивНоменклатуры)
| И НоменклатураДополнительныеРеквизиты.Свойство.Имя = &ИмяСвойства";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
// Задаем имя свойства, настроенное в БСП
Запрос.УстановитьПараметр("ИмяСвойства", "КальяннаяСмесь_e7b9bc90");
Выборка = Запрос.Выполнить().Выбрать();
// 3. Заполняем соответствие для быстрого поиска в оперативной памяти
СоответствиеСвойств = Новый Соответствие;
Пока Выборка.Следующий() Цикл
СоответствиеСвойств.Вставить(Выборка.Номенклатура, Выборка.Значение);
КонецЦикла;
// 4. В цикле быстро сопоставляем данные без обращений к базе данных
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
ЗначениеРеквизита = СоответствиеСвойств.Получить(СтрокаТЧ.Номенклатура);
Если ЗначениеРеквизита = Истина Тогда
// Здесь выполняем логику изменения наименования в позиции чека
// СтрокаПозицииЧека.Наименование = "Кальянная смесь";
КонецЕсли;
КонецЦикла;
Такой подход гарантирует минимальную нагрузку на сервер СУБД, повышает отказоустойчивость системы и обеспечивает мгновенный отклик интерфейса при оформлении документов продаж.
Для успешного написания чистого и быстрого кода в общих модулях «1С:Предприятия» придерживайтесь следующих правил:
#Если Сервер Тогда.НайтиПоРеквизиту. По возможности используйте предопределенные элементы.УправлениеСвойствами) — это экономит время разработки и гарантирует совместимость при обновлениях конфигурации.Соответствие и только потом обрабатывайте коллекцию.