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