Правильно используя нуль
Есть разные способы использования null. Наиболее распространенный и семантически правильный способ - использовать его, когда вы можете иметь или не иметь единственное значение. В этом случае значение либо равно, nullлибо является чем-то значимым, например запись из базы данных или что-то еще.
В этих ситуациях вы в основном используете это так (в псевдокоде):
if (value is null) {
doSomethingAboutIt();
return;
}
doSomethingUseful(value);
проблема
И это очень большая проблема. Проблема в том, что к тому времени, когда вы вызываете doSomethingUsefulзначение, возможно, не было проверено null! Если этого не произошло, программа, скорее всего, вылетит. И пользователь может даже не увидеть никаких хороших сообщений об ошибках, оставаясь с чем-то вроде «ужасная ошибка: хотел значение, но получил ноль!» (после обновления: хотя может быть даже менее информативная ошибка, например Segmentation fault. Core dumped., или, что еще хуже, в некоторых случаях нет ошибок и неправильных манипуляций с нулем)
Забыть писать чеки nullи обрабатывать nullситуации - очень распространенная ошибка . Вот почему изобретатель Тони Хоар nullсказал на конференции по программному обеспечению QCon London в 2009 году, что он допустил ошибку в миллиард долларов в 1965 году:
https://www.infoq.com/presentations/Null-References-The-Billion-Dollar- Ошибка-Тони Хоара
Как избежать проблемы
Некоторые технологии и языки делают проверку на nullневозможность забыть по-разному, уменьшая количество ошибок.
Например, Haskell имеет Maybeмонаду вместо нулей. Предположим, что DatabaseRecordэто определенный пользователем тип. В Haskell значение типа Maybe DatabaseRecordможет быть равным Just <somevalue>или равным Nothing. Затем вы можете использовать его по-разному, но независимо от того, как вы его используете, вы не сможете применить некоторые операции, Nothingне зная об этом.
Например, эта вызываемая функция zeroAsDefaultвозвращает xдля Just xи 0для Nothing:
zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
Nothing -> 0
Just x -> x
Кристиан Хакл говорит, что C ++ 17 и Scala имеют свои собственные пути. Поэтому вы можете попытаться выяснить, есть ли на вашем языке что-то подобное, и использовать его.
Нули все еще широко используются
Если у вас нет ничего лучше, тогда nullпользуйтесь. Просто продолжайте следить за этим. Объявления типов в функциях помогут вам в любом случае.
Также это может звучать не очень прогрессивно, но вы должны проверить, хотят ли ваши коллеги использовать nullили что-то еще. Они могут быть консервативными и могут не хотеть использовать новые структуры данных по некоторым причинам. Например, поддержка старых версий языка. Такие вещи должны быть заявлены в стандартах кодирования проекта и должным образом обсуждены с командой.
По вашему предложению
Вы предлагаете использовать отдельное логическое поле. Но вы все равно должны это проверить и все же можете забыть это проверить. Так что здесь ничего не выиграно. Если вы даже можете забыть что-то еще, например, каждый раз обновлять оба значения, то это еще хуже. Если проблема забвения чека nullне решена, тогда нет смысла. Избегать nullтрудно, и вы не должны делать это таким образом, чтобы это еще хуже.
Как не использовать нуль
Наконец, есть общие способы использования nullнеправильно. Один из таких способов - использовать его вместо пустых структур данных, таких как массивы и строки. Пустой массив - это правильный массив, как и любой другой! Почти всегда важно и полезно, чтобы структуры данных, которые могут соответствовать нескольким значениям, могли быть пустыми, то есть иметь длину 0.
С точки зрения алгебры пустая строка для строк очень похожа на 0 для чисел, то есть тождество:
a+0=a
concat(str, '')=str
Пустая строка позволяет строкам в целом стать моноидом:
https://en.wikipedia.org/wiki/Monoid
Если вы его не получите, это не так важно для вас.
Теперь давайте посмотрим, почему это важно для программирования на следующем примере:
for (element in array) {
doSomething(element);
}
Если мы передадим пустой массив здесь, код будет работать нормально. Это просто ничего не сделает. Однако, если мы передадим nullздесь, то, скорее всего, получим сбой с ошибкой типа «не могу пройти через ноль, извините». Мы могли бы обернуть это, ifно это не так чисто, и опять же, вы можете забыть проверить это
Как обращаться с нулем
Что doSomethingAboutIt()следует делать, и особенно то, должно ли оно генерировать исключение, это еще один сложный вопрос. Короче говоря, это зависит от того, nullбыло ли приемлемое входное значение для данной задачи и что ожидается в ответ. Исключения составляют события, которые не были ожидаемы. Я не буду углубляться в эту тему. Этот ответ очень длинен уже.
std::optionalилиOption. В других языках вам, возможно, придется создать соответствующий механизм самостоятельно, или вы можете прибегнуть кnullчему-то подобному, потому что это более идиоматично.