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