При разработке в среде «1С:Предприятие» программисты часто сталкиваются с необходимостью проверки, заполнено ли то или иное поле (реквизит), прежде чем выводить данные на печать или обрабатывать их в коде. Типичный сценарий: если адрес заполнен, мы хотим вывести полное наименование города, а если пуст — записать пробел или пустую строку в печатную форму.
Однако простая интуитивная проверка на равенство значению Неопределено часто приводит к аварийному завершению программы с ошибкой: «Ошибка при вызове метода контекста (...) по причине: Элемент не выбран!». Разберем по шагам, почему возникает эта ошибка и как правильно реализовать проверку в коде.
Рассмотрим код, который изначально пытался использовать разработчик:
Если Адрес = Неопределено Тогда
КомОбъект.FormFields("Г").Result = " ";
Иначе
КомОбъект.FormFields("Г").Result = Адрес.Город.ПолноеНаименование();
КонецЕсли;
Этот код вызывает сбой в строке с вызовом метода ПолноеНаименование(). Выясним причину, проанализировав особенности работы платформы «1С:Предприятие» со ссылочными типами данных:
Неопределено, а специальную типизированную пустую ссылку (ее аналог можно получить методом ПустаяСсылка() конкретного менеджера). Сравнение Адрес = Неопределено возвращает Ложь, и программа заходит в ветку Иначе.Адрес.Город, и при этом реквизит Адрес пуст, платформа не выдает ошибку. Она возвращает значение по умолчанию для вложенного реквизита — в данном случае пустую ссылку справочника городов. Но как только мы пытаемся вызвать встроенный метод (такой как ПолноеНаименование()) у этой пустой ссылки, платформа генерирует исключение: «Элемент не выбран!», так как физически объект базы данных отсутствует и метод вызвать невозможно.Ситуация усложняется, если реквизит имеет составной тип данных (например, когда в поле «Адрес» можно записать как элемент справочника «Адреса», так и строку или контрагента). Начальным незаполненным значением для составного типа действительно является Неопределено. Однако если пользователь сначала выбрал конкретный тип (справочник «Адреса»), а затем просто очистил это поле, значение реквизита изменится. Вместо Неопределено в нем окажется пустая ссылка выбранного ранее типа. Именно поэтому проверка на равенство Неопределено крайне ненадежна в реальных бизнес-сценариях.
Самый простой, надежный и рекомендуемый платформенный метод решения этой проблемы — использование встроенной функции ЗначениеЗаполнено(). Данная функция является универсальной: она возвращает Истина, если переданное ей значение отличается от пустой ссылки любого типа, Неопределено, Null, пустой строки, пустой даты или нуля.
Перепишем исходный код с использованием этой функции:
Если ЗначениеЗаполнено(Адрес.Город) Тогда
КомОбъект.FormFields("Г").Result = Адрес.Город.ПолноеНаименование();
Иначе
КомОбъект.FormFields("Г").Result = " ";
КонецЕсли;
В данном случае, если реквизит Город в адресе не заполнен (содержит пустую ссылку), функция ЗначениеЗаполнено() вернет Ложь, управление перейдет в ветку Иначе, и вызов метода ПолноеНаименование() не произойдет. Это полностью исключает возникновение ошибки.
Если мы точно знаем тип данных проверяемого реквизита, мы можем выполнить явное сравнение с пустой ссылкой этого типа. Рассмотрим два способа реализации такой проверки:
Способ А. Сравнение с пустой ссылкой менеджера справочника:
Если Адрес.Город <> Справочники.Города.ПустаяСсылка() Тогда
КомОбъект.FormFields("Г").Result = Адрес.Город.ПолноеНаименование();
Иначе
КомОбъект.FormFields("Г").Result = " ";
КонецЕсли;
Способ Б. Использование метода .Пустая():
У ссылочных типов данных есть удобный встроенный метод Пустая(). Мы можем применить его:
Если Не Адрес.Город.Пустая() Тогда
КомОбъект.FormFields("Г").Result = Адрес.Город.ПолноеНаименование();
Иначе
КомОбъект.FormFields("Г").Result = " ";
КонецЕсли;
Важное ограничение: метод Пустая() можно использовать только тогда, когда мы абсолютно уверены, что в переменной находится ссылка. Если там окажется значение Неопределено или Null (что часто бывает при работе с составными типами данных или при левом соединении в запросах), вызов метода приведет к критической ошибке «Метод объекта не обнаружен».
Хотя проверка через ЗначениеЗаполнено() решает проблему с ошибкой, многоуровневое обращение через точку (Адрес.Город.ПолноеНаименование()) обладает существенным недостатком — оно порождает неявные запросы к базе данных. Если такой код выполняется в цикле (например, при выводе строк отчета или заполнении табличной части), производительность системы резко снизится. Это классическая проблема «N+1 запросов».
Чтобы избежать падения производительности, правильнее всего получать готовое представление города с помощью объекта Запрос. Посмотрим на пример реализации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(ДокументСсылка.Адрес.Город.ПолноеНаименование, """") КАК ГородНаименование
|ИЗ
| Документ.Распоряжение КАК ДокументСсылка
|ГДЕ
| ДокументСсылка.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Если ЗначениеЗаполнено(Выборка.ГородНаименование) Тогда
КомОбъект.FormFields("Г").Result = Выборка.ГородНаименование;
Иначе
КомОбъект.FormFields("Г").Result = " ";
КонецЕсли;
КонецЕсли;
Использование запроса не только ускоряет работу программы, выполняя всего одно обращение к СУБД, но и автоматически обрабатывает пустые ссылки: если город или адрес не заполнены, запрос вернет пустую строку, что исключает возникновение ошибки «Элемент не выбран!» при формировании печатной формы.
Если разработка ведется в типовой конфигурации на базе Библиотеки стандартных подсистем (БСП), мы можем воспользоваться готовыми оптимизированными функциями общего назначения для безопасного чтения реквизитов. Это позволяет избежать написания громоздких запросов вручную и предотвращает получение объектов в память.
Рассмотрим пример использования функции ЗначениеРеквизитаОбъекта:
ГородСсылка = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Адрес, "Город");
Если ЗначениеЗаполнено(ГородСсылка) Тогда
ПолноеНаименование = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ГородСсылка, "ПолноеНаименование");
КомОбъект.FormFields("Г").Result = ПолноеНаименование;
Иначе
КомОбъект.FormFields("Г").Result = " ";
КонецЕсли;
Таким образом, мы разобрали основные способы решения проблемы «Элемент не выбран». Для простых точечных проверок на клиенте или в формах идеально подойдет функция ЗначениеЗаполнено(), а при массовой обработке данных и формировании сложных печатных форм всегда следует отдавать предпочтение запросам.