Почему возникает ошибка «Переменная не определена (глСписокПрефиксовУзлов)» и как исправить контекстные ошибки компиляции модуля объекта в 1С?

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

При работе в клиент-серверном режиме «1С:Предприятия» разработчики часто сталкиваются со специфическими ошибками компиляции кода. Особенно остро эта проблема проявляется в крупных исторических конфигурациях, таких как «Управление производственным предприятием» (УПП), «Управление торговлей 10.3» или «Комплексная автоматизация 1.1», работающих в режиме обычного приложения. Типичный сценарий: при проведении документа на сервере внезапно начинают сыпаться ошибки о неопределенных глобальных переменных или отсутствующих функциях в совершенно других, казалось бы, типовых и не изменяемых в данный момент объектах.

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

---

Анализ исходной проблемы и симптомов

Рассмотрим практическую ситуацию. При проведении самописного документа в транзакции программно создается типовой документ ПеремещениеТоваров. Процедура создания и заполнения этого документа вынесена в общий привилегированный модуль, который выполняется на сервере без контекста клиента. Во время выполнения кода платформа аварийно завершает работу и выдает в системный журнал следующие сообщения об ошибках:


{Документ.ПеремещениеТоваров.МодульОбъекта(2681,82)}: Переменная не определена (глСписокПрефиксовУзлов)
    Шапка.Параметры.НомерДокумента = ОбщегоНазначения.ПолучитьНомерНаПечать(Ссылка, <<?>>глСписокПрефиксовУзлов);
{Документ.ПеремещениеТоваров.МодульОбъекта(3079,77)}: Переменная не определена (глСписокПрефиксовУзлов)
    Шапка.Параметры.Номер = ОбщегоНазначения.ПолучитьНомерНаПечать(ЭтотОбъект, <<?>>глСписокПрефиксовУзлов); 
{Документ.ПеремещениеТоваров.МодульОбъекта(2698,29)}: Процедура или функция с указанным именем не определена (ПолучитьТаблицуСтоимостиТоваров)
        ТаблицаСтоимости        = <<?>>ПолучитьТаблицуСтоимостиТоваров(Ссылка);            
{Документ.ПеремещениеТоваров.МодульОбъекта(2728,13)}: Процедура или функция с указанным именем не определена (ПолучитьСтоимостьТовара)
                Сумма = <<?>>ПолучитьСтоимостьТовара(ТаблицаСтоимости,

У разработчика закономерно возникают вопросы: почему сервер ругается на процедуры, которые не вызываются в процессе проведения документа, и почему аналогичный механизм создания документа ОприходованиеТоваров отрабатывает без каких-либо замечаний? Разберем эти вопросы по порядку.

---

Причина №1: Особенности компиляции модуля объекта платформой «1С:Предприятие»

Давайте выясним, почему вообще компилятор 1С обращает внимание на процедуры печати, если на сервере выполняется только создание и запись объекта без какого-либо намека на его вывод на экран.

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

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

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

---

Причина №2: Клиентский контекст глобальной переменной `глСписокПрефиксовУзлов`

Что представляет собой переменная глСписокПрефиксовУзлов? Это глобальная переменная обычного приложения, которая объявляется в Модуле обычного приложения с ключевым словом Экспорт:


// В Модуле обычного приложения
Перем глСписокПрефиксовУзлов Экспорт;

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

Ключевой конфликт: модуль обычного приложения компилируется и существует исключительно в клиентском контексте. Сервер базы данных ничего не знает о существовании этого модуля и его переменных. Когда серверный компилятор пытается распарсить строку кода, содержащую обращение к глСписокПрефиксовУзлов, он не находит эту переменную в области видимости сервера и выбрасывает ошибку Переменная не определена.

---

Причина №3: Смешивание контекстов при использовании директив препроцессора

Теперь проанализируем, почему сервер «потерял» процедуры ПолучитьТаблицуСтоимостиТоваров и ПолучитьСтоимостьТовара.

В типовых конфигурациях эти вспомогательные функции используются для расчета стоимостных показателей в печатных формах и изначально обернуты в директиву препроцессора:


#Если Клиент Тогда
Функция ПолучитьТаблицуСтоимостиТоваров(Ссылка)
    // Реализация функции расчета стоимости
КонецФункции
#КонецЕсли

Это означает, что для сервера данных процедур физически не существует. Когда сторонний разработчик решает добавить свою собственную, нетиповую печатную форму непосредственно в модуль объекта, он совершает критическую ошибку — размещает код своей новой экспортной процедуры вне блока #Если Клиент Тогда.

Пример ошибочного размещения кода:


// ОШИБКА: Код находится вне директив препроцессора и компилируется на сервере!
Процедура ПечатьМоейКастомнойФормы() Экспорт
    // Сервер пытается скомпилировать эту строку и падает, 
    // так как ПолучитьТаблицуСтоимостиТоваров определена только на клиенте!
    ТаблицаСтоимости = ПолучитьТаблицуСтоимостиТоваров(Ссылка); 
КонецПроцедуры

Поскольку сервер пытается скомпилировать процедуру ПечатьМоейКастомнойФормы, он видит внутри нее вызовы функций ПолучитьТаблицуСтоимостиТоваров и ПолучитьСтоимостьТовара. Но так как эти функции скрыты от сервера директивой #Если Клиент Тогда, компилятор выдает ошибку: Процедура или функция с указанным именем не определена.

---

Пошаговое руководство по устранению ошибок

Рассмотрим, как правильно исправить данную проблему, восстановить работоспособность проведения документов на сервере и не нарушить логику печати.

Шаг 1. Изоляция клиентского кода директивами препроцессора

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

Откроем модуль объекта Документ.ПеремещениеТоваров, найдем добавленные нетиповые процедуры печати и обернем их в блок:


#Если Клиент Тогда

Процедура ПечатьМоейКастомнойФормы() Экспорт
    // Безопасный клиентский контекст
    ТаблицаСтоимости = ПолучитьТаблицуСтоимостиТоваров(Ссылка);
    Сумма = ПолучитьСтоимостьТовара(ТаблицаСтоимости, ...);
    
    // Использование глобальной клиентской переменной здесь абсолютно легально
    Шапка.Параметры.Номер = ОбщегоНазначения.ПолучитьНомерНаПечать(ЭтотОбъект, глСписокПрефиксовУзлов);
КонецПроцедуры

#КонецЕсли

После этого сервер при компиляции модуля объекта просто пропустит этот блок кода, и ошибки компиляции исчезнут.

Шаг 2. Безопасный вызов функции «ПолучитьНомерНаПечать»

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

Если вам действительно необходимо вызвать эту функцию в коде, который должен одинаково успешно работать и на сервере, и на клиенте, используйте безопасный вызов без передачи глобальной переменной:


// Если префикс не передан, функция попытается обработать номер по умолчанию
НомерДляПечати = ОбщегоНазначения.ПолучитьНомерНаПечать(Ссылка);

Если же префиксы критически важны для формирования номера на сервере, необходимо отказаться от использования глобальной переменной глСписокПрефиксовУзлов в пользу получения данных через запросы к планам обмена информационной базы.

---

Лучшие практики разработки для предотвращения подобных ошибок

Чтобы в будущем не сталкиваться с подобными «сюрпризами» при обновлении конфигурации или написании нового функционала, мы рекомендуем придерживаться следующих правил архитектуры приложений 1С:

  1. Минимизируйте изменения в модулях объектов типовых документов. Модуль объекта — это критически важное место, которое компилируется при любых транзакционных действиях с документами. Любая синтаксическая ошибка здесь парализует работу пользователей всей системы.
  2. Используйте механизм внешних печатных форм (ВПФ). Вместо того чтобы внедрять код печатных форм непосредственно в модуль объекта, выносите его во внешние обработки. Это полностью изолирует логику печати от транзакционной логики проведения документов и существенно облегчит последующее обновление конфигурации.
  3. Переходите на использование параметров сеанса. Если вам необходимо хранить глобальные данные, доступные в любом контексте (и на клиенте, и на сервере), откажитесь от глобальных переменных обычного приложения. Используйте ПараметрыСеанса. Они инициализируются один раз при старте сеанса пользователя и работают бесшовно на всех уровнях системы.
  4. Используйте общие модули с повторным использованием возвращаемых значений. Вместо глобальных переменных для кэширования условно-постоянной информации (например, списка префиксов РИБ или настроек пользователя) создавайте общие модули со свойством возврата кэшированных значений.

Пример реализации получения списка префиксов через кэширующий общий модуль:


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

Такой подход гарантирует высокую скорость работы за счет кэширования данных платформой «1С:Предприятие» и обеспечивает абсолютную контекстную безопасность вашего программного кода.

← На главную