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