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