При разработке универсального функционала в 1С:Предприятии 8 мы часто сталкиваемся с необходимостью написания кода, который должен корректно отрабатывать для самых разных типов интерфейсных окон. Типичный пример — создание общего обработчика в модуле типа МодификацияКонфигурацииПредопределяемый, выполняющего действия при открытии любой формы в системе.
Попытка напрямую обратиться к основному реквизиту формы с помощью конструкции Форма.Объект гарантированно приведет к аварийному завершению работы программы с ошибкой «Поле объекта не обнаружено (объект)», если пользователь откроет форму списка, форму выбора, отчет или обработку, у которых данный реквизит попросту отсутствует. В этой статье мы детально разберем природу этой проблемы и рассмотрим четыре практических способа реализации безопасной проверки.
В платформе 1С:Предприятие 8 управляемая форма является динамическим объектом. Состав ее реквизитов определяется на этапе проектирования в конфигураторе или генерируется программно при создании. Для форм документов, справочников и бизнес-процессов по умолчанию создается главный реквизит с именем Объект (имеющий тип СправочникОбъект, ДокументОбъект и т.д.).
В то же время формы списков оперируют реквизитом Список (тип ДинамическийСписок), отчеты — реквизитом Отчет, а у произвольных форм состав данных может быть абсолютно любым. Платформа 1С строго типизирована в отношении обращений к свойствам объектов: если свойства с указанным именем в структуре формы нет, система не вернет Неопределено, а сразу сгенерирует исключение. Поэтому перед обращением к свойству Объект необходимо программно убедиться в его существовании.
Ниже мы подробно проанализируем различные методы решения этой задачи — от стандартных механизмов БСП до тонких приемов работы с платформенными функциями.
Если ваша конфигурация разработана на базе БСП (что справедливо для большинства типовых решений от 1С, таких как «Бухгалтерия предприятия», «ERP Управление предприятием», «Управление торговлей» и др.), вам не нужно изобретать велосипед. В составе БСП есть готовый, оптимизированный и надежный инструмент.
Для проверки существования реквизита достаточно вызвать функцию из стандартного общего модуля ОбщегоНазначенияКлиентСервер. Рассмотрим пример реализации:
Если ОбщегоНазначенияКлиентСервер.ЕстьРеквизитИлиСвойствоОбъекта(Форма, "Объект") Тогда
// Реквизит гарантированно существует у формы
Если ЗначениеЗаполнено(Форма.Объект) Тогда
// Выполняем полезную работу с объектом
КонецЕсли;
КонецЕсли;
Преимущества этого подхода:
Этот способ был предложен в качестве финального решения в обсуждении на форуме. Метод управляемой формы ПолучитьРеквизиты() возвращает массив объектов типа РеквизитФормы, содержащих метаданные обо всех доступных реквизитах контекста формы.
Давайте разберем по шагам, как правильно обойти этот массив для решения нашей задачи:
// ВНИМАНИЕ: Метод ПолучитьРеквизиты() может быть вызван только на СЕРВЕРЕ!
РеквизитыФормы = Форма.ПолучитьРеквизиты();
ЕстьРеквизитОбъект = Ложь;
Для Каждого ТекущийРеквизит Из РеквизитыФормы Цикл
Если ТекущийРеквизит.Имя = "Объект" Тогда
ЕстьРеквизитОбъект = Истина;
Прервать; // Прекращаем обход, цель достигнута
КонецЕсли;
КонецЦикла;
Если ЕстьРеквизитОбъект Тогда
// Теперь мы уверены, что обращение к Форма.Объект безопасно
Если ЗначениеЗаполнено(Форма.Объект) Тогда
// Логика обработки объекта
КонецЕсли;
КонецЕсли;
Важные особенности метода:
Основным ограничением данного подхода является его жесткая привязка к серверному контексту. Если ваш общий модуль объявлен как клиентский (или вызывается непосредственно из клиентских процедур формы, например, ПриОткрытии или ПослеЗаписи), вызов ПолучитьРеквизиты() вызовет ошибку компиляции. Использовать данный способ целесообразно только тогда, когда вся цепочка вызовов гарантированно выполняется на сервере.
Что делать, если вы ведете разработку в редкой самописной конфигурации, где БСП отсутствует, а проверка должна одинаково хорошо работать и на клиенте, и на сервере? В этом случае мы можем применить оригинальный и крайне эффективный прием, основанный на поведении встроенной функции платформы ЗаполнитьЗначенияСвойств().
Давайте проанализируем пример функции-помощника:
Функция ФормаСодержитРеквизит(УправляемаяФорма, ИмяРеквизита) Экспорт
// Создаем уникальный маркер, который заведомо не может быть значением реквизита формы
МаркерОтсутствия = Новый УникальныйИдентификатор;
// Инициализируем структуру, где ключом выступает имя искомого реквизита
СтруктураПроверки = Новый Структура(ИмяРеквизита, МаркерОтсутствия);
// Пытаемся скопировать данные из формы в структуру
ЗаполнитьЗначенияСвойств(СтруктураПроверки, УправляемаяФорма);
// Если значение ключа в структуре изменилось — значит, реквизит в форме существует
Возврат СтруктураПроверки[ИмяРеквизита] <> МаркерОтсутствия;
КонецФункции
Разберем по шагам принцип работы этого механизма:
УникальныйИдентификатор. Он служит «сторожевым» значением. Мы не можем использовать для этого Неопределено или Ложь, так как сам проверяемый реквизит формы может содержать такие значения, что привело бы к ложноотрицательному результату.ЗаполнитьЗначенияСвойств(Получатель, Источник) копирует значения из свойств источника в свойства получателя при совпадении их имен. Если у УправляемаяФорма есть реквизит «Объект», его текущее значение запишется в структуру, перезаписав наш маркер. Если реквизита нет — структура останется нетронутой.Этот способ работает невероятно быстро, не требует обращения к метаданным и полностью работоспособен в любом контексте выполнения.
Иногда разработчики идут по пути наименьшего сопротивления, используя стандартный перехват ошибок платформы. Выглядит это следующим образом:
РеквизитСуществует = Истина;
Попытка
// Пробуем прочитать значение реквизита
ВременноеЗначение = Форма.Объект;
Исключение
// Сюда мы попадем, если возникнет ошибка "Поле объекта не обнаружено"
РеквизитСуществует = Ложь;
КонецПопытки;
Если РеквизитСуществует Тогда
// Реквизит найден, обрабатываем данные
КонецЕсли;
Почему этот способ настоятельно не рекомендуется использовать?
Несмотря на кажущуюся простоту, данный подход имеет серьезные архитектурные и технические недостатки:
Попытка...Исключение для управления логикой работы программы (а не для обработки непредсказуемых внешних сбоев вроде обрыва связи или блокировок БД) считается грубой ошибкой.Мы рассмотрели различные варианты решения задачи. Для наглядности сопоставим их возможности в сравнительной таблице:
| Метод проверки | Работает на клиенте | Работает на сервере | Требует БСП | Рекомендуется стандартами 1С |
|---|---|---|---|---|
| Способ 1 (Функция БСП) | Да | Да | Да | Да (Лучший выбор) |
| Способ 2 (ПолучитьРеквизиты) | Нет | Да | Нет | Да (В серверном коде) |
| Способ 3 (Через структуру) | Да | Да | Нет | Да (Для самописных баз) |
| Способ 4 (Попытка-Исключение) | Да | Да | Нет | Нет (Избегать использования) |
Таким образом, при работе со стандартными конфигурациями всегда отдавайте предпочтение Способу 1. Если же вы создаете независимое решение «с нуля», вашим верным соратником станет гибкий и быстрый Способ 3.