Правильно используя нуль
Есть разные способы использования 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
чему-то подобному, потому что это более идиоматично.