При активной разработке с использованием механизма расширений на платформе «1С:Предприятие 8.3» программисты часто сталкиваются с крайне странным поведением системы: абсолютно идентичные с виду значения при сравнении возвращают Ложь. Типичный пример такой ситуации — проверка реквизита на пустую ссылку заимствованного справочника. Внутри модуля расширения привычное условие сравнения с результатом функции ПредопределенноеЗначение() внезапно перестает работать, хотя отладчик показывает, что обе части выражения содержат пустую ссылку одного и того же типа.
В этой статье мы подробно разберем, почему возникает этот неприятный эффект, рассмотрим внутренние технические причины платформенного сбоя, а также изучим надежные практические способы его обхода.
Рассмотрим классическую ситуацию. В расширение заимствован справочник Контрагенты. В коде расширения (например, в обработчике «После» или «Вместо» какого-либо метода) разработчик пишет следующее условие:
Если Контрагент = ПредопределенноеЗначение("Справочник.Контрагенты.ПустаяСсылка") Тогда
// Данный блок кода не выполняется, даже если Контрагент пустой!
КонецЕсли;
Если мы остановим исполнение программы в отладчике на этой строке и добавим выражения в табло, то увидим поразительную картину:
Контрагент возвращает пустую ссылку справочника контрагентов.ПредопределенноеЗначение("Справочник.Контрагенты.ПустаяСсылка") также возвращает пустую ссылку этого же справочника.Контрагент = ПредопределенноеЗначение("Справочник.Контрагенты.ПустаяСсылка") возвращает значение Ложь!Если же мы перепишем условие, обратившись напрямую к менеджеру справочника, система отработает корректно и вернет Истина:
Если Контрагент = Справочники.Контрагенты.ПустаяСсылка() Тогда
// Этот код успешно выполняется
КонецЕсли;
Такое поведение связано с технической ошибкой платформы (официально зарегистрированный баг фирмы «1С» под номером 000038192). Причина кроется в особенностях кэширования и сопоставления типов метаданных. Когда объект заимствуется в расширение конфигурации, платформа создает для него внутреннее описание типов в контексте этого расширения. Из-за некорректной десериализации или рассинхронизации пространств имен, функция ПредопределенноеЗначение() генерирует пустую ссылку, привязанную к контексту типов расширения, в то время как реквизит из базы данных (или переданный из основной конфигурации) ссылается на тип основной конфигурации.
Для платформы на низком бинарном уровне это два разных типа данных, несмотря на одинаковое строковое представление в отладчике. Отсюда и возникает феномен «неравенства» одинаковых пустых ссылок.
Данная проблема наиболее ярко проявила себя в версиях платформы линейки 8.3.11 (в частности, в релизе 8.3.11.2867), но также периодически воспроизводилась и на более поздних версиях вплоть до 8.3.14. Ошибка затрагивает следующие сценарии:
ПредопределенноеЗначение().ПредопределенноеЗначение("Перечисление.Имя.Значение") часто возвращает ложный результат.Хотя фирма «1С» заявила об исправлении этой ошибки в релизе 8.3.11.2924, разработчики отмечают аналогичные симптомы при работе с перечислениями и в более поздних версиях. Поэтому важно знать безопасные шаблоны программирования, исключающие подобные риски.
Если в вашей организации используется платформа, подверженная этой ошибке, или вы хотите застраховать свой код от возможных будущих багов кэширования в расширениях, настоятельно рекомендуется использовать альтернативные методы проверки.
Для получения пустой ссылки заимствованного справочника или документа всегда используйте прямое обращение к его менеджеру вместо строкового вызова ПредопределенноеЗначение(). Этот метод гарантирует получение системной ссылки в правильном контексте типов:
// Безопасный вариант сравнения
Если Контрагент = Справочники.Contragents.ПустаяСсылка() Тогда
// Код гарантированно сработает
КонецЕсли;
В большинстве случаев разработчики проверяют реквизит на пустую ссылку просто для того, чтобы понять, заполнен он или нет. В таких сценариях лучшим решением будет использование встроенной функции ЗначениеЗаполнено(). Она работает на уровне платформы чрезвычайно быстро, не требует обращения к метаданным конкретных классов и не страдает от ошибок кэширования типов в расширениях:
// Оптимальный и быстрый способ проверки заполнения
Если НЕ ЗначениеЗаполнено(Контрагент) Тогда
// Объект не заполнен (пустая ссылка, Неопределено или Null)
КонецЕсли;
Важное примечание: функция ЗначениеЗаполнено() вернет Ложь для любого пустого значения (будь то пустая ссылка справочника, пустая дата, число 0 или пустая строка). Если по логике программы вам критически важно убедиться, что в реквизите находится именно пустая ссылка конкретного типа (например, при анализе составных типов данных), используйте комбинированную проверку:
Если ТипЗнч(Контрагент) = Тип("СправочникСсылка.Контрагенты") И НЕ ЗначениеЗаполнено(Контрагент) Тогда
// Мы точно знаем, что это пустая ссылка именно справочника Контрагенты
КонецЕсли;
Если ваша конфигурация построена на базе БСП, вы можете использовать возможности общего модуля ОбщегоНазначенияКлиентСервер. Функция ПредопределенныйЭлемент() из этого модуля выполняет дополнительные проверки безопасности и корректно инициализирует объекты даже при возникновении платформенных коллизий с кэшем:
// Использование безопасного аналога из состава БСП
ПустоеЗначение = ОбщегоНазначенияКлиентСервер.ПредопределенныйЭлемент("Справочник.Контрагенты.ПустаяСсылка");
Если Контрагент = ПустоеЗначение Тогда
// Проверка проходит корректно
КонецЕсли;
Если ваш код написан правильно, платформа обновлена, но после очередного обновления расширения пользователи начали жаловаться на неработающие условия, проблема может крыться в застрявшем серверном кэше. В этом случае выполните следующие действия:
snccg и локальные кэши пользователей в каталоге Local Application Data\1C\1Cv8).Это полностью обновит связи метаданных между основной конфигурацией и вашими расширениями.
Проанализировав ситуацию, мы пришли к выводу, что использование функции ПредопределенноеЗначение() для получения пустых ссылок внутри расширений является небезопасной практикой. Отдавайте предпочтение конструкции ЗначениеЗаполнено() или прямому обращению к менеджеру объекта Справочники.ИмяСправочника.ПустаяСсылка(). Это сделает ваш код более надежным, быстродействующим и устойчивым к любым неожиданным обновлениям платформы 1С.