Что значит null
Перейти к содержимому

Что значит null

NULL (SQL)

NULL в Системах управления базами данных (СУБД) — специальное значение (псевдозначение), которое может быть записано в поле таблицы базы данных (БД). NULL соответствует понятию «пустое поле», то есть «поле, не содержащее никакого значения». Введено для того, чтобы различать в полях БД пустые (визуально не отображаемые) значения (например, строку нулевой длины) и отсутствующие значения (когда в поле не записано вообще никакого значения, даже пустого).

NULL означает отсутствие, неизвестность информации. Значение NULL не является значением в полном смысле слова: по определению оно означает отсутствие значения и не принадлежит ни одному типу данных. Поэтому NULL не равно ни логическому значению FALSE, ни пустой строке, ни нулю. При сравнении NULL с любым значением будет получен результат NULL, а не FALSE и не 0. Более того, NULL не равно NULL!

Содержание

Необходимость NULL в реляционных БД

  • Мнение 1: NULL является необходимым и обязательным для любой БД, претендующей на реляционность. В частности без него невозможно корректно построить внешнее соединение (OUTER JOIN) строк из двух таблиц. Именно этой точки зрения придерживался Э. Кодд, явно включив его в качестве третьего из 12 правил для реляционных СУБД. Именно этот принцип закреплен в последних стандартах на язык SQL .
  • Мнение 2: Значение NULL не требуется, а его использование — следствие ошибки проектирования БД. В базе данных, разработанной в полном соответствии с критериями нормализации, не может быть полей без значений, а значит, не нужно и специальное псевдозначение для таких полей. На практике, однако, из соображений эффективности, нередко оказывается удобным пренебречь некоторыми из правил нормализации, но одним из видов платы за такое пренебрежение является появление пустых полей, для которых и предназначен NULL [1] .

Использование NULL в БД

В БД, поддерживающих понятие NULL, для поля таблицы при описании определяется, может ли оно быть пустым. Если да, то в это поле можно не записывать никакого значения, и это поле будет иметь значение NULL. Также можно и явно записать в такое поле значение NULL.

Как правило, СУБД не разрешает значение NULL для полей, являющихся частью первичного ключа таблицы. В полях внешних ключей, напротив, NULL допускается. Наличие NULL в поле внешнего ключа может трактоваться как признак отсутствия связанной записи, и для такого внешнего ключа не требуется исполнение правил ссылочной целостности, обязательных для любого другого значения внешнего ключа.

Операции с NULL

Поскольку NULL не является, в общем смысле, значением, использование его в арифметических, строковых, логических и других операциях, строго говоря, некорректно. Тем не менее, большинство СУБД поддерживают такие операции, но вводят для них специальные правила:

  • NULL может присваиваться переменным и записываться в поля, независимо от объявленного типа данных этих переменных (полей);
  • NULL может передаваться в процедуры и функции как легальное значение параметра. Результаты выполнения такой процедуры или функции определяются операциями, выполняемыми с параметрами внутри неё.
  • Любая операция сравнения с NULL (даже операция «NULL = NULL»), даёт в результате значение «неизвестность» (UNKNOWN). Окончательный результат при этом зависит от полного логического выражения в соответствии с таблицей истинности логических операций. Если сравнение с NULL есть вся логическая операция целиком (а не её часть), то результат её аналогичен FALSE (выражение вида IF <что-то> = NULL THEN <действие1> ELSE <действие2> END IF всегда будет приводить к выполнению действия2).
  • Агрегатные и аналитические функции (используемые в SQL в качестве операций на множествах и списках), как правило, игнорируют значения NULL в пользу допустимых значений остальных элементов множества. Например, для функции AVG, предназначенной для нахождения среднего арифметического значения какого-либо выражения, вычисленного для каждой строки из группы, результат получается таким же, как если бы строки, содержащие для этого выражения значение NULL, вообще не содержались бы в группе.
  • Существует специальная системная функция или операция (обычно expr IS [NOT] NULL), возвращающая логическое значение «истина» (TRUE), если expr является (не является) NULL и FALSE в противном случае.

Кроме того, могут существовать специальные системные функции для удобного преобразования NULL к определённым значениям, например, в Oracle имеется системная функция NVL, которая возвращает значение своего параметра, если он не NULL, или значение по умолчанию, если операнд — NULL. В стандарте SQL-92 определены две функции: NULLIF и COALESCE, поэтому их использование является более предпочтительным (если конкретная СУБД их реализует).

Ошибка дизайна

Именно так и никак иначе: null в C# — однозначно ошибочное решение, бездумно скопированное из более ранних языков.

  1. Самое страшное: в качестве значения любого ссылочного типа может использоваться универсальный предатель — null, на которого никак не среагирует компилятор. Зато во время исполнения легко получить нож в спину — NullReferenceException. Обрабатывать это исключение бесполезно: оно означает безусловную ошибку в коде.
  2. Перец на рану: сбой (NRE при попытке разыменования) может находится очень далеко от дефекта (использование null там, где ждут полноценный объект).
  3. Упитанный пушной зверек: null неизлечим — никакие будущие нововведения в платформе и языке не избавят нас от прокаженного унаследованного кода, который физически невозможно перестать использовать.

Этот ящик Пандоры был открыт еще при создании языка ALGOL W великим Хоаром, который позднее назвал собственную идею ошибкой на миллиард долларов.

Лучшая историческая альтернатива

Разумеется, она была, причем очевидная по современным меркам

  1. Унифицированный Nullable для значимых и ссылочных типов.
  2. Разыменование Nullable только через специальные операторы (тернарный — ?:, Элвиса — ?., coalesce — ??), предусматривающие обязательную обработку обоих вариантов (наличие или отсутствие объекта) без выбрасывания исключений.
  3. Примеры:
  4. В этом случае NRE отсутствует по определению: возможность присвоить или передать null определяется типом значения, конвертация с выбросом исключения отсутствует.

Самое трагичное, что все это не было откровением и даже новинкой уже к моменту проектирования первой версии языка. Увы, тогда матерых функциональщиков в команде Хейлсберга не было.

Лекарства для текущей реальности

Хотя прогноз очень серьезный, летального исхода можно избежать за счет применения различных практик и инструментов. Способы и их особенности пронумерованы для удобства ссылок.

Явные проверки на null в операторе if. Очень прямолинейный способ с массой серьезных недостатков.

  1. Гигантская масса шумового кода, единственное назначение которого — выбросить исключение поближе к месту предательства.
  2. Основной сценарий, загроможденный проверками, читается плохо
  3. Требуемую проверку легко пропустить или полениться написать
  4. Проверки можно добавлять отнюдь не везде (например, это нельзя сделать для автосвойств)
  5. Проверки не бесплатны во время выполнения.

Атрибут NotNull. Немного упрощает использование явных проверок

  1. Позволяет использовать статический анализ
  2. Поддерживается R#
  3. Требует добавления изрядного количества скорее вредного, чем бесполезного кода: в львиной доле вариантов использования null недопустим, а значит атрибут придется добавлять буквально везде.

Паттерн проектирования Null object. Очень хороший способ, но с ограниченной сферой применения.

  1. Позволяет не использовать проверок на null там, где существует эквивалент нуля в виде объекта: пустой IEnumerable, пустой массив, пустая строка, ордер с нулевой суммой и т.п. Самое впечатляющее применение — автоматическая реализация интерфейсов в мок-библиотеках.
  2. Бесполезен в остальных ситуация: как только вам потребовалось отличать в коде нулевой объект от остальных — вы имеете эквивалент null вместо null object, что является уже двойным предательством: неполноценный объект, который даже NRE не выбрасывает.

Конвенция о возврате живых объектов по умолчанию. Очень просто и эффективно.

Любой метод или свойство, для которых явно не заявлена возможность возвращать null, должны всегда предоставлять полноценный объект. Для поддержания достаточно выработки хорошей привычки, например, посредством ревью кода.

Конвенция о стандартных способах явно указать что свойство или метод может вернуть null: например, префикс Try или суффикс OrDefault в имени метода. Органичное дополнение к возврату полноценных объектов по умолчанию. Достоинства и недостатки те же.

Атрибут CanBeNull. Добрый антипод-близнец атрибута NotNull.

  1. Поддерживается R#
  2. Позволяет помечать явно опасные места, вместо массовой разметки по площадям как NotNull
  3. Неудобен в случае когда null возвращается часто.

Операторы C# (тернарный, Элвиса, coalesce)

  1. Позволяют элегантно и лаконично организовать проверку и обработку null значений без потери прозрачности основного сценария обработки.
  2. Практически не упрощают выброс ArgumentException при передаче null в качестве значения NotNull параметра.
  3. Покрывают лишь некоторую часть вариантов использования.
  4. Остальные недостатки те же, что и у проверок в лоб.

Тип Optional. Позволяет явно поддержать отсутствие объекта.

  1. Можно полностью исключить NRE
  2. Можно гарантировать наличие обработки обоих основных вариантов на этапе компиляции.
  3. Против легаси этот вариант немного помогает, вернее, помогает немного.
  4. Во время исполнения помимо дополнительных инструкций добавляется еще и memory traffic

Монада Maybe. LINQ для удобной обработки случаев как наличия, так и отсутствия объекта.

  1. Сочетает элегантность кода с полнотой покрытия вариантов использования.
  2. В сочетании с типом Optional дает кумулятивный эффект.
  3. Отладка затруднена, так как с точки зрения отладчика вся цепочка вызовов является одной строкой.
  4. Легаси по-прежнему остается ахиллесовой пятой.
  1. В теории почти идеал, на практике все гораздо печальнее.
  2. Библиотека Code Contracts скорее мертва, чем жива.
  3. Очень сильное замедление сборки, вплоть до невозможности использовать в цикле редактирование-компиляция-отладка.

Пакет Fody/NullGuard. Автоматические проверки на null на стероидах.

  1. Проверяется все: передача параметров, запись, чтение и возврат значений, даже автосвойства.
  2. Никакого оверхеда в исходном коде
  3. Никаких случайных пропусков проверок
  4. Поддержка атрибута AllowNull — с одной стороны это очень хорошо, а с другой — аналогичный атрибут у решарпера другой.
  5. С библиотеками, агрессивно использующими null, требуется довольно много ручной работы по добавлению атрибутов AllowNull
  6. Поддержка отключения проверки для отдельных классов и целых сборок
  7. Используется вплетение кода после компиляции, но время сборки растет умеренно.
  8. Сами проверки работают только во время выполнения.
  9. Гарантируется выброс исключения максимально близко к дефекту (возврату null туда, где ожидается реальный объект).
  10. Тотальность проверок помогает даже при работе с легаси, позволяя как можно быстрее обнаружить, пометить и обезвредить даже null, полученный из чужого кода.
  11. Если отсутствие объекта допустимо — NullGuard сможет помочь только при попытках передать его куда не следует.
  12. Вычистив дефекты в тестовой версии, можно собрать промышленную из тех же исходников с отключенными проверками, получив нулевую стоимость во время выполнения при гарантии сохранения всей прочей логики.

Ссылочные типы без возможности присвоения null (если добавят в одну из будущих версий C#)

  1. Проверки во время компиляции.
  2. Можно полностью ликвидировать NRE в новом коде.
  3. В реальности не реализовано, надеюсь, что только пока
  4. Единообразия со значимыми типами не будет.
  5. Легаси достанет и здесь.

Итоги

Буду краток — все выводы в таблице:

Настоятельная рекомендация Антипаттерн На ваш вкус и потребности
4, 5, 7, 11, 12 (когда и если будет реализовано) 1, 2 3, 6, 8, 9, 10

На предвосхищение ООП через 20 лет не претендую, но дополнениям и критике буду очень рад.

Значение слова null

NULL в Системах управления базами данных , которое может быть записано в поле таблицы базы данных . NULL соответствует понятию «пустое поле», то есть «поле, не содержащее никакого значения». Введено для того, чтобы различать в полях БД пустые значения (например, строку нулевой длины) и отсутствующие значения .

NULL означает отсутствие, неизвестность информации. Значение NULL не является значением в полном смысле слова: по определению оно означает отсутствие значения и не принадлежит ни одному типу данных. Поэтому NULL не равно ни логическому значению FALSE, ни пустой строке, ни нулю. При сравнении NULL с любым значением будет получен результат NULL, а не FALSE и не 0. Более того, NULL не равно NULL!

NULL в языках программирования Си и C++ — макрос , объявленный в заголовочном файле stddef.h . Константа нулевого указателя — это целочисленное константное выражение со значением 0, или такое же выражение, но приведённое к типу void *. Константа нулевого указателя, приведённая к любому типу указателей, является нулевым указателем. Гарантируется, что нулевой указатель не равен указателю на любой объект или функцию. Гарантируется, что любые два нулевых указателя равны между собой. Разыменовывание нулевого указателя является операцией с неопределённым поведением .

Иначе говоря, реализация предоставляет специальное значение — константу нулевого указателя, которую можно присвоить любому указателю и такой указатель при сравнении не будет равен любому «корректному» указателю. То есть, можно считать, что нулевой указатель не содержит корректный адрес в памяти.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *