Должны ли ссылки на объектно-ориентированных языках программирования быть неубедительными по умолчанию?
Нулевые указатели были описаны как " ошибка в миллиард долларов". Некоторые языки имеют ссылочные типы, которым не может быть присвоено нулевое значение.
Интересно, следует ли при разработке нового объектно-ориентированного языка использовать поведение по умолчанию для ссылок, чтобы предотвратить присвоение значения null. Затем для переопределения этого поведения можно использовать специальную версию. Например:
MyClass notNullable = new MyClass();
notNullable = null; // Error!
// a la C#, where "T?" means "Nullable<T>"
MyClass? nullable = new MyClass();
nullable = null; // Allowed
Итак, мой вопрос: есть ли причина не делать этого на новом языке программирования?
EDIT:
Я хотел добавить, что недавний комментарий к моему блогу указал, что типы, не имеющие нулевого значения, имеют определенную проблему, когда используется в массивах. Я также хочу поблагодарить всех за полезную информацию. Очень полезно, извините, я мог только выбрать один ответ.
Ответы
Ответ 1
Основное препятствие, которое я вижу по умолчанию для типов с нулевыми значениями по умолчанию, заключается в том, что некоторая часть сообщества программирования предпочитает шаблон использования шаблона:
x = new Foo()
x.Prop <- someInitValue
x.DoSomething()
для перегруженных конструкторов:
x = new Foo(someInitValue)
x.DoSomething()
и это оставляет конструктор API в привязке относительно начального значения переменных экземпляра, которые в противном случае могли бы быть пустыми.
Конечно, как и сам "нуль", сам шаблон создания создает множество бессмысленных состояний объектов и предотвращает полезные инварианты, поэтому избавление от этого на самом деле является скорее благословением, чем бичем. Однако это немного влияет на дизайн API таким образом, что многие люди будут незнакомы с ним, так что это не так легко сделать.
Но в целом, да, если есть большой катаклизм, который уничтожает все существующие языки и компиляторы, можно только надеяться, что когда мы перестроим, мы не будем повторять эту конкретную ошибку. Исключением является исключение, а не правило!
Ответ 2
Мне нравится способ Ocaml справиться с проблемой "может быть нулевой". Всякий раз, когда значение типа 'a
может быть неизвестно /undefined/unitialized, оно завернуто в тип 'a Option
, который может быть либо None
, либо Some x
, где x
является фактическим не-nullable стоимость. При доступе к x
вам необходимо использовать соответствующий механизм для разворачивания. Вот функция, которая увеличивает число с нулевым значением и возвращает 0 на None
>>> let f = function Some x -> x+1 | None->0 ;;
val f : int option -> int = <fun>
Как это работает:
>>> f Some 5 ;;
- : int = 6
>>> f None ;;
- : int = 0
Механизм сопоставления заставляет вас рассмотреть случай None
. Вот что происходит, когда вы его забываете:
>>> let f = function Some x -> x+1 ;;
Characters 8-31:
let f = function Some x -> x+1 ;;
^^^^^^^^^^^^^^^^^^^^^^^
Warning P: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
None
val f : int option -> int = <fun>
(Это всего лишь предупреждение, а не ошибка. Теперь, если вы передадите None
в функцию, вы получите соответствующее исключение.)
Варианты типов + сопоставление - общий механизм, он также работает для таких вещей, как сопоставление списка только с head :: tail
(забывание пустого списка).
Ответ 3
Еще лучше отключить нулевые ссылки. В редких случаях, когда "ничего" является допустимым значением, может существовать состояние объекта, которое ему соответствует, но ссылка все равно указывает на этот объект, но не имеет нулевого значения.
Ответ 4
Как я понимаю, обоснование Мартина Одерского для включения null в Scala заключается в том, чтобы легко использовать библиотеки Java (т.е. все ваши api не имеют, например, "Object?" повсюду):
http://www.artima.com/scalazine/articles/goals_of_scala.html
В идеале, я думаю, что null должен быть включен в язык как функция, но не-nullable должен быть по умолчанию для всех типов. Это сэкономит много времени и предотвратит ошибки.
Ответ 5
Самая большая ошибка, связанная с ошибкой в дизайне языка, - это отсутствие ловушки при индексировании нулевых указателей. Многие компиляторы поймают ловушку при попытке разыменования нулевого указателя, не будут ловутся, если добавить смещение к указателю и попытаться разыграть это. В стандарте C попытка добавления смещения - это Undefined Поведение, а стоимость выполнения проверки указателя там будет не хуже, чем проверка разыменования (особенно если компилятор мог понять, что если он проверил, null перед добавлением смещения, возможно, не потребуется повторно проверять его позже).
Что касается поддержки языков для непустых переменных, может быть полезно иметь возможность запросить, чтобы определенные переменные или поля, чьи объявления включали начальное значение, должны автоматически проверять любые записи, чтобы гарантировать, что произойдет немедленное исключение, если попытка делается для записи в них нулевого значения. Массивы могут включать в себя аналогичную функцию, если бы был эффективный идиоматический способ построения массива путем построения всех элементов и не сделать доступным объект массива до завершения построения. Обратите внимание, что, вероятно, также должен быть способ указания функции очистки, которая должна быть вызвана для всех ранее построенных элементов, если исключение возникает до того, как все элементы были построены.
Наконец, было бы полезно, если бы можно было указать, что некоторые члены экземпляра должны быть вызваны с помощью не виртуальных вызовов и должны быть invokable даже на нулевых элементах. Что-то вроде String.IsNullOrEmpty(someStringVariable)
является отвратительным по сравнению с someStringVariable.IsNullOrEmpty
.
Ответ 6
Null - это только проблема, потому что разработчики не проверяют, что что-то действительно, прежде чем использовать его, но, если люди начнут злоупотреблять новой конструкцией с нулевым значением, она не решит никаких реальных проблем.
Важно просто проверить, что каждая переменная, которая может быть нулевой, проверяется до ее использования, и если это означает, что вы должны использовать аннотации, чтобы разрешить обход проверки, то это может иметь смысл, иначе компилятор может не скомпилируйте, пока вы не проверите.
Мы ставим все больше логики в компиляторы, чтобы защитить разработчиков от самих себя, что страшно и очень грустно, поскольку мы знаем, что мы должны делать, и все же иногда пропускаем шаги.
Итак, ваше решение также подвергнется злоупотреблениям, и мы вернемся к тому, с чего мы начали, к сожалению.
UPDATE:
На основе некоторых комментариев здесь была тема в моих ответах. Думаю, я должен был быть более явным в своем первоначальном ответе:
В принципе, если целью является ограничение воздействия нулевых переменных, тогда компилятор выдает ошибку всякий раз, когда переменная не проверяется на значение null, и если вы хотите предположить, что она никогда не будет нулевой, тогда потребуется аннотация пропустить чек. Таким образом, вы даете людям возможность предполагать, но вы также легко можете найти все места в коде, который имеет это предположение, и в обзоре кода он может быть оценен, если предположение действительно.
Это поможет защитить, не ограничивая разработчика, но позволяя легко узнать, где предполагается, что он не является нулевым.
Я считаю, что нам нужна гибкость, и я предпочел бы, чтобы компиляция занимала больше времени, чем что-то негативное влияние на время выполнения, и я думаю, что мое решение будет делать то, что вам нужно.
Ответ 7
Нет
Состояние неинициализации будет существовать определенным образом из-за логической необходимости; в настоящее время обозначение равно null.
Возможно, может быть разработана "действительная, но неинициализированная" концепция объекта, но как это существенно отличается? Семантика "доступа к неинициализированному объекту" все еще будет существовать.
Лучшим маршрутом является проверка статического времени, когда вы не получаете доступ к объекту, который не назначен (я не могу придумать что-то с головы, которое предотвратит это, кроме строк).