Неопределено, Null и пустые значения: какую проверку ставить в коде 1С

· 9 мин

Содержание

Проверить значение на Неопределено в 1С проще всего прямым сравнением: Если Значение = Неопределено Тогда. Тот же результат даёт ТипЗнч(Значение) = Тип("Неопределено"). Если же нужно отсеять сразу всё «пустое» — Неопределено, Null, пустую ссылку, ноль, пустую строку и пустую дату, — используйте ЗначениеЗаполнено(Значение).

Выбор между этими вариантами — не вопрос вкуса. Сравнение ловит ровно одно значение, а ЗначениеЗаполнено ловит целый набор. Перепутаете — получите либо ошибку «Преобразование значения к типу Число не может быть выполнено», либо тихо пропущенный ноль, который на самом деле был нужен. Всё описанное относится к встроенному языку платформы 1С:Предприятие 8.3 и не зависит от конфигурации.

Что такое Неопределено и откуда оно берётся в коде

Неопределено — это одновременно тип и его единственное значение. У типа нет других значений, поэтому «пустое Неопределено» и «заполненное Неопределено» не бывают: значение либо равно Неопределено, либо нет.

Платформа возвращает Неопределено во многих местах, и не всегда это очевидно:

  • 🔹 объявленная через Перем переменная до первого присваивания;
  • 🔹 функция, которая закончилась без Возврат Значение, или Возврат; без значения;
  • 🔹 необязательный параметр процедуры со значением по умолчанию = Неопределено;
  • 🔹 Соответствие.Получить(Ключ) и Соответствие[Ключ], когда ключа нет;
  • 🔹 Массив.Найти(), ТаблицаЗначений.Найти(), СписокЗначений.НайтиПоЗначению() — если ничего не найдено;
  • 🔹 элементы массива, созданного как Новый Массив(3);
  • 🔹 реквизит составного типа, который ещё не заполнили;
  • 🔹 Ссылка.ПолучитьОбъект(), если объекта по этой ссылке в базе нет;
  • 🔹 null при чтении JSON через ПрочитатьJSON().

Отдельно запомните, где Неопределено не возвращается. Справочники.Контрагенты.НайтиПоКоду() и НайтиПоНаименованию() при неудаче дают пустую ссылку, а не Неопределено. Проверка Если Найденный = Неопределено здесь всегда ложна, и код пойдёт дальше с пустой ссылкой.

Контрагент = Справочники.Контрагенты.НайтиПоКоду("000000123");
Если Контрагент.Пустая() Тогда      // правильно
	Сообщить("Контрагент не найден");
КонецЕсли;

Проверка оператором сравнения и через ТипЗнч

Прямое сравнение — самый быстрый и читаемый способ. Оно работает для значения любого типа и не вызывает ошибок.

Если Значение = Неопределено Тогда
	// значение не задано
КонецЕсли;

Если Значение <> Неопределено Тогда
	// что-то пришло, но что именно — ещё неизвестно
КонецЕсли;

Вариант через тип делает то же самое, только длиннее:

Если ТипЗнч(Значение) = Тип("Неопределено") Тогда
	// значение не задано
КонецЕсли;

Зачем тогда ТипЗнч? Он полезен, когда проверка Неопределено — лишь одна ветка разбора по типам. Например, в обработчике, куда может прийти строка, число или ничего:

ТипЗначения = ТипЗнч(Значение);
Если ТипЗначения = Тип("Неопределено") Тогда
	Результат = "";
ИначеЕсли ТипЗначения = Тип("Число") Тогда
	Результат = Формат(Значение, "ЧГ=0");
ИначеЕсли ТипЗначения = Тип("Строка") Тогда
	Результат = СокрЛП(Значение);
Иначе
	ВызватьИсключение "Неожиданный тип значения: " + Строка(ТипЗначения);
КонецЕсли;

Неопределено = Null возвращает Ложь. Это два разных значения разных типов. Если в переменную может прийти и то и другое, проверяйте оба явно или берите ЗначениеЗаполнено.

ЗначениеЗаполнено: что функция считает пустым

ЗначениеЗаполнено() возвращает Ложь, если значение равно «пустому» значению своего типа. Для основных типов это выглядит так:

Значение= НеопределеноЗначениеЗаполнено()
НеопределеноИстинаЛожь
NullЛожьЛожь
0ЛожьЛожь
"" (пустая строка)ЛожьЛожь
'00010101' (пустая дата)ЛожьЛожь
Пустая ссылка Справочники.Номенклатура.ПустаяСсылка()ЛожьЛожь
5, "Текст", заполненная ссылкаЛожьИстина

Из таблицы видна главная ловушка. Ноль для ЗначениеЗаполнено — «пусто». Если в документе количество может законно быть нулевым, а вам надо отличить «пользователь ввёл 0» от «поле вообще не передали», функция не подойдёт. Нужна проверка = Неопределено.

Вторая особенность — работа со ссылками. ЗначениеЗаполнено смотрит только на саму ссылку и не обращается к базе. Ссылка на удалённый объект («битая», в форме видна как <Объект не найден>) для функции заполнена. Проверить, что объект существует, можно так:

Если ЗначениеЗаполнено(Ссылка) И Ссылка.ПолучитьОбъект() = Неопределено Тогда
	// ссылка есть, а объекта в базе нет
КонецЕсли;

ПолучитьОбъект() читает объект целиком, поэтому в цикле по тысячам ссылок так делать не стоит. В конфигурациях на БСП есть функция ОбщегоНазначения.СсылкаСуществует(Ссылка) — она проверяет наличие запросом и работает легче.

Когда что выбирать:

ЗадачаПроверка
Параметр не передалиПараметр = Неопределено
Реквизит не заполнен пользователемНе ЗначениеЗаполнено(Реквизит)
Поиск в коллекции ничего не нашёлНайденное = Неопределено
Поиск справочника по коду не нашёлСсылка.Пустая()
Поле из запроса с левым соединением пустоеПоле = Null или ЕСТЬNULL в тексте запроса
Ноль — допустимое значениетолько = Неопределено

Null в запросах и выборках: отдельный случай

Null приходит из запросов. Чаще всего — из левого соединения, когда в правой таблице нет пары, и из обращения к реквизиту через точку у пустой ссылки. В языке запросов Null нельзя проверить обычным равенством: Поле = NULL не даёт ожидаемого результата. Пишите так:

ВЫБРАТЬ
	Номенклатура.Ссылка КАК Номенклатура,
	ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
	Справочник.Номенклатура КАК Номенклатура
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
		ПО Номенклатура.Ссылка = Остатки.Номенклатура

ЕСТЬNULL(Поле, 0) подставляет ноль вместо Null: товары без остатков попадут в результат с нулём, а не с пустотой. Имя регистра в примере условное — подставьте своё.

Если нужны, наоборот, только товары без записей в остатках, добавьте условие ГДЕ Остатки.Номенклатура ЕСТЬ NULL. Обратное условие пишется как НЕ Остатки.Номенклатура ЕСТЬ NULL.

С реквизитами составного типа другая история. Незаполненный реквизит составного типа хранит Неопределено, а не Null. Для него в запросе есть литерал НЕОПРЕДЕЛЕНО:

ГДЕ
	Документ.Основание = НЕОПРЕДЕЛЕНО

Если результат запроса уже выгружен и вы проверяете его в коде, Null сравнивается обычным равенством:

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
	Если Выборка.Цена = Null Тогда
		Цена = 0;
	Иначе
		Цена = Выборка.Цена;
	КонецЕсли;
КонецЦикла;

Короче и надёжнее — не тащить Null в код вообще, а закрыть его через ЕСТЬNULL в тексте запроса. Тогда вся последующая арифметика работает без проверок.

Необязательные параметры процедур и функций

Значение по умолчанию = Неопределено — стандартный способ сделать параметр необязательным:

Функция СуммаСоСкидкой(Сумма, ПроцентСкидки = Неопределено) Экспорт
	Если ПроцентСкидки = Неопределено Тогда
		ПроцентСкидки = ПроцентСкидкиПоУмолчанию();
	КонецЕсли;
	Возврат Сумма - Окр(Сумма * ПроцентСкидки / 100, 2);
КонецФункции

Проверьте пример на числах: при сумме 1 250 и скидке 8% скидка составит 100, результат — 1 150. Если вызвать СуммаСоСкидкой(1250, 0), функция честно посчитает без скидки. Вот почему здесь = Неопределено, а не ЗначениеЗаполнено: со вторым вариантом явно переданный ноль заменился бы скидкой по умолчанию.

Ограничение, о котором полезно знать: изнутри функции нельзя отличить «параметр не передали» от «передали Неопределено явно». Для платформы это одно и то же. Если различие важно, заведите отдельный параметр-флаг или передавайте параметры структурой.

Параметры в структуре

Со структурой обращение к отсутствующему ключу через точку падает с ошибкой «Поле объекта не обнаружено». Безопасный способ — метод Свойство с двумя параметрами:

Значение = Неопределено;
Если Параметры.Свойство("Склад", Значение) И Значение <> Неопределено Тогда
	// ключ есть и значение задано
КонецЕсли;

Свойство() возвращает Истина, если ключ есть, и кладёт значение во второй параметр. Ключ может существовать и при этом хранить Неопределено, поэтому вторая проверка не лишняя.

Значения из соответствия

С соответствием похожая неоднозначность. Получить(Ключ) возвращает Неопределено и когда ключа нет, и когда под ключом лежит Неопределено. Обычно этого достаточно:

Цена = ЦеныНоменклатуры.Получить(Номенклатура);
Если Цена = Неопределено Тогда
	Цена = 0;
	ТоварыБезЦены.Добавить(Номенклатура);
КонецЕсли;

Если же разница между «ключа нет» и «значение пустое» принципиальна, не кладите Неопределено в соответствие как значение. Храните, например, 0 или отдельный признак — тогда Неопределено всегда означает «ключа нет».

Возврат функции

Функция, в которой есть ветка без Возврат Значение, вернёт Неопределено. Вызывающий код это часто не ожидает. Хорошая привычка — либо возвращать значение во всех ветках, либо явно описать в комментарии к функции, что Неопределено означает «не найдено».

Типизированные проверки и приведение к нужному типу

Когда значение пришло извне — из формы, файла, HTTP-запроса, JSON — тип может быть любым. Простое <> Неопределено тут мало: внутри может оказаться строка вместо числа. Проверьте тип перед использованием:

Если ТипЗнч(Количество) <> Тип("Число") Тогда
	ВызватьИсключение "Количество должно быть числом";
КонецЕсли;

Для ссылочных типов удобна проверка через описание типов:

ДопустимыеТипы = Новый ОписаниеТипов("СправочникСсылка.Контрагенты");
Если Не ДопустимыеТипы.СодержитТип(ТипЗнч(Владелец)) Тогда
	// пришло что-то другое или Неопределено
КонецЕсли;

Второй приём — не проверять, а привести значение к нужному типу. ОписаниеТипов.ПривестиЗначение() превращает Неопределено и неподходящие значения в пустое значение типа:

ОписаниеЧисла = Новый ОписаниеТипов("Число");
Количество = ОписаниеЧисла.ПривестиЗначение(ВходящееЗначение);
// Неопределено → 0, число остаётся числом

Этот способ хорош для загрузки данных, где «нет значения» и «ноль» по смыслу совпадают. Где не совпадают — сначала проверка = Неопределено, потом приведение.

Ещё одна мелочь: Строка(Неопределено) возвращает пустую строку. Поэтому склейка "Склад: " + Строка(Склад) не упадёт, но и не покажет, что склад не задан. Для сообщений пользователю лучше выводить понятный текст вроде «не указан».

Частые ошибки при проверке на Неопределено

Диагностика: выберите симптом

Ещё несколько привычек, которые избавляют от этих ошибок:

  1. Проверяйте значение сразу там, где оно появилось: после Получить(), Найти(), чтения JSON. Не тащите Неопределено через три процедуры.
  2. В запросах закрывайте Null на месте, через ЕСТЬNULL.
  3. Не пишите Если Значение Тогда для проверки на пустоту. Условие Если ждёт Булево; другой тип, включая Неопределено, вызовет ошибку преобразования к Булево.
  4. Не смешивайте в одной функции разные смыслы Неопределено: «не найдено», «ошибка» и «не задано». Для ошибок есть ВызватьИсключение.
  5. Перед обращением к реквизиту через точку у ссылки, которая может быть пустой, проверьте её — иначе на сервере получите Null там, где ждали значение.

Частые вопросы

Что быстрее: `= Неопределено` или `ЗначениеЗаполнено`?
Оба варианта работают мгновенно и к базе не обращаются. Выбирайте по смыслу: сравнение ловит только Неопределено, функция — любое пустое значение.
Можно ли проверить на Неопределено на клиенте?
Да. Сравнение, ТипЗнч и ЗначениеЗаполнено доступны и на клиенте, и на сервере. А вот обратиться к реквизиту ссылки через точку на клиенте нельзя — для этого нужен серверный вызов.
Как проверить, что значение не Неопределено и не Null одновременно?
Если ноль и пустая строка тоже считаются пустыми — ЗначениеЗаполнено(Значение). Если нет — Значение <> Неопределено И Значение <> Null.
Как в запросе отобрать документы с пустым реквизитом составного типа?
Условием Реквизит = НЕОПРЕДЕЛЕНО. Если реквизит может хранить и пустые ссылки разных типов, добавьте их через ИЛИ или используйте условие ТИПЗНАЧЕНИЯ.
Что вернёт ЗаписатьJSON для Неопределено?
Значение null. При обратном чтении через ПрочитатьJSON() оно снова станет Неопределено, так что проверка в коде после загрузки остаётся той же.