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