Нулевые ссылки действительно плохо?

Я слышал, что он сказал, что включение нулевых ссылок в языках программирования является «ошибкой миллиарда долларов». Но почему? Конечно, они могут вызывать NullReferenceExceptions, но что? Любой элемент языка может быть источником ошибок при неправильном использовании.

А какая альтернатива? Я полагаю, вместо этого:

 Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Вы могли бы сказать следующее:

 if (Customer.ExistsWithLastName("Goodman"))
{
    Customer c = Customer.GetByLastName("Goodman") // throws error if not found
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); 
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Но как это лучше? В любом случае, если вы забудете проверить, существует ли клиент, вы получаете исключение.

Я полагаю, что исключение CustomerNotFoundException немного легче отлаживать, чем исключение NullReferenceException, поскольку оно более наглядное. Это все есть?

146 голосов | спросил 5 revs, 4 users 64%
Tim Goodman
1 Jam1000000amThu, 01 Jan 1970 03:00:00 +030070 1970, 03:00:00

21 ответ


87

null is evil

В InfoQ есть презентация по этой теме: Null References: Ошибка в миллиард долларов Тони Хоар

Тип опции

Альтернативой функционального программирования является тип параметра , который может содержать SOME value или NONE.

Хорошая статья Шаблон «Эффект» , которые обсуждают тип Option и предоставляют его реализацию для Java.

Я также нашел отчет об ошибке для Java об этой проблеме: Добавить Nice Варианты типов Java для предотвращения NullPointerExceptions . запрошенная функция была представлена ​​на Java 8 .

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
121

Проблема в том, что, поскольку теоретически любой объект может быть нулевым и бросать исключение, когда вы пытаетесь его использовать, ваш объектно-ориентированный код в основном представляет собой коллекцию неразорвавшихся бомб.

Вы правы в том, что грациозная обработка ошибок может быть функционально идентичной операциям с нулевым контролем if. Но что происходит, когда что-то, что вы убедили, не может возможно быть нулевым, на самом деле, является нулевым? Kerboom. Что бы ни случилось потом, я готов поспорить, что 1) он не будет изящным и 2) вам это не понравится.

И не отклоняйте значение «легко отлаживать». Зрелый код производства - сумасшедшее, растянутое существо; все, что дает вам больше информации о том, что пошло не так, и где можно сэкономить вам часы рытья.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
48

Существует несколько проблем с использованием нулевых ссылок в коде.

Во-первых, он обычно используется для обозначения специального состояния . Вместо определения нового класса или константы для каждого состояния, когда обычно выполняются специализации, использование нулевой ссылки использует с потерями, массово обобщенный тип /значение .

Во-вторых, код отладки становится более сложным, когда появляется нулевая ссылка, и вы пытаетесь определить, что сгенерировало ее , это состояние действует и его причина, даже если вы можете отслеживать его путь выполнения вверх.

В-третьих, нулевые ссылки вводят дополнительные коды кода для проверки .

В-четвертых, когда нулевые ссылки используются как допустимые состояния для параметров, а также возвращаемые значения, защитное программирование (для состояний, вызванных дизайном) требует более полной проверки ссылок в разных местах | Только в случае.

В-пятых, время выполнения языка уже выполняет проверки типов , когда выполняет селекторный поиск в таблице методов объекта. Таким образом, вы дублируете усилия, проверяя, является ли тип объекта допустимым /недействительным, а затем проверяя тип действительного объекта для запуска его метода.

Почему бы не использовать шаблон NullObject до воспользоваться проверкой времени выполнения , чтобы иметь его invoke NOP , специфичные для этого состояния (соответствующие интерфейсу обычного состояния), а также устраняя всю дополнительную проверку для нулевых ссылок в течение всего ваша кодовая база?

Он включает в себя больше работы , создавая класс NullObject для каждого интерфейса, с которым вы хотите представлять особое состояние. Но по крайней мере специализация изолирована для каждого специального состояния, а не для кода, в котором может присутствовать государство. IOW, количество тестов сокращается, поскольку в ваших методах у вас меньше альтернативных путей выполнения .

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
45

Нули не так уж плохи, если вы их не ожидаете. Вы должны явно указывать в коде, который вы ожидаете от нуля, что является проблемой языкового дизайна. Рассмотрим это:

 Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Если вы пытаетесь вызвать методы в Customer?, вы должны получить ошибку времени компиляции. Причина, по которой другие языки не делают этого (IMO), заключается в том, что они не предусматривают изменения типа переменной в зависимости от того, в какой области она находится. Если язык может справиться с этим, тогда проблема может быть решена целиком в пределах типа.

Существует также функциональный способ справиться с этой проблемой, используя опции-типы и Maybe, но я не так хорошо знаком с этим. Я предпочитаю этот путь, потому что для правильного кода теоретически нужен только один символ, добавленный для правильной компиляции.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
18

Есть много отличных ответов, которые покрывают неудачные симптомы null, поэтому я хотел бы представить альтернативный аргумент: Null - это недостаток в системе типов.

Цель системы типов заключается в обеспечении надлежащего соответствия различных компонентов программы; хорошо типизированная программа не может «отключить рельсы» в неопределенное поведение.

Рассмотрим гипотетический диалект Java или любой ваш предпочтительный статически типизированный язык, где вы можете назначить строку "Hello, world!" для любой переменной любого типа:

 Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

И вы можете проверить такие переменные:

 if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

В этом нет ничего невозможного - кто-то мог бы спроектировать такой язык, если захочет. Особым значением не должно быть "Hello, world!" - это могло быть число 42, кортеж (1, 4, 9) или, скажем, null. Но зачем вам это делать? Переменная типа Foo должна содержать только Foo s - вот вся суть системы типов! null не является Foo больше, чем "Hello, world!" is. Хуже того, null не является значением типа any , и вы ничего не можете с ним сделать!

Программист никогда не может быть уверен, что переменная на самом деле содержит Foo, и ни одна из них не может работать; чтобы избежать неопределенного поведения, он должен проверять переменные для "Hello, world!", прежде чем использовать их как Foo s. Обратите внимание, что выполнение проверки строки в предыдущем фрагменте не распространяется на тот факт, что foo1 действительно является Foo - bar, скорее всего, будет иметь свою собственную проверку, просто чтобы быть сейф.

Сравните это с использованием Maybe /Option с шаблоном:

 case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

В коде Just foo, и вы, и программа наверняка знаете, что наша переменная Maybe Foo действительно содержит значение Foo эта информация распространяется по цепочке вызовов, а bar не требуется никаких проверок. Поскольку Maybe Foo является отличным типом от Foo, вы вынуждены обрабатывать возможность того, что он может содержать Nothing, поэтому вы никогда не сможете вслепую с помощью NullPointerException. Вы можете более разумно рассуждать о своей программе, и компилятор может опустить нулевые проверки, зная, что все переменные типа Foo действительно содержат Foo s. Побеждают все.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
14

(Бросив мою шляпу в кольцо на старый вопрос;))

Конкретная проблема с null заключается в том, что она прерывает статическую типизацию.

Если у меня есть Thing t, тогда компилятор может гарантировать, что я могу вызвать t.doSomething(). Ну, UNLESS t имеет значение null во время выполнения. Теперь все ставки отключены. Компилятор сказал, что все в порядке, но я узнал намного позже, что t НЕ на самом деле doSomething(). Поэтому, вместо того, чтобы доверять компилятору, чтобы улавливать ошибки типа, я должен дождаться времени выполнения, чтобы поймать их. Я мог бы просто использовать Python!

Итак, в некотором смысле, null вводит динамическую типизацию в статически типизированную систему с ожидаемыми результатами.

Разница между тем, что деление на ноль или лог отрицательного и т. д. заключается в том, что когда i = 0, он все еще является int. Компилятор все еще может гарантировать его тип. Проблема в том, что логика неправильно применяет это значение таким образом, который не допускается логикой ... но если логика это делает, это в значительной степени определение ошибки.

(Компилятор может улавливать некоторые из этих проблем, BTW. Подобно помеченным выражениям, таким как i = 1 / 0 во время компиляции. Но вы не можете ожидать компилятор, чтобы следовать за функцией и убедиться, что все параметры соответствуют логике функции)

Практическая проблема заключается в том, что вы выполняете много дополнительной работы и добавляете нулевые проверки, чтобы защитить себя во время выполнения, но что, если вы забыли его? Компилятор не позволяет вам назначить:

  

Строка s = новое целое число (1234);

Итак, почему он должен позволять присваивать значение (null), которое разрушит де-ссылки на s?

Смешивая «нет значения» с «типизированными» ссылками в вашем коде, вы ставите дополнительную нагрузку на программистов. И когда происходят NullPointerExceptions, отслеживание их может быть еще более трудоемким. Вместо того, чтобы полагаться на статическую типизацию, чтобы сказать, что «это есть ссылка на что-то ожидаемое», вы позволяете говорить на языке «этот может быть ссылкой на что-то ожидаемое. «

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
12

Указатель null - это инструмент

не враг. Вы просто должны использовать их правильно.

Подумайте минутку о том, сколько времени требуется, чтобы найти и исправить типичную ошибку недопустимый указатель , по сравнению с обнаружением и исправлением ошибки с нулевым указателем. Легко проверить указатель на null. Это PITA, чтобы проверить, указывает ли указатель не нуль на действительные данные.

Если вы все еще не уверены, добавьте хорошую дозу многопоточности к вашему сценарию, а затем подумайте еще раз.

Мораль истории: не бросайте детей с водой. И не обвиняйте инструменты в аварии. Человек, который давно изобрел нулевое число, был довольно умным, с того дня вы могли назвать «ничего». Указатель null не так далеко от него.


РЕДАКТИРОВАТЬ: Хотя шаблон NullObject кажется лучшим решением, чем ссылки, которые могут быть нулевыми, он сам создает проблемы:

  • Ссылка, содержащая NullObject, должна (согласно теории) ничего не делать при вызове метода. Таким образом, тонкие ошибки могут быть введены из-за ошибочно неназначенных ссылок, которые теперь гарантированы не равными нулю (yehaa!), Но выполняют нежелательное действие: ничего. С NPE рядом с очевидным, где проблема. С NullObject, который ведет себя как-то (но неправильно), мы вводим риск ошибок, обнаруженных (слишком) поздно. Это не случайно похоже на недопустимую проблему с указателем и имеет аналогичные последствия: ссылка указывает на что-то, что выглядит как достоверные данные, но это не так, вводит ошибки данных и может быть трудно отследить. Честно говоря, в этих случаях я бы без мигания предпочел бы NPE, который не сразу , теперь, по ошибке логического /данных, который внезапно ударил меня позже по дороге. Помните, что исследование IBM об стоимости ошибок является функцией того, на какой стадии они обнаружены?

  • Идея ничего не делать при вызове метода в NullObject не выполняется, когда вызывается свойство getter или функция или когда метод возвращает значения через out. Скажем, возвращаемое значение - это int, и мы решаем, что «ничего не делать» означает «return 0». Но как мы можем быть уверены, что 0 - это правильное «ничего» (нет)? В конце концов, NullObject ничего не должен делать, но когда его спрашивают о значении, он должен как-то реагировать. Неудача не является вариантом: мы используем NullObject для предотвращения NPE, и мы, конечно же, не будем торговать с другим исключением (не реализовано, делить на ноль, ...), не так ли? Итак, как вы правильно ничего не возвращаете, когда вам нужно что-то вернуть?

Я не могу помочь, но когда кто-то пытается применить шаблон NullObject к каждой проблеме, он больше похож на попытку исправить одну ошибку, выполнив другую. Это, без сомнения, хорошее и полезное решение в некоторых случаях, но это, конечно, не волшебная пуля для всех случаев.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
8

Нулевые ссылки - это ошибка, потому что они допускают нечувствительный код:

foo = null
foo.bar()

Существуют альтернативы, если вы используете систему типов:

 Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

В общем, идея состоит в том, чтобы поместить переменную в поле, и единственное, что вы можете сделать, это распаковать ее, желательно заручиться помощью компилятора, например, предложенной для Eiffel.

Haskell имеет это с нуля (Maybe), в C ++ вы можете использовать boost::optional<T>, но вы все равно можете получить неопределенное поведение ...

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
8
  

А какая альтернатива?

Дополнительные типы и сопоставление шаблонов. Так как я не знаю C #, вот фрагмент кода в вымышленном языке Scala #: -)

 Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}
ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
6

Проблема с нулями заключается в том, что языки, которые позволяют им в значительной степени заставляют вас программировать против нее. Требуется много усилий (гораздо больше, чем пытаться использовать защитные блоки if), чтобы убедиться, что

  1. объекты, которые вы ожидаете от них not , чтобы имеют значение null, действительно никогда не являются нулевыми, а
  2. что ваши защитные механизмы действительно иметь дело со всеми потенциальными НПЭ эффективно.

Таким образом, нули в конечном итоге становятся дорогостоящими.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
4

Проблема, в какой степени ваш язык программирования пытается доказать правильность вашей программы до ее запуска. На статически типизированном языке вы доказываете, что у вас есть правильные типы. Перейдя по умолчанию для непустых ссылок (с необязательными ссылками на NULL), вы можете устранить многие случаи, когда null передан, и этого не должно быть. Вопрос заключается в том, стоит ли прилагать дополнительные усилия для обработки непустых ссылок, с точки зрения правильности программы.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
4

Проблема не столько в null, а в том, что вы не можете указать нулевой ссылочный тип на многих современных языках.

Например, ваш код может выглядеть как

 public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    egg.Crack();
    MixingBowl.Mix(egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

Когда ссылки на классы передаются, защитное программирование побуждает вас снова проверять все параметры для null, даже если вы только что проверили их для null заблаговременно, особенно при создании повторно используемых единиц тестирования. Та же ссылка может быть легко проверена на нуль в 10 раз по всей кодовой базе!

Не было бы лучше, если бы у вас был нормальный тип с нулевым значением, когда вы получаете вещь, обрабатываете нуль тогда и там, а затем передаете ее как непустой тип всем вашим небольшим вспомогательным функциям и классам, не проверяя null все время?

Точка решения Option - не позволяет вам включать состояния ошибок, но позволяет создавать функции, которые неявно не могут принимать нулевые аргументы. Независимо от того, является ли реализация типов по умолчанию «обнуляемой» или «не-обнуляемой» менее важной, чем гибкость наличия обоих инструментов.

http: //twistedoakstudios .com /блог /Post330_non-обнуляемый-тип-VS-с-фиксируя-на-миллиардной ошибку

Это также отображается как # 2 наиболее признанная функция для C # -> https://visualstudio.uservoice.com/forums/121579-зрительно-студия /категория /30931-языки-с

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
1

Оптимизируйте для наиболее распространенного случая.

При проверке нулевого значения все время утомительно - вы хотите иметь возможность просто захватить объект Customer и работать с ним.

В нормальном случае это должно работать нормально - вы просматриваете, получаете объект и используете его.

В исключительном случае , где вы (случайно) просматриваете клиента по имени, не зная, существует ли эта запись /объект, вам нужно указать, что это не удалось. В этой ситуации ответ заключается в том, чтобы выбросить исключение RecordNotFound (или позволить провайдеру SQL ниже сделать это для вас).

Если вы находитесь в ситуации, когда вы не знаете, можете ли вы доверять поступающим данным (параметр), возможно, потому, что он был введен пользователем, тогда вы также можете предоставить «TryGetCustomer (имя, выход) клиент). Перекрестная ссылка с int.Parse и int.TryParse.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
1

Нуль - зло. Однако отсутствие нулевого значения может быть большим злом.

Проблема в том, что в реальном мире у вас часто возникает ситуация, когда у вас нет данных. Ваш пример версии без нулей может все еще взорваться - либо вы сделали логическую ошибку, либо забыли проверить Goodman, или, возможно, Goodman женился между вами, когда вы проверили, и когда вы посмотрели на нее. (Это помогает в оценке логики, если вы фигурируете, что Мориарти наблюдает каждый бит вашего кода и пытается вас опрокинуть.)

Что делает Гудман, когда ее нет? Без нуля вы должны вернуть своего клиента по умолчанию - и теперь вы продаете вещи по умолчанию.

В сущности, дело сводится к тому, важно ли, чтобы код работал независимо от того, что он или что он работает правильно. Если неправильное поведение предпочтительнее никакого поведения, то вы не хотите нулей. (Пример того, как это может быть правильным выбором, рассмотрим первый запуск Ariane V. Ошибка uncaught /0 привела к тому, что ракета совершила жесткий поворот, и самозатухание было выпущено из-за этого. он пытался рассчитать, фактически больше не служил какой-либо цели в тот момент, когда был активирован бустер, - он сделал бы орбиту, несмотря на рутину, возвращающую мусор.)

Как минимум в 99 раз из 100 я бы выбрал использование нулей. Тем не менее, я бы с радостью откликнулся на их версию.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
0

Исключения не eval!

Они там по какой-то причине. Если ваш код работает плохо, существует шаблон гения, называемый exception , и он сообщает вам, что что-то не так.

Избегая использования нулевых объектов, вы скрываете часть этих исключений. Я не говорю о примере OP, где он конвертирует исключение нулевого указателя в хорошо типизированное исключение, на самом деле это может быть хорошо, поскольку оно повышает удобочитаемость. Как правило, когда вы принимаете решение Вариант , как указано в @Jonas, вы скрываете исключения.

Чем в вашем производственном приложении вместо исключения, которое нужно бросить, когда вы нажимаете кнопку, чтобы выбрать пустой вариант, ничего не происходит. Вместо исключения null-указателя будет выбрано исключение, и мы, вероятно, получим отчет об этом исключении (как и во многих производственных приложениях, у нас есть такой механизм), и мы могли бы его исправить.

Создание вашего кода пуленепробиваемым путем исключения исключений - это плохая идея, что делает ваш код пуленепробиваемым, исправляя исключение, ну это путь, который я бы выбрал.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
0

Nulls являются проблематичными, поскольку они должны быть явно проверены, но компилятор не может предупредить вас о том, что вы забыли проверить их. Это может потребовать только статический анализ времени. К счастью, есть несколько хороших альтернатив.

Извлеките переменную из области видимости. Слишком часто, null используется как владелец места, когда программист слишком рано объявляет переменную или слишком долго ее поддерживает. Лучший подход - просто избавиться от переменной. Не объявляйте его, пока не получите действительное значение. Это не так сложно, как вы думаете, и это делает ваш код намного более чистым.

Использовать шаблон нулевого объекта. Иногда недостающее значение является допустимым для вашей системы. Нулевой шаблон объекта является хорошим решением здесь. Это позволяет избежать необходимости явных проверок, но все же позволяет вам представлять нулевое состояние. Это не «документирование состояния ошибки», как утверждают некоторые люди, поскольку в этом случае нулевое состояние является допустимым семантическим состоянием. При этом вы не должны использовать этот шаблон, когда нулевое состояние не является действительным семантическим состоянием. Вы должны просто удалить свою переменную из области.

Используйте параметр Maybe /Option. . Прежде всего, это позволяет компилятору предупредить вас о необходимости проверки отсутствующего значения, но он делает больше, чем заменяет один тип явной проверки на другой. Используя Options, вы можете связать свой код и продолжать, как если бы ваше значение существовало, не нужно фактически проверять до абсолютной последней минуты. В Scala ваш примерный код будет выглядеть примерно так:

 val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

Во второй строке, где мы создаем удивительное сообщение, мы не делаем явной проверки, был ли найден клиент, а красота нам не нужно . Это происходит только до последней строки, которая может быть много строк позже или даже в другом модуле, где мы явно заботимся о том, что произойдет, если Option - None. Не только это, если бы мы забыли это сделать, у него не было бы проверки типа. Даже тогда это делается очень естественным образом, без явного выражения if.

Контрастируйте это с помощью null, где он корректно проверяет, но там, где вы должны сделать явную проверку на каждом шагу пути или все ваше приложение взорвано во время выполнения, вам повезло во время использования вашего теста. Это просто не стоит хлопот.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
0

Центральная проблема NULL заключается в том, что она делает систему ненадежной. В 1980 году Тони Хоар в статье, посвященной его Премии Тьюринга, написал:

  

Итак, лучший мой совет для создателей и дизайнеров ADA был проигнорирован. â € |. Не позволяйте этому языку в его нынешнем   состояние, которое должно использоваться в приложениях, где критическая надежность, т. е.   атомные электростанции, крылатые ракеты, системы раннего предупреждения,   противоракетной обороны. Следующая ракета заблудится, как   результат ошибки языка программирования не может быть поисковым   космическая ракета на безобидной поездке на Венеру: может быть ядерная боеголовка   взрываясь над одним из наших городов. Недостоверное программирование   Язык, генерирующий ненадежные программы, представляет собой гораздо больший риск   к нашей среде и к нашему обществу, чем к небезопасным автомобилям, токсичным   пестицидов или аварий на атомных электростанциях. Будьте бдительны   уменьшить риск, а не увеличивать его.

Язык ADA сильно изменился с тех пор, однако такие проблемы все еще существуют в Java, C # и многих других популярных языках.

Обязанностью разработчика является создание контрактов между клиентом и поставщиком. Например, в C #, как и в Java, вы можете использовать Generics, чтобы минимизировать влияние ссылки Null, создав readonly NullableClass<T> ( два параметра):

 class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

, а затем используйте его как

 NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

или используйте два стиля опций с помощью методов расширения C #:

 customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

Разница значительна. Как клиент метода вы знаете, чего ожидать. Команда может иметь правило:

Если класс не имеет тип NullableClass, то он должен быть не null .

Команда может укрепить эту идею, используя Дизайн по контракту и статическую проверку во время компиляции , например с предварительным условием:

 function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

или для строки

 function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

Этот подход может значительно увеличить приложение надежность и качество программного обеспечения. Дизайн по контракту - это случай Логика Хора , который был заселен Бертраном Майером в его знаменитом объекте -Ориентированная книга по строительству программного обеспечения и язык Эйфелевой башни в 1988 году, но она не используется неверно в современных разработках программного обеспечения.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
-1

В основном центральная идея заключается в том, что проблемы с нулевым указателем ссылки должны попадать во время компиляции вместо времени выполнения. Поэтому, если вы пишете функцию, которая принимает какой-то объект, и вы не хотите, чтобы кто-то вызывал ее с нулевыми ссылками, тогда система типов должна позволять вам указывать это требование, чтобы компилятор мог использовать его, чтобы гарантировать, что вызов вашей функции никогда не будет компиляция, если вызывающий абонент не удовлетворяет вашим требованиям. Это можно сделать разными способами, например, украсить ваши типы или использовать типы опций (также называемые типами NULL на некоторых языках), но, очевидно, это намного больше подходит для разработчиков компиляторов.

Я думаю, что понятно, почему в 1960-х и 70-х годах разработчик компилятора не пошел ва-банк, чтобы реализовать эту идею, потому что машины были ограничены в ресурсах, компиляторы должны были быть относительно простыми, и никто не знал, насколько это плохо выходят через 40 лет.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
-1

У меня есть одно простое возражение против null:

Он полностью разбивает семантику вашего кода, введя неоднозначность .

Часто вы ожидаете , что результат имеет определенный тип. Это семантически звучит. Скажем, вы запросили базу данных для пользователя с определенным id, вы ожидаете, что resut будет иметь определенный тип (= user). Но что, если нет пользователя этого идентификатора? Одно (плохое) решение: добавление значения null. Таким образом, результат ambigous : либо это user, либо это null. Но null не является ожидаемым типом. И здесь начинается запах кода .

Во избежание null вы делаете свой код семантически понятным.

Всегда есть варианты null: рефакторинг для коллекций, Null-objects, optionals и т. д.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
-2

Если семантически возможно, чтобы доступ к хранилищу ссылочного или указательного типа был доступен до того, как код был запущен, чтобы вычислить значение для него, нет никакого идеального выбора в отношении того, что должно произойти. Наличие таких мест по умолчанию для ссылок на нулевой указатель, которые могут свободно считываться и копироваться, но которые будут ошибочными в случае разыменования или индексирования, является одним из возможных вариантов выбора. Другие варианты:

  1. По умолчанию местоположения по умолчанию имеют нулевой указатель, но не позволяют коду смотреть на них (или даже определять, что они являются нулевыми) без сбоев. Возможно, явное средство установки указателя на нуль.
  2. По умолчанию местоположения по умолчанию имеют нулевой указатель и сбой при попытке прочитать один, но предоставляют средство без сбоев проверки того, содержит ли указатель нуль. Кроме того, вы можете указать явное средство установки указателя на нуль.
  3. У местоположений по умолчанию указывается указатель на какой-либо конкретный экземпляр экземпляра по умолчанию указанного типа.
  4. У местоположений по умолчанию указатель на какой-либо конкретный экземпляр экземпляра по умолчанию указанного типа.
  5. Имейте любой доступ к нулевому указателю (а не только операции разыменования) вызывать некоторую запрограммированную программу, чтобы предоставить экземпляр.
  6. Создайте семантику языка, так что коллекции мест хранения не будут существовать, за исключением недоступного временного компилятора, до тех пор, пока не будут выполняться процедуры инициализации для всех членов (что-то вроде конструктора массива должно быть снабжено либо экземпляр по умолчанию, функция, которая возвращает экземпляр или, возможно, пару функций - один для возврата экземпляра и тот, который будет вызываться в ранее построенных экземплярах, если исключение возникает во время построения массива).

Последний выбор может иметь некоторую привлекательность, особенно если язык включает в себя как типы с нулевым, так и непустым значением (можно было бы назвать специальные конструкторы массивов для любых типов, но нужно только их вызвать при создании массивов не-nullable типы), но, вероятно, это было бы невозможно в то время, когда были изобретены нулевые указатели. Из других вариантов никто не кажется более привлекательным, чем разрешать копирование нулевых указателей, но не разыменовывается или не индексируется. Подход №4 может быть удобен для использования в качестве опции, и его следует довольно дешево реализовать, но это, безусловно, не единственный вариант. Требование, чтобы указатели по умолчанию указывали на какой-либо конкретный допустимый объект, намного хуже, чем указатели по умолчанию имеют нулевое значение, которое может быть прочитано или скопировано, но не разыменовано или не индексировано.

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08
-2

Да, NULL - страшный дизайн , в объектно-ориентированном мире. Вкратце, использование NULL приводит к:

  • обработка ошибок ad-hoc (вместо исключений)
  • неоднозначная семантика
  • медленный, а не быстрый сбой
  • компьютерное мышление против объектного мышления
  • изменяемые и неполные объекты

Проверьте это сообщение в блоге для подробного объяснения: http : //www.yegor256.com/2014/05/13/why-null-is-bad.html

ответил Yordan Georgiev 10 PMpTue, 10 Apr 2018 12:14:08 +030014Tuesday 2018, 12:14:08

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132