Я уверен, что разработчики языков, таких как Java или C #, знали проблемы, связанные с существованием нулевых ссылок (см. Действительно ли нулевые ссылки - это плохо? ). Также реализация типа параметра не намного сложнее, чем нулевые ссылки.
Почему они решили включить его в любом случае? Я уверен, что отсутствие нулевых ссылок будет стимулировать (или даже заставлять) код лучшего качества (особенно лучший дизайн библиотеки) как от создателей языка, так и от пользователей.
Это просто из-за консерватизма - «у других языков есть, у нас тоже должно быть ...»?
language-design
null
mrpyo
источник
источник
Ответы:
Отказ от ответственности: Поскольку я лично не знаю дизайнеров языка, любой ответ, который я дам вам, будет умозрительным.
От самого Тони Хоара :
Акцент мой.
Естественно, в то время это не казалось ему плохой идеей. Вероятно, что это было увековечено частично по той же причине - если это показалось хорошей идеей изобретателю удостоенной награды Тьюринга быстрой сортировки, неудивительно, что многие люди до сих пор не понимают, почему это зло. Это также, вероятно, отчасти потому, что новым языкам удобно быть похожими на более старые, как по маркетингу, так и по причине кривой обучения. Дело в точке:
(Источник: http://www.paulgraham.com/icad.html )
И, конечно, C ++ имеет значение null, потому что C имеет значение null, и нет необходимости вдаваться в историческое влияние C. Тип C # заменил J ++, который был реализацией Microsoft Java, и также заменил C ++ как язык выбора для разработки Windows, так что он мог получить его от любого из них.
РЕДАКТИРОВАТЬ Вот еще одна цитата из Hoare стоит рассмотреть:
Опять акцент мой. Sun / Oracle и Microsoft - это компании, и суть любой компании - деньги. Преимущества их наличия
null
могли перевесить минусы или просто слишком сжатые сроки, чтобы полностью рассмотреть вопрос. В качестве примера другой языковой ошибки, которая, вероятно, произошла из-за сроков:(Источник: http://www.artima.com/intv/bloch13.html )
источник
null
на своем языке? Любой ответ на этот вопрос будет умозрительным, если он не придет от дизайнера языка. Я не знаю ни одного такого частого сайта, кроме Эрика Липперта. Последняя часть - красная сельдь по многим причинам. Количество стороннего кода, написанного поверх API MS и Java, явно перевешивает количество кода в самом API. Так что, если ваши клиенты хотятnull
, вы даете имnull
. Вы также предполагаете, что они принялиnull
, стоило им денег.ICloneable
аналогичным образом нарушается в .NET; к сожалению, это единственное место, где недостатки Java не были извлечены вовремя.Конечно.
Позволю себе не согласиться! Вопросы проектирования, которые вошли в типы значений Nullable в C # 2, были сложными, противоречивыми и сложными. Они взяли проектные команды обоих языков и во время выполнения многих месяцев дискуссий, реализации прототипов, и так далее, а на самом деле семантика обнуляемого бокса были изменены очень очень близко к перевозке груза C # 2.0, который был очень спорным.
Весь дизайн - это процесс выбора среди множества тонких и грубо несовместимых целей; Я могу дать лишь краткий обзор лишь нескольких факторов, которые будут рассмотрены:
Ортогональность языковых особенностей обычно считается хорошей вещью. C # имеет типы значений Nullable, типы значений NULL, а также типы ссылок NULL. Необнуляемые ссылочные типы не существуют, что делает систему типов неортогональной.
Знакомство с существующими пользователями C, C ++ и Java очень важно.
Простота взаимодействия с COM важна.
Важное значение имеет простота взаимодействия со всеми другими языками .NET.
Важное значение имеет простота взаимодействия с базами данных.
Согласованность семантики важна; если у нас ссылка TheKingOfFrance равна нулю, значит ли это всегда «сейчас нет короля Франции», или это может также означать «король Франции определенно есть; я просто не знаю, кто он сейчас»? или это может означать «само понятие наличия короля во Франции бессмысленно, так что даже не задавайте вопрос!»? Нуль может означать все эти вещи и многое другое в C #, и все эти понятия полезны.
Стоимость исполнения важна.
Быть поддающимся статическому анализу важно.
Согласованность системы типов важна; можем ли мы всегда знать, что необнуляемая ссылка ни при каких обстоятельствах не считается недействительной? Как насчет конструктора объекта с ненулевым полем ссылочного типа? Как насчет финализатора такого объекта, где объект завершен, потому что код, который должен был заполнить ссылку, вызвал исключение ? Система типов, которая лжет вам о своих гарантиях, опасна.
А как насчет согласованности семантики? Нулевые значения распространяются при использовании, но нулевые ссылки выдают исключения при использовании. Это противоречиво; это несоответствие оправдано какой-то выгодой?
Можем ли мы реализовать эту функцию, не нарушая другие функции? Какие другие возможные будущие функции исключает эта функция?
Вы идете на войну с армией, которая у вас есть, а не с той, которая вам нужна. Помните, что в C # 1.0 не было дженериков, поэтому говорить об
Maybe<T>
альтернативе - совсем не стартер. Должно ли .NET проскочить в течение двух лет, пока команда времени выполнения добавила дженерики, исключительно для устранения нулевых ссылок?Как насчет согласованности системы типов? Вы можете сказать
Nullable<T>
для любого типа значения - нет, подождите, это ложь. Ты не можешь сказатьNullable<Nullable<T>>
. Должны ли вы быть в состоянии? Если так, какова его желательная семантика? Стоит ли делать так, чтобы вся система типов имела специальный случай именно для этой функции?И так далее. Эти решения сложны.
источник
catches
, ничего не делают. Я думаю, что лучше позволить системе взорваться, чем продолжать работу в возможно неправильном состоянии.Нуль служит очень ценной цели представления недостатка стоимости.
Я скажу, что я - самый громкий человек, которого я знаю о злоупотреблениях null и обо всех головных болях и страданиях, которые они могут причинить, особенно когда используются свободно.
Моя личная позиция заключается в том, что люди могут использовать нули только тогда, когда они могут обосновать это необходимостью и уместностью.
Пример, оправдывающий нули:
Дата смерти, как правило, пустое поле. Есть три возможных ситуации с датой смерти. Либо человек умер, и дата известна, человек умер, и дата неизвестна, либо человек не умер, и поэтому дата смерти не существует.
Дата смерти также является полем DateTime и не имеет «неизвестного» или «пустого» значения. У него есть дата по умолчанию, которая появляется, когда вы создаете новую дату и время, которая меняется в зависимости от используемого языка, но технически существует вероятность того, что человек действительно умер в то время и будет помечен как «пустое значение», если вы используйте дату по умолчанию.
Данные должны правильно представлять ситуацию.
Человек мертв Дата смерти известна (09.03.1984)
Простой, 3/9/1984
Человек мертв Дата смерти неизвестна
Так что лучше? Null , '0/0/0000' или '01 / 01/1869 '(или каково ваше значение по умолчанию?)
Человек не умер, дата смерти не применяется
Так что лучше? Null , '0/0/0000' или '01 / 01/1869 '(или каково ваше значение по умолчанию?)
Итак, давайте подумаем над каждым значением ...
Тот факт , иногда вы ли нужно не представлять ничего и уверен , что иногда переменная типа хорошо работает, но часто типы переменных должны быть в состоянии представить ничего.
Если у меня нет яблок, у меня 0 яблок, но что, если я не знаю, сколько у меня яблок?
Безусловно, null злоупотребляет и потенциально опасен, но иногда это необходимо. Во многих случаях это только значение по умолчанию, потому что пока я не предоставлю значение, ему не хватает значения, и что-то должно его представлять. (Значение NULL)
источник
Null serves a very valid purpose of representing a lack of value.
Option
ИлиMaybe
типа служит этот очень веская причина , не в обход системы типов.NOT NULL
. Мой вопрос не был связан с RDBMS, хотя ...Person
тип имеетstate
поле типаState
, которое является дискриминационным объединениемAlive
иDead(dateOfDeath : Date)
.Я бы не пошел так далеко, как «у других языков есть, у нас тоже должно быть…», как будто это некое подобие Джонсам. Ключевой особенностью любого нового языка является возможность взаимодействия с существующими библиотеками на других языках (читай: C). Поскольку C имеет нулевые указатели, для уровня взаимодействия обязательно требуется понятие нулевого (или некоторого другого «не существует» эквивалента, который взрывается при его использовании).
Разработчик языка мог выбрать использование типов опций и заставить вас обрабатывать нулевой путь везде , где все может быть нулевым. И это почти наверняка приведет к меньшему количеству ошибок.
Но (особенно для Java и C # из-за времени их появления и их целевой аудитории) использование типов опций для этого уровня взаимодействия могло бы повредить, если бы не торпедировало их принятие. Либо тип параметра передается полностью вверх, что раздражает программистов на C ++ середины и конца 90-х - или уровень интероперабельности генерирует исключения при обнаружении нулей, раздражая программистов на C ++ середины и конца 90-х. ..
источник
Option
для использования в Scala, что так же просто, как иval x = Option(possiblyNullReference)
. На практике людям не нужно очень много времени, чтобы увидеть преимуществаOption
.Match
методом, который принимает делегатов в качестве аргументов. Затем вы передаете лямбда-выраженияMatch
(бонусные баллы за использование именованных аргументов) иMatch
вызываете правильное.Прежде всего, я думаю, что мы все можем согласиться с тем, что концепция ничтожности необходима. Есть некоторые ситуации, когда нам нужно представить отсутствие информации.
Разрешение
null
ссылок (и указателей) - это только одна реализация этой концепции, и, возможно, самая популярная, хотя известно, что у нее есть проблемы: C, Java, Python, Ruby, PHP, JavaScript, ... все используют подобноеnull
.Почему ? Ну, а какая альтернатива?
В функциональных языках, таких как Haskell, у вас есть
Option
илиMaybe
тип; однако они построены на:Теперь, поддерживали ли оригинальные C, Java, Python, Ruby или PHP какие-либо из этих функций? Нет. Дефектные дженерики Java недавно появились в истории языка, и я почему-то сомневаюсь, что другие вообще их реализуют.
Там у вас есть это.
null
легко, параметрические алгебраические типы данных сложнее. Люди пошли за самой простой альтернативой.источник
Object
», не в состоянии признать, что практическим приложениям нужны неразделимые изменяемые объекты и разделяемые неизменяемые объекты (оба из которых должны вести себя как значения), а также разделяемые и неразделимые юридические лица. Использование одногоObject
типа для всего не устраняет необходимость в таких различиях; это просто затрудняет их правильное использование.Null / nil / none само по себе не является злом.
Если вы посмотрите его вводящее в заблуждение известное выступление «Ошибка в миллиард долларов», Тони Хоар расскажет о том, что позволить любой переменной иметь нулевое значение было огромной ошибкой. Альтернатива - использование Options - фактически не избавляется от нулевых ссылок. Вместо этого он позволяет вам указать, какие переменные могут содержать значение null, а какие нет.
На самом деле, с современными языками, которые реализуют правильную обработку исключений, ошибки нулевого разыменования ничем не отличаются от любых других исключений - вы находите это, вы исправляете это. Некоторые альтернативы пустым ссылкам (например, шаблон Null Object) скрывают ошибки, заставляя вещи молча терпеть неудачу намного позже. На мой взгляд, гораздо лучше быстро провалиться .
Тогда возникает вопрос: почему языки не могут реализовать Опции? На самом деле, пожалуй, самый популярный язык всех времен C ++ имеет возможность определять объектные переменные, которые не могут быть назначены
NULL
. Это решение «нулевой проблемы», о которой говорил Тони Хоар. Почему следующий самый популярный типизированный язык, Java, не имеет его? Кто-то может спросить, почему у него так много недостатков в целом, особенно в его системе типов. Я не думаю, что вы действительно можете сказать, что языки систематически совершают эту ошибку. Некоторые делают, некоторые нет.источник
null
.null
это единственное разумное значение по умолчанию. Однако ссылки, используемые для инкапсуляции значения, могут иметь разумное поведение по умолчанию в тех случаях, когда тип будет иметь или может создать разумный экземпляр по умолчанию. Многие аспекты того, как должны вести себя ссылки, зависят от того, инкапсулируют ли они ценность и каким образом, но ...foo
содержит единственную ссылку наint[]
содержимое,{1,2,3}
а код хочетfoo
содержать ссылку наint[]
содержащее{2,2,3}
, самый быстрый способ достичь этого - увеличитьfoo[0]
. Если код хочет дать методу знать, что онfoo
выполняется{1,2,3}
, другой метод не будет модифицировать массив и не будет сохранять ссылку за пределами точки, в которойfoo
он хотел бы ее изменить, самый быстрый способ добиться этого - передать ссылку на массив. Если у Java был тип «эфемерная ссылка только для чтения», то ...Потому что языки программирования обычно разрабатываются, чтобы быть практически полезными, а не технически правильными. Дело в том, что
null
состояния являются обычным явлением из-за плохих или отсутствующих данных или состояния, которое еще не было принято. Технически превосходные решения все более громоздки, чем просто допущение нулевых состояний и смирение с тем фактом, что программисты допускают ошибки.Например, если я хочу написать простой скрипт, который работает с файлом, я могу написать псевдокод, например:
и он просто потерпит неудачу, если joebloggs.txt не существует. Дело в том, что для простых сценариев это, вероятно, хорошо, и для многих ситуаций в более сложном коде я знаю, что он существует, и сбоя не произойдет, поэтому вынуждая меня проверять, тратит ли я свое время. Более безопасные альтернативы достигают своей безопасности, заставляя меня правильно справляться с состоянием потенциального сбоя, но часто я не хочу этого делать, я просто хочу продолжать.
источник
for line in file
) и выдает бессмысленное исключение нулевой ссылки, что нормально для такой простой программы, но вызывает реальные проблемы отладки в гораздо более сложных системах. Если бы нули не существовали, разработчик openfile не смог бы совершить эту ошибку.let file = something(...).unwrap()
. В зависимости от вашего POV, это простой способ не обрабатывать ошибки или лаконичное утверждение, что null не может возникнуть. Потраченное время минимально, и вы экономите время в других местах, потому что вам не нужно выяснять, может ли что-то быть нулевым. Другое преимущество (которое само по себе может стоить дополнительного вызова) состоит в том, что вы явно игнорируете регистр ошибки; когда это терпит неудачу, есть немного сомнения, что пошло не так и куда нужно исправить.if file exists { open file }
страдает от состояния гонки. Единственный надежный способ узнать, удастся ли открыть файл, - это попробовать открыть его.Существуют четкие, практические применения указателя
NULL
(илиnil
, илиNil
, илиnull
, или,Nothing
или как это называется на вашем предпочтительном языке).Для тех языков, которые не имеют системы исключений (например, C), нулевой указатель может использоваться как признак ошибки, когда указатель должен быть возвращен. Например:
Здесь
NULL
возвращенный изmalloc(3)
используется в качестве маркера сбоя.При использовании в аргументах метода / функции он может указывать на использование по умолчанию для аргумента или игнорировать выходной аргумент. Пример ниже.
Даже для тех языков с механизмом исключений нулевой указатель может использоваться в качестве признака мягкой ошибки (то есть ошибок, которые можно исправить), особенно когда обработка исключений стоит дорого (например, Objective-C):
Здесь программная ошибка не приводит к падению программы, если она не обнаружена. Это устраняет сумасшедшую попытку выполнения, которую имеет Java, и обеспечивает лучший контроль над потоком программ, поскольку программные ошибки не прерываются (а несколько оставшихся жестких исключений, как правило, не восстанавливаются и остаются необработанными).
источник
null
от тех, которые должны. Например, если я хочу новый тип, который содержит 5 значений в Java, я мог бы использовать enum, но я получаю тип, который может содержать 6 значений (5 я хотел +null
). Это недостаток в системе типов.Null
может быть присвоено значение только тогда, когда значения типа не содержат данных (например, значения enum). Как только ваши значения становятся чем-то более сложным (например, структура),null
нельзя присвоить значение, которое имеет смысл для этого типа. Нет способа использоватьnull
как структуру или список. И, опять же, проблема с использованиемnull
в качестве сигнала ошибки заключается в том, что мы не можем сказать, что может вернуть ноль или принять ноль. Любая переменная в вашей программе может быть,null
если вы не очень тщательно проверяете каждую из нихnull
перед каждым использованием, чего никто не делает.null
как используемое значение по умолчанию (например, значение по умолчаниюstring
ведет себя как пустая строка, как это делалось в предыдущей Общей Объектной Модели). Все, что было бы необходимо, было бы для использования языков,call
а не дляcallvirt
вызова не виртуальных членов.Есть два связанных, но немного разных вопроса:
null
вообще существовать? Или вы всегда должны использоватьMaybe<T>
там, где null полезен?Должны ли все ссылки быть обнуляемыми? Если нет, то какой должен быть по умолчанию?
Необходимость явного объявления обнуляемых ссылочных типов как
string?
или аналогичных позволит избежать большинства (но не всех)null
причин проблем , не слишком отличаясь от того, к чему привыкли программисты.Я по крайней мере согласен с вами, что не все ссылки должны быть обнуляемыми. Но избегание нуля не обходится без его сложностей:
.NET инициализирует все поля до того,
default<T>
как они будут доступны для управляемого кода. Это означает, что для ссылочных типов вам нужноnull
или что-то эквивалентное, и что типы значений могут быть инициализированы до какого-то нуля без выполнения кода. Хотя у обоих из них есть серьезные недостатки, простотаdefault
инициализации, возможно, перевесила эти недостатки.Для полей экземпляра вы можете обойти эту проблему , требуя инициализацию полей перед тем обнажая
this
указатель на управляемый код. Spec # пошел по этому пути, используя другой синтаксис из цепочки конструктора по сравнению с C #.Для статических полей обеспечить это сложнее, если только вы не наложите строгие ограничения на то, какой код может выполняться в инициализаторе поля, поскольку вы не можете просто скрыть
this
указатель.Как инициализировать массивы ссылочных типов? Рассмотрим,
List<T>
который поддерживается массивом с емкостью, превышающей длину. Остальные элементы должны иметь некоторое значение.Другая проблема заключается в том, что он не допускает такие методы,
bool TryGetValue<T>(key, out T value)
которые возвращают,default(T)
какvalue
будто они ничего не находят. Хотя в этом случае легко утверждать, что параметр out в первую очередь плохой дизайн, и этот метод должен возвращать различающее объединение или может быть вместо этого.Все эти проблемы могут быть решены, но это не так просто, как «запретить ноль и все хорошо».
источник
List<T>
ИМХО лучший пример, потому что для каждого из них требуется либоT
значение по умолчанию, либо для каждого элемента в резервном хранилище,Maybe<T>
с дополнительным полем «isValid», даже еслиT
оно естьMaybe<U>
, либо код дляList<T>
поведения ведет себя по-разному в зависимости наT
самом ли это обнуляемый тип. Я бы посчитал инициализациюT[]
элементов значением по умолчанию наименьшим злом из этих выборов, но это, конечно, означает, что элементы должны иметь значение по умолчанию.Большинство полезных языков программирования позволяют записывать и считывать элементы данных в произвольных последовательностях, так что зачастую невозможно статически определить порядок, в котором происходит чтение и запись перед запуском программы. Существует много случаев, когда код фактически сохраняет полезные данные в каждом слоте перед их чтением, но при этом доказать это будет сложно. Таким образом, часто бывает необходимо запускать программы там, где по крайней мере теоретически возможно, чтобы код пытался прочитать что-то, что еще не было написано с полезным значением. Независимо от того, допустимо ли это для кода, нет общего способа помешать выполнению кода. Вопрос только в том, что должно произойти, когда это произойдет.
Разные языки и системы используют разные подходы.
Один из подходов состоит в том, чтобы сказать, что любая попытка прочитать что-то, что не было записано, приведет к немедленной ошибке.
Второй подход состоит в том, чтобы требовать от кода предоставления некоторого значения в каждом месте, прежде чем его можно будет прочитать, даже если бы не было никакого способа для семантически полезного сохраненного значения.
Третий подход состоит в том, чтобы просто игнорировать проблему и позволить тому, что произойдет "естественно", просто произойти.
Четвертый подход состоит в том, чтобы сказать, что у каждого типа должно быть значение по умолчанию, и любой слот, который не был записан ни с чем другим, будет по умолчанию иметь это значение.
Подход № 4 намного безопаснее, чем подход № 3, и в целом дешевле, чем подходы № 1 и № 2. Тогда возникает вопрос о том, каким должно быть значение по умолчанию для ссылочного типа. Для неизменяемых ссылочных типов во многих случаях имеет смысл определить экземпляр по умолчанию и сказать, что значением по умолчанию для любой переменной этого типа должна быть ссылка на этот экземпляр. Однако для изменяемых ссылочных типов это было бы не очень полезно. Если предпринята попытка использовать изменяемый ссылочный тип до того, как он будет записан, обычно не существует никаких безопасных действий, кроме как отловить в точке попытки использования.
С семантической точки зрения, если у вас есть массив
customers
типаCustomer[20]
, и кто-то пытается,Customer[4].GiveMoney(23)
не сохраняя ничегоCustomer[4]
, выполнение должно быть перехвачено. Можно утверждать, что попытка чтенияCustomer[4]
должна быть немедленно завершена, а не ждать, пока код попытается это сделатьGiveMoney
, но есть достаточно случаев, когда полезно прочитать слот, выяснить, что он не содержит значения, а затем использовать это информация о том, что попытка чтения сама по себе не удалась, часто доставляет большие неудобстваНекоторые языки позволяют указывать, что определенные переменные никогда не должны содержать ноль, и любая попытка сохранить ноль должна вызвать немедленную ловушку. Это полезная функция. В целом, тем не менее, любой язык, который позволяет программистам создавать массивы ссылок, должен либо учитывать возможность пустых элементов массива, либо принудительно инициализировать элементы массива данными, которые не могут быть значимыми.
источник
Maybe
/Option
тип решить проблему с # 2, так как если вы не имеете значение для вашей справки пока , но будет иметь один в будущем, вы можете просто хранитьNothing
вMaybe <Ref type>
?null
правильно / разумно использовать?List<T>
бытьT[]
илиMaybe<T>
? А как насчет типа поддержкиList<Maybe<T>>
?Maybe
имеет смысл использовать тип поддержки,List
поскольку онMaybe
содержит одно значение. Вы имели в видуMaybe<T>[]
?Nothing
можно назначать только значениям типаMaybe
, так что это не совсем то же самое, что присваиваниеnull
.Maybe<T>
иT
два разных типа.