Что бы мы делали без NULL?
Однажды я прочитал, что наличие обнуляемых типов - абсолютное зло. Я верю, что это было в статье, написанной самим человеком, который их создал (в Аде?) Я верю, что это статья
В любом случае, а что, если по умолчанию такой язык, как C#, использует ненулевые типы? Как бы вы заменили некоторые из распространенных идиом в C# или Ruby или любом другом общем языке, где null
является приемлемым значением?
11 ответов
Вместо того, чтобы прямо заявлять, что обнуляемые типы являются злом, я бы сказал: большинство языков прививают обнуляемость целым типам, когда эти два понятия действительно должны быть ортогональными.
Например, все не примитивные типы Java (и все ссылочные типы C#) обнуляются. Зачем? Мы можем идти вперед и назад, но в конечном итоге я готов поспорить, что ответ сводится к "это было легко". Нет ничего присущего языку Java, который требует повсеместного обнуляемости. Ссылки на C++ предлагают прекрасный пример того, как изгнать нули на уровне компилятора. Конечно, C++ имеет гораздо более отвратительный синтаксис, который Java явно пытался сократить, поэтому некоторые хорошие функции оказались на переднем крае наряду с плохими.
Типы значений Nullable в C# 2.0 предложили шаг в правильном направлении - отделить Nullability от несвязанных семантик типов или, что еще хуже, от деталей реализации CLR - но все еще не хватает способа сделать обратное с ссылочными типами. (Контракты кода хороши и все, но они не встроены в систему типов, как мы здесь обсуждаем.)
Множество функциональных или иным образом малоизвестных языков поняли эти концепции "прямо" с самого начала... но если бы они широко использовались, мы бы не стали обсуждать это...
Чтобы ответить на ваш вопрос: запрещать нули на современном языке, оптом, было бы так же глупо, как и так называемая "ошибка в миллиард долларов". Существуют допустимые программные конструкции, в которых хорошо иметь нулевые значения: необязательные параметры, любые виды вычислений по умолчанию / резервные значения, когда оператор объединения приводит к лаконичному коду, взаимодействию с реляционными базами данных и т. Д. Принудительное использование значений Sentinel, NaN и т. Д. "лекарство" гораздо хуже, чем болезнь.
Тем не менее, я предварительно согласен с мнением, выраженным в цитате, до тех пор, пока я могу уточнить, чтобы соответствовать моему собственному опыту:
- количество ситуаций, когда нулевые значения желательны, меньше, чем думает большинство людей
- как только вы вводите пустые значения в библиотеку или путь кода, избавиться от них гораздо сложнее, чем добавлять их. (так что не позволяйте младшим программистам делать это по прихоти!)
- Масштаб ошибочных ошибок с переменным временем жизни
- Коррелят №3: ранний сбой
Мы использовали бы типы опций для (очень) нескольких мест, где на самом деле желательно разрешить нулевое значение, и у нас было бы намного меньше неясных ошибок, поскольку любая ссылка на объект гарантированно указывала бы на действительный экземпляр соответствующего типа.
Haskell - мощный язык, в котором нет понятия нуль. По сути, каждая переменная должна быть инициализирована ненулевым значением. Если вы хотите представить "необязательную" переменную (переменная может иметь значение, но не может), вы можете использовать специальный тип "Возможно".
Реализовать эту систему в Haskell проще, чем в C#, потому что в Haskell данные неизменяемы, поэтому не имеет смысла иметь нулевую ссылку, которую вы будете позже заполнять. Однако в C# последняя ссылка в связанном списке может иметь нулевой указатель на следующую ссылку, которая заполняется при расширении списка. Я не знаю, как будет выглядеть процедурный язык без нулевых типов.
Кроме того, обратите внимание, что многие из вышеперечисленных людей, похоже, предлагают заменить пустые значения логическими значениями типа "ничего" (999-999-9999, "NULL" и т. Д.). Эти значения на самом деле ничего не решают, потому что проблема, с которой сталкиваются люди с пустыми значениями, заключается в том, что они представляют собой особый случай, но люди забывают кодировать этот особый случай. Со специфическими для типа значениями логического "ничего" люди все равно забывают кодировать для особого случая, но избегают ошибок, которые улавливают эту ошибку, что является плохой вещью.
Вы можете принять простое правило: все переменные инициализируются (по умолчанию это может быть переопределено) в неизменяемое значение, определяемое классом переменной. Для скаляров это обычно будет некая форма нуля. Для ссылок каждый класс будет определять, каково его "нулевое" значение, и ссылки будут инициализированы указателем на это значение.
Это было бы эффективно для общеязыковой реализации шаблона NullObject: http://en.wikipedia.org/wiki/Null_Object_pattern Таким образом, на самом деле он не избавляется от нулевых объектов, он просто не дает им быть особыми случаями, которые должны быть обрабатываются как таковые.
Я думаю, что вы имеете в виду этот доклад: " Нулевые ссылки: ошибка в миллиард долларов"
Ноль не проблема, это язык, позволяющий вам писать код, который обращается к значениям, которые могут быть нулевыми.
Если бы язык просто требовал, чтобы любой доступ к указателю был проверен или преобразован в необнуляемый тип, 99% ошибок, связанных с нулем, исчезли бы. Например, в C++
void fun(foo *f)
{
f->x; // error: possibly null
if (f)
{
f->x; // ok
foo &r = *f; // ok, convert to non-nullable type
if (...) f = bar; // possibly null again
f->x; // error
r.x; // ok
}
}
К сожалению, это не может быть модифицировано для большинства языков, так как это может привести к поломке большого количества кода, но было бы вполне разумно для нового языка.
Tcl - это один из языков, который не только не имеет понятия "ноль", но и в котором само понятие "ноль" расходится с ядром языка. В tcl мы говорим: "все является строкой". На самом деле это означает, что tcl имеет строгую семантику значений (что случается со строками по умолчанию).
Так что же используют программисты tcl для представления данных без данных? В основном это пустая строка. В некоторых случаях, когда пустая строка может представлять данные, обычно это одно из:
В любом случае используйте пустую строку - в большинстве случаев это не имеет значения для конечного пользователя.
Используйте значение, которое вы знаете, не будет существовать в потоке данных - например, строка
"_NULL_"
или число9999999
или мой любимый байт NUL"\0"
,Используйте структуру данных, обернутую вокруг значения - самый простой - это список (который другие языки называют массивами). Список одного элемента означает, что значение существует, нулевой элемент означает ноль.
Проверка на наличие переменной -
[info exists variable_name]
,
Интересно отметить, что Tcl - не единственный язык со строгой семантикой значений. C также имеет строгую семантику значений, но семантика значений по умолчанию оказывается целыми числами, а не строками.
О, почти забыл еще один:
Некоторые библиотеки используют вариант числа 2, который позволяет пользователю указать, что является заполнителем для "нет данных". В основном это позволяет вам указать значение по умолчанию (а если вы этого не сделаете, значением по умолчанию обычно будет пустая строка).
Реально говоря, в любом мощном языке программирования, который в первую очередь допускает указатели или ссылки на объекты, будут ситуации, когда код сможет получить доступ к указателям, на которых не был запущен какой-либо код инициализации. Может быть возможно гарантировать, что такие указатели будут инициализированы к некоторому статическому значению, но это не кажется очень полезным. Если у машины есть общие средства захвата доступа к неинициализированным переменным (будь то указатели или что-то еще), это лучше, чем нулевые указатели в специальном корпусе, но в остальном самые большие связанные с нулями ошибки, которые я вижу, возникают в реализациях, которые допускают арифметику с нулевыми указателями, Добавление 5 к (char*)0 не должно приводить к символьному указателю на адрес 5; это должно вызвать ошибку (если уместно создавать указатели на абсолютные адреса, должны быть другие способы сделать это).
Что бы мы делали без NULL? Придумай это!:-) Вам не нужно быть ученым, чтобы использовать 0, если вы ищете значение внутриполосного указателя, чтобы выразить фактически не указатель.
Мы создали бы всевозможные странные конструкции, чтобы передать сообщение об объекте, "являющемся недействительным" или "не находящемся там", как видно из других ответов. Сообщение, которое null
может передать очень хорошо.
- Шаблон Null Object имеет свои недостатки, как я объяснил здесь.
- Специфичные для домена нули. Это заставляет вас проверять магические числа, что плохо.
- Оболочки для коллекций, где пустая коллекция означает "нет значения". Nullable обертки будет лучше, но это мало чем отличается от проверки на
null
или используя шаблон Null Object.
Лично я написал бы некоторый препроцессор C#, который позволяет мне использовать null
, Это будет затем сопоставить с некоторыми dynamic
объект, который бросает NullReferenceException
всякий раз, когда метод вызывается на нем.
Еще в 1965 году нулевые ссылки могли выглядеть как ошибка. Но в настоящее время со всеми видами инструментов анализа кода, которые предупреждают нас о нулевых ссылках, нам не нужно сильно беспокоиться. С точки зрения программирования null
это очень ценное ключевое слово.
Мы используем либо
Дискриминаторы. Дополнительный атрибут или флаг или индикатор, который говорит, что значение является "нулевым" и должно игнорироваться.
Специфичные для домена нули. Определенное значение - в пределах разрешенного домена - это интерпретируется как "игнорировать это значение". Например, номер социального страхования 999-99-9999 может быть нулевым значением для конкретного домена, которое говорит, что SSN либо неизвестен, либо неприменим.