Ошибка в программе на миллион

[Обновление 2015-10-31] Дополнительная трансляция, измененная из StackOverflowПочтовыйПараграф:

Чашка! В нашей компании фамилия сотрудника Null.При использовании его фамилии в качестве термина запроса все приложения запросов сотрудников вылетали из строя! Что я должен делать?

В 1965 году кто-то допустил худшую ошибку в области информатики. Ошибка уродливее, чем обратная косая черта в Windows, более странная, чем ===, более распространенная, чем PHP, более неудачная, чем CORS, и более тревожная, чем дженерики Java. XMLHttpRequest, более сложный для понимания, чем препроцессор C, более подверженный фрагментации, чем MongoDB, и более прискорбный, чем UTF-16.

«Я называю нулевую ссылку своей ошибкой на миллиард долларов. Она была изобретена в 1965 году, когда я разработал первую всеобъемлющую систему ссылочных типов на объектно-ориентированном языке (АЛГОЛ W). Моя цель — гарантировать, что использование всех ссылок абсолютно безопасно, компилятор проверит автоматически. Но я не смог устоять перед соблазном добавить нулевые ссылки только потому, что это очень легко реализовать. Это вызвало бесчисленное количество ошибок, уязвимостей и систем. Авария могла привести к убыткам в миллиарды долларов в следующий раз. 40 лет. В последние годы люди начали использовать различные программы анализа программ, такие как Microsoft PREfix и PREfast, чтобы проверять ссылки и предупреждать, если существует риск ненулевого значения. Новые языки программирования, такие как Spec #, имеют ввел объявление ненулевых ссылок. Это решение, которое я отверг в 1965 году »-« Нулевые ссылки: ошибка на миллиард долларов »Тони Хоар, лауреат премии Тьюринга

В ознаменование 50-летия нулевой ошибки мистера Хора в этой статье объясняется, что такое null, почему это так ужасно и как этого избежать.

Проще говоря: NULL — это значение, которое не является значением. Вот и проблема.

Эта проблема усугубилась в самом популярном языке всех времен, и теперь у него много имен: NULL, nil, null, None, Nothing, Nil и nullptr. У каждого языка есть свои нюансы.

Некоторые из проблем, вызванных NULL, связаны только с конкретным языком, в то время как другие универсальны; некоторые — просто разные аспекты проблемы.

NULL…

  1. Тип Subversion
  2. Это грязно
  3. Это особый случай
  4. Сделать API хуже
  5. Сделать неправильные языковые решения хуже
  6. Сложно отлаживать
  7. Не сочетается

1. Тип подрывной деятельности NULL

Статически типизированные языки могут проверять использование типов в программе без фактического выполнения программы и обеспечивать определенные гарантии поведения программы.

Например, в Java, если я напишуx.toUppercase(), Компилятор проверитx тип. в случае x Является String, То проверка типа прошла успешно; еслиx Является Socket, Тогда проверка типа не удалась.

При написании большого и сложного программного обеспечения статическая проверка типов является мощным инструментом. Но для Java эти отличные проверки во время компиляции имеют фатальный недостаток: любая ссылка может быть нулевой, а вызов метода нулевого объекта приведет кNullPointerException. и так,

  • toUppercase()Может быть произвольноStringВызов объекта. Пока неStringНулевой.
  • read()Может быть произвольноInputStreamВызов объекта. Пока неInputStreamНулевой.
  • toString()Может быть произвольноObjectВызов объекта. Пока неObjectНулевой.

Java — не единственный язык, вызывающий эту проблему; многие другие системы типов имеют те же недостатки, включая, конечно, язык AGOL W.

В этих языках NULL выходит за рамки проверки типов. Он незаметно превзошел проверку типа, дождался времени выполнения и, наконец, выпустил сразу большое количество ошибок. NULL — это ничто, и в то же время это ничто.

2. NULL беспорядочный

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

Программисты на Java пишут о риске синдрома запястного канала

if (str == null || str.equals(«»)) {

}

И добавляем в C #String.IsNullOrEmptyОбычная грамматика

if (string.IsNullOrEmpty(str)) {

}

Черт!

Каждый раз, когда вы пишете код, который путает пустые строки с пустыми строками, команда Guava плачет. -Google Guava

хорошо сказано. Но когда ваша система типов (например, Java или C #) допускает NULL везде, вы не можете надежно исключить возможность NULL и неизбежно где-то запутаетесь.

Возможность нулевого повсюду вызвала такую ​​проблему, добавлена ​​Java 8@NonNullМарк, попробуй ретроспективно устранить этот дефект в системе его типов.

3. NULL — это особый случай.

Учитывая, что NULL не является значением, но также играет роль значения, NULL, естественно, становится предметом различных специальных методов обработки.

указатель

Например, рассмотрим следующий код C ++:

char c = ‘A’;

char *myChar = &c;

std::cout << *myChar << std::endl;

myChar Является char *, Что означает, что это указатель, то есть сохранить адрес памяти вcharв. Компилятор это проверит. Следовательно, следующий код недействителен:

char *myChar = 123; // compile error

std::cout << *myChar << std::endl;

Потому что123Нет гарантии, что это одинchar, Итак, компиляция не удалась. В любом случае, если поменять номер на0(0 является NULL в C ++), тогда его можно скомпилировать с помощью:

char *myChar = 0;

std::cout << *myChar << std::endl; // runtime error

с 123То же самое, NULL на самом деле неcharадрес. Но на этот раз компилятор все еще позволяет его компилировать, потому что0(NULL) — особый случай.

Нить

Есть еще один особый случай, который встречается в символьной строке, оканчивающейся на NULL в языке C. Это немного отличается от других примеров, потому что здесь нет указателей или ссылок. Однако идея о том, что это не ценность, а также играет роль ценности, все еще существует, здесь нетcharНо это играетcharСуществуют в виде.

Строка C представляет собой последовательность байтов и заканчивается байтом NUL (0).

Следовательно, каждый символ строки C может быть любым из 256 байтов, кроме 0 (то есть символа NUL). Это не только делает длину строки линейной операцией времени; что еще хуже, это означает, что строки C не могут использоваться в ASCII или расширенном ASCII. Вместо этого они могут использоваться только для ASCIIZ, который обычно не используется.

Исключение одного символа NUL вызвало бесчисленное количество ошибок: странное поведение API, уязвимости безопасности и переполнение буфера.

NULL — это наихудшая ошибка в строках C; точнее, строка, заканчивающаяся на NUL, являетсяСамый дорогойОдин байтошибка。

4. NULL делает API плохим

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

Хранилище ключей и значений

Предположим, мы создаем класс Ruby, который будет действовать как хранилище значений ключей. Это может быть кеш, интерфейс для базы данных ключ-значение и т. Д. Создадим простой и универсальный API:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class Store

    ##

    # associate key with value

    #

    def set(key, value)

        ...

    end

    ##

    # get value associated with key, or return nil if there is no such key

    #

    def get(key)

        ...

    end

end

Мы можем представить подобные классы на многих языках (Python, JavaScript, Java, C # и т. Д.).

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

Чтобы повысить производительность, мы будем использовать локальное хранилище в качестве кеша для сопоставления имени человека с его номером телефона.

store = Store.new()

store.set(‘Bob’, ‘801-555-5555’)

store.get(‘Bob’) # returns ‘801-555-5555’, which is Bob’s number

store.get(‘Alice’) # returns nil, since it does not have Alice

Однако у некоторых людей нет номера телефона (т.е. их номер телефона равен нулю). Мы по-прежнему будем кэшировать эту информацию, поэтому нам не нужно ее пополнять позже.

store = Store.new()

store.set(‘Ted’, nil) # Ted has no phone number

store.get(‘Ted’) # returns nil, since Ted does not have a phone number

Но теперь это означает, что наши результаты неоднозначны! Это может означать:

  1. Этого человека нет в кеше (Алиса)
  2. Этот человек существует в кеше, но у него нет номера телефона (Том)

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

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

Используйте одинcontains()Способ ремонтаStoreКласс может быть полезным. Но это вводит повторные поиски, что приводит к снижению производительности и условиям гонки.

Двойные неприятности

У JavaScript такая же проблема, но она возникает вКаждый объект

Если атрибуты объекта не существуют, JS вернет значение, указывающее, что у объекта отсутствуют атрибуты. Разработчики JavaScript выбрали это значение равным нулю.

Они беспокоятся о том, когда свойство существует и для него установлено значение null. «Талант» в том, что JavaScript добавляет undefined, чтобы отличать свойства с нулевыми значениями от несуществующих свойств.

Но что, если свойство существует и его значение не определено? Странно то, что JavaScript остановился на этом и не предоставил «super undefined».

JavaScript предлагает не только одну, но и две формы NULL.

5. NULL ухудшает неправильные языковые решения

Java незаметно преобразует ссылки в основные типы. Добавление null делает ситуацию еще более странной.

Например, следующий код не компилируется:

int x = null; // compile error

Этот код компилируется и передается:

Integer i = null;

int x = i; // runtime error

Хотя он сообщит, когда код запускаетсяNullPointerException ошибка.

Обращение к нулевому методу члена достаточно плохо; еще хуже, если вы никогда не видели вызываемый метод.

6. NULL сложно отлаживать.

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

#include <iostream>

struct Foo {

    int x;

    void bar() {

        std::cout << «La la la» << std::endl;

    }

    void baz() {

        std::cout << x << std::endl;

    }

};

int main() {

    Foo *foo = NULL;

    foo->bar(); // okay

    foo->baz(); // crash

}

Когда я использую gcc для компиляции приведенного выше кода, первый вызов выполняется успешно, а второй — нет.

Почему?foo->bar()Это известно во время компиляции, поэтому компилятор избегает поиска в виртуальной таблице во время выполнения и преобразует его в статический вызов, аналогичныйFoo_bar(foo), Возьмите это как первый параметр. Потому чтоbarНет косвенной ссылки на указатель NULL, поэтому он работает успешно. НоbazИмеется ссылка на указатель NULL, который вызывает segfault.

Но мы будемbarСтаньте виртуальной функцией. Это означает, что его реализация может быть отменена подклассом.

    ...

    virtual void bar() {

    ...

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

int main() {

    Foo *foo = NULL;

    foo->bar(); // crash

    foo->baz();

}

NULL сделалmainДля программистов функций отладка этого кода становится очень сложной и неинтуитивной.

Действительно, ссылка на NULL не определена в стандарте C ++, поэтому технически мы не должнылюбая ситуацияУдивлен. Кроме того, это непатологический, распространенный, очень простой и реальный пример.Этот пример является одним из многих примеров, когда NULL на практике непостоянен.

7. NULL нельзя комбинировать

Языки программирования построены на возможности компоновки: способности применять одну абстракцию к другой. Это может быть наиболее важной особенностью любого языка, библиотеки, фреймворка, модели, API или шаблона проектирования: возможность использовать другие функции ортогонально.

Фактически, компоновка — действительно основная проблема, стоящая за многими из этих проблем. Например,StoreНет возможности компоновки между API, возвращающим nil несуществующему значению, и сохранением nil для несуществующего телефонного номера.

Для C #NullableЧтобы разобраться с некоторыми проблемами, связанными с NULL. Вы можете включить в тип необязательность (пустоту).

int a = 1;     // integer

int? b = 2;    // optional integer that exists

int? c = null; // optional integer that does not exist

Но это вызвало серьезный недостаток, то естьNullableНе применимо ни к какомуT. Применяется только к непустымT. Например, это не сделаетStoreПроблема исправлена ​​любым способом.

  1. Прежде всего stringМожет быть пустым; вы не можете создать непустойstring
  2. Даже еслиstringНе пусто, поэтому создайтеstring? Может быть, но все равно нельзя исключить неоднозначность нынешней ситуации. Нет string??

решение

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

не так! У вас может быть полный язык программирования без NULL. Проблема с NULL — это нечисловое значение, дозорный, особый случай, который концентрируется на всем остальном.

Вместо этого нам нужно, чтобы сущность содержала некоторую информацию о (1) содержит ли она значение и (2) содержащееся значение, если есть содержащееся значение. И эта сущность должна уметь «содержать» любой тип. Это идея Haskell’s Maybe, Java’s Optional, Swift’s Optional и т. Д.

Например, в ScalaSome[T]Сохранить одинTЗначение типа.NoneНет никакой ценности. Оба этиOption[T]Подтипы этих двух подтипов могут иметь значение или не иметь значения.

Читатели, не знакомые с Maybes / Options, могут подумать, что мы заменили одну форму (NULL) другой формой (None). Но есть одно отличие — обнаружить его непросто, но оно очень важно.

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

В динамически типизированном языке вы не можете спутать использование Maybes / Options с содержащимися значениями.

Вернемся к предыдущемуStore, Но на этот раз можно использовать рубин. Если есть значение, тоStoreКласс возвращается со значениемSome, В противном случае вернутьNone. Для телефонных номеровSomeЭто номер телефона,NoneУказывает на отсутствие номера телефона. Так что естьДва уровня присутствия / отсутствия:Внешний MaybeУказывает, что он существует вStoreВ; внутреннийMaybeУказывает номер телефона, соответствующий этому имени. Мы успешно объединили несколькоMaybe, Это то, что мы не можем сделать с nil.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

cache = Store.new()

cache.set(‘Bob’, Some(‘801-555-5555’))

cache.set(‘Tom’, None())

bob_phone = cache.get(‘Bob’)

bob_phone.is_some # true, Bob is in cache

bob_phone.get.is_some # true, Bob has a phone number

bob_phone.get.get # ‘801-555-5555’

alice_phone = cache.get(‘Alice’)

alice_phone.is_some # false, Alice is not in cache

tom_phone = cache.get(‘Tom’)

tom_phone.is_some # true, Tom is in cache

tom_phone.get.is_some #false, Tom does not have a phone number

Существенная разница в том, чтоБольше не существует союза между NULL и любым другим статически типизированным или динамически предполагаемым типом., Больше не существует объединения между существующим значением и несуществующим значением.

Используйте Maybes / Опции

Давайте продолжим обсуждение кода без NULL. Предположим, в Java 8+ у нас есть целое число, оно может существовать, а может и не существовать, и если оно существует, мы его распечатываем.

Optional<Integer> option = ...

if (option.isPresent()) {

   doubled = System.out.println(option.get());

}

Замечательно. Но большая частьMaybe/OptionalРеализация, включая Java, поддерживает более практичный метод:

option.ifPresent(x -> System.out.println(x));

// or option.ifPresent(System.out::println)

Этот практический метод не только более краткий, но и более безопасный. Необходимо помнить, что если это значение не существует, тоoption.get()Произойдет ошибка. В предыдущем примереget()Получите одинifЗащита. В этом примереifPresent()Это полностью устраняет наши опасенияget()Необходимость. Это делает код явно свободным от ошибок, а не без ошибок.

Параметры можно рассматривать как набор с максимальным значением 1. Например, если есть значение, то мы можем умножить его на 2, в противном случае оставить его пустым.

При желании мы можем выполнить операцию, которая возвращает необязательное значение и стремится «сгладить» результат.

option.flatMap(x -> methodReturningOptional(x))

Если такового не существует, мы можем указать значение по умолчанию:

В целом,Maybe/OptionРеальная стоимость

  1. Избавьтесь от небезопасных предположений о существовании и несуществовании ценностей.
  2. Управляйте дополнительными данными проще и безопаснее
  3. Явно заявляйте о любых предположениях о небезопасном существовании (например,.get()Метод)

Не быть NULL!

NULL — ужасный недостаток дизайна, постоянная и неизмеримая боль. Лишь немногим языкам удается избежать его ужаса.

Если вы все же выберете язык с NULL, то, по крайней мере, сознательно избегайте этой неприятности в своем собственном коде и используйте эквивалентMaybe/Option

NULL в распространенных языках:

«Балл» определяется на основании следующих критериев:

редактировать

счет

Не относитесь слишком серьезно к «оценкам» в приведенной выше таблице. Настоящая проблема состоит в том, чтобы суммировать статус NULL на разных языках и представить альтернативы NULL, а не ранжировать часто используемые языки.

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

  • Пример: Haskell’sForeign.Ptr.nullPtrОн используется в FFI (интерфейсе внешних функций) для маршалинга значений в Haskell и обратно.
  • Пример: SwiftUnsafePointerДолжен быть сunsafeUnwrapИли же!использовать вместе.
  • Контрпример: Scala, несмотря на то, что обычно избегает null, по-прежнему обрабатывает null, как Java, для улучшения взаимодействия.val x: String = null

Когда NULL ОК?

Стоит отметить, что при сокращении циклов ЦП специальное значение того же размера, например 0 или NULL, может быть очень полезным, жертвуя качеством кода на производительность. Когда это действительно важно, это удобно для низкоуровневых языков, таких как C, но на самом деле стоит оставить все как есть.

Настоящая проблема

Более распространенная проблема с NULL — это контрольные значения: эти значения такие же, как и другие значения, но имеют совершенно другое значение. Из indexOfХорошим примером является возврат индекса целого числа или целого числа -1. Другой пример — строки, оканчивающиеся на NULL. В этой статье основное внимание уделяется NULL, что придает его универсальность и реальное влияние, но так же, как Саурон — всего лишь слуга Моргота, NULL — это просто форма базовой проблемы дозорного устройства.

InfoQ Homepage

Presentations

Null References: The Billion Dollar Mistake

Summary

Tony Hoare introduced Null references in ALGOL W back in 1965 «simply because it was so easy to implement», says Mr. Hoare. He talks about that decision considering it «my billion-dollar mistake».

Bio

Sir Charles Antony Richard Hoare, commonly known as Tony Hoare, is a British computer scientist, probably best known for the development in 1960, at age 26, of Quicksort. He also developed Hoare logic, the formal language Communicating Sequential Processes (CSP), and inspired the Occam programming language.

About the conference

QCon is a conference that is organized by the community, for the community.The result is a high quality conference experience where a tremendous amount of attention and investment has gone into having the best content on the most important topics presented by the leaders in our community. QCon is designed with the technical depth and enterprise focus of interest to technical team leads, architects, and project managers.

INFOQ EVENTS


  • March 27-29, London, or Online

    QCon London 2023

    Learn how to solve complex software engineering and leadership challenges. Attend in-person, or online.
    Save your spot now!

  • QCon New York 2023
    June 13-15, In-person & Online

    QCon New York 2023

    Solve your challenges with valuable insights from senior software developers applying the latest trends and practices. Attend in-person, or online.
    Register now!

Key Takeaways

  • Null references have historically been a bad idea
  • Early compilers provided opt-out switches for run-time checks, at the expense of correctness
  • Programming language designers should be responsible for the errors in programs written in that language
  • Customer requests and markets may not ask for what’s good for them; they may need regulation to build the market
  • If the billion dollar mistake was the null pointer, the C gets function is a multi-billion dollar mistake that created the opportunity for malware and viruses to thrive

Show notes

  • 00:45 Thesis: historically, null references have been a bad idea.
  • 02:15 Null references were created in 1964 — how much have they cost? Less or more than a billion dollars?
  • 03:20 Whilst we don’t know, the amount is probably in the order of an (American) billion — more than a tenth of a billon, less than ten billion.

History of programming languages

  • 03:35 A little on the history of the idea. Tony started as a programmer with Elliot’s [Ed: Elliot Brothers, London Ltd] in 1960, and was asked to design a new programming language.
  • 04:10 In the library was a 23-page booklet entitled «Report on the international language ALGOL60»;, edited by Peter Naur.
  • 04:30 Used as a basis for the new language, but left out the complicated parts such as «if»; and «then»;.
  • 05:00 Most software was still written in machine code (including the complier).
  • 05:25 Most assembly was simple enough to understand that when it went wrong, it could be diagnosed by following through to find out what the fault was.

Towards a high level language

  • 05:40 Using a high level language meant you couldn’t step through the machine code.
  • 05:50 The Elliot’s machine had 4096 locations, with a length of 4 7/8 bytes long (39 bits), although other machines had different sizes (IBM’s had 36 bits.
  • 06:30 To shield customers from implementation details, customers were told the errors in terms of the high level programming language, instead of a hexadecimal core dump.
  • 07:10 In order to implement error messages, an array had a check to verify whether its reference was in the bounds.
  • 08:00 Adding checks to arrays added space and time to the program; on Tony’s first machine it ran at less than 2k operations per second (500 micro seconds per operation, and two such tests for each array bounds).
  • 08:40 No undetected array errors, and customers didn’t know they could trade off safety for speed.
  • 09:30 The Java language has, after 30 years, decided to replicate the decision to bounds checking arrays. [Ed: other languages, like Python, handle this as well].

Record oriented programming

  • 10:20 Introduced the concept of an object, which could be referred to with a pointer.
  • 10:30 With pointers, it is possible to wreak havoc with the program you are trying to test [Ed: this is the single biggest cause of security failures in modern day code].
  • 10:55 If a floating point value or integer is used as a pointer accidentally, and the value it is pointing to is updated, then it will just as likely update the program which may then crash or cause problems now or in the future. [Ed: these days, virtual memory and page mapping takes away some of the problems about editing program code, but these weren’t present in the computers of that era.]
  • 12:00 As a given, when invoking a function with a pointer required the type of the pointer to be declared.
  • 13:30 The type of the program can be compile time checked from the static types.
  • 13:45 Many years later Tony discovered that some of these ideas had been integrated for the first time, although previous examples came from both Doug Rossier’s Plex and Simula.

Records avoid subscript errors

  • 14:35 The great thing about record handling is that you don’t need to have a subscript error, because you cannot construct a pointer that points to something that doesn’t exist, and a whole set of errors cannot occur and do not need to be checked at run-time.
  • 15:50 Later, we asked the customers whether they wanted the option to be able turn off the type checking in production. It’s a bit like wearing a life jacket when you are practicing drills, but then taking it off the ship was sinking. The customers decided to not switch off the type checking.
  • 17:00 We produced a compiler that would translate Fortran programs to Algol programs. It was a disaster, and no Fortran user would use it.
  • 18:00 The reason that they couldn’t use it was because they couldn’t use any of their programs. Within a few milliseconds of running it would come up with a subscript error. The error wasn’t wanted as they just wanted the code to run.

Type checking as standard

  • 19:00 Things have changed a bit — mainstream programming languages like Java now have subscript checking as standard, type-checked object oriented programming.
  • 19:30 And then I went and invented the null pointer. You either have to check every reference, or you risk disaster.
  • 19:45 Fortran programmers preferred to risk disaster; in fact, experience disaster, rather than check subscripts.
  • 20:00 I didn’t know it a the time, but my friend Edsger Dijkstra thought the null reference was a bad idea. He said:
  • 20:20 «If you have a null reference, then every bachelor who you represent in your object structure will seem to be married polyamocursly to the same person Null».
  • 20:55 It brings back the same question whether you want to run your code quickly (without checks) or safely (with checks).

Disjoint unions and discrimination test

  • 21:10 I did know there was a solution based on the idea of discrimination of objects belong to a disjoint union class; that is, two sets in which there are no members in common. For example a Vehicle class that has subtypes Car and Bus; the Car may have a luggage carrying capacity property while the Bus has a person carrying capacity. You would then have a discrimination test and do different operations based on whether it was a Bus or a Car.
  • 23:40 The size of the program grows with the number of discrimination clauses and number of types. This allows null to be represented as a different class, which can then be passed in to functions.
  • 24:30 The types of the pointer could then be implemented as a union of either a pointer to the null type, or a pointer to the type.
  • 25:20 This leads to implementation problems; what happens if you assume that a pointer is a Bus but change that pointer to a Car instead?
  • 25:55 One of the things you want is to be able to know in a high level language is that when it is created, all of its data structure is initialised. In this case, a null reference can be used to indicate that the data is missing or not known at this time. In fact, it’s the only thing that can be assigned if you have a pointer to a particular type.
  • 26:35 If you don’t want to use null, you have to implement a sublanguage for representing how to initialise objects of the right type. If the data structure is a tree-based representation, this is achievable if you create the leaves first because they can be fully created.
  • 27:10 It isn’t possible to create a cyclic structure using this technique; if there’s a cycle in the data structure you can start with a null pointer and then assign it once the rest of the cycle has been completed.

Introducing null

  • 27:40 This led me to suggest that the null value is a member of every type, and a null check is required on every use of that reference variable, and it may be perhaps a billion dollar mistake.
  • 28:00 Modern languages such as C# or Spec# and even Java are introducing the idea of non-null reference parameters, and compile time checking which verifies that they cannot possibly have null values.
  • 28:50 The issues of overloading and inheritance make it a lot more difficult to do these when null references were originally created.
  • 29:20 The movement must have been made based on the fact that null references were an expensive mistake.

Programming languages should be responsible for their users

  • 30:20 A programming language designer should be responsible for the mistakes made by programmers using the language. It is a serious activity; not one that should be given to programmers with 9 months experience with assembly; they should have a strong scientific basis, a good deal of ingenuity and invention and control of detail, and a clear objective that the programs written by people using the language would be correct. free of obvious errors and free of syntactical traps.
  • 31:40 This was the idea that led me to the idea of using proof and formal verification of programs as logical and mathematical models, is a method of conducting research into the design of good programming languages. I wasn’t too optimistic in 1969 would actually be using proofs to guarantee correctness of programs.
  • 32:20 By looking at the programming language and whether programs written would be possible to prove the programs written in the language gives an objective measure of how easy it would be to verify the program later. If the understanding of applying a rule locally has to depend on global knowledge of the program then you haven’t done a good job in creating the programming language, and you don’t need your customers to tell you that.
  • 33:30 In fact customers don’t tell you — it’s very easy to persuade your customers that anything that goes wrong is their fault rather than yours.
  • 33:40 I rejected that — programming language design is a serious scientific engineering activity, and we should begin to take responsibility for the mistakes that our users make.

Designing for safety

  • 33:55 It’s beginning to happen again — the Java programming language and its successors have all used avoidance of error as one of the criteria in the detail ed design of new features of the language, and I’m delighted to give them a great deal of credit for that — but it is only one criteria, and it is only one.
  • 34:35 The most important criteria is backwards compatibility of everything that has gone before, with the millions or billions lines of code that have been written.
  • 34:55 Every commercial language has to make concessions for commercial and historical reasons; but gradually, ideas change, programmers get more interested in provable correctness; production techniques, languages, checkers, analytic tools, test case generators and so on that are going to help them get their programs correct.

Safe at any speed?

  • 35:40 The analogy that I draw is with agricultural pollution and vehicle security. When Ralph Nader first started publishing «Unsafe at any speed», what he was saying had no connection with the marketplace — customers were not asking for reliability or safety as one of their vehicles.
  • 36:20 But gradually, customers started to demand reliability and safety, with the aid of law making and legal constraints requiring basic levels of safety to be included in every vehicle sold.
  • 36:50 There is a possibility that the marketplace will move the reliability of programs and the language in which they&’re expressed.
  • 37:15 For many professional engineers, they do have ideals and do pursue them in preference to not pursuing them whenever the opportunity arises. The commercial imperative that requires greater attention paid to the formal correctness of the programs is the virus.
  • 37:50 The virus (or malware, or worm) does dreadful things by reaching the parts of the program that it doesn’t usually reach. It is no longer applicable to test the cases that are likely to arise, the virus will attack the places that are not likely to arise, and so need just the same level of testing.
  • 38:35 It forces you to get the while program correct, not just the ones that will be used by customers, the code that will be used by viruses needs to be checked too.
  • 38:45 And that can’t be done by testing, it has to be done by analysis.
  • 38:55 Analysis of the source code, type-checking techniques are the simplest, but more sophisticated reasoning techniques are being used to high volume code to check that it doesn’t contain any naughty things like null reference dereferencing.

Introduction of the virus

  • 39:30 So if I am responsible for a billion dollar mistake; and I bring it up because other designers are much more responsible.
  • 39:40 The designers of C — one can definitely quantify. The buffer overflow is a direct result of the C language gets fnction that doesn’t check the bounds of the string input. That allowed the early viruses to get in by overwriting the return values of the code.
  • 40:10 These simple viruses taught the world how to write malware. Without this very simple entry point, it is quite possible that nobody would ever have thought to look for the more subtle kind of thing which are now being exploited every day by people who are now motivated, skilled, and whose profession and income it is to write botware, malware.
  • 40:45 If it hadn’t been for the gets routine in C, we might have had no malware.
  • 40:55 Now one virus — the CodeRed virus — was estimated to have cost the world economy 4 billion dollars, because it brought down all the networks, and the interruption to business and all the ordinary banking, other business was estimated to cost that amount. There was another one later as well.
  • 41:30 And that was more than the Millennium bug, which was estimated a little less than 4 billion dollars.

Companies mentioned

  • Elliot Brothers (London) Ltd

People mentioned

  • Peter Naur
  • Doug Rossier
  • Edsger Dijkstra
  • Ralph Nader

Languages mentioned

  • Algol60
  • Occam
  • Plex
  • Simula
  • Fortran
  • C#
  • Spec#
  • C

Products mentioned

  • ACM Turing Award speech

See more presentations with show notes

Recorded at:

Aug 25, 2009

[Обновление 2015-10-31] Дополнительная трансляция, измененная из StackOverflowПочтовыйПараграф:

Чашка! В нашей компании фамилия сотрудника Null.При использовании его фамилии в качестве термина запроса все приложения запросов сотрудников вылетали из строя! Что я должен делать?

В 1965 году кто-то допустил худшую ошибку в области информатики. Ошибка уродливее, чем обратная косая черта в Windows, более странная, чем ===, более распространенная, чем PHP, более неудачная, чем CORS, и более тревожная, чем дженерики Java. XMLHttpRequest, более сложный для понимания, чем препроцессор C, более подверженный фрагментации, чем MongoDB, и более прискорбный, чем UTF-16.

«Я называю нулевую ссылку своей ошибкой на миллиард долларов. Она была изобретена в 1965 году, когда я разработал первую всеобъемлющую систему ссылочных типов на объектно-ориентированном языке (АЛГОЛ W). Моя цель — гарантировать, что использование всех ссылок абсолютно безопасно, компилятор проверит автоматически. Но я не смог устоять перед соблазном добавить нулевые ссылки только потому, что это очень легко реализовать. Это вызвало бесчисленное количество ошибок, уязвимостей и систем. Авария могла привести к убыткам в миллиарды долларов в следующий раз. 40 лет. В последние годы люди начали использовать различные программы анализа программ, такие как Microsoft PREfix и PREfast, чтобы проверять ссылки и предупреждать, если существует риск ненулевого значения. Новые языки программирования, такие как Spec #, имеют ввел объявление ненулевых ссылок. Это решение, которое я отверг в 1965 году »-« Нулевые ссылки: ошибка на миллиард долларов »Тони Хоар, лауреат премии Тьюринга

В ознаменование 50-летия нулевой ошибки мистера Хора в этой статье объясняется, что такое null, почему это так ужасно и как этого избежать.

Проще говоря: NULL — это значение, которое не является значением. Вот и проблема.

Эта проблема усугубилась в самом популярном языке всех времен, и теперь у него много имен: NULL, nil, null, None, Nothing, Nil и nullptr. У каждого языка есть свои нюансы.

Некоторые из проблем, вызванных NULL, связаны только с конкретным языком, в то время как другие универсальны; некоторые — просто разные аспекты проблемы.

NULL…

  1. Тип Subversion
  2. Это грязно
  3. Это особый случай
  4. Сделать API хуже
  5. Сделать неправильные языковые решения хуже
  6. Сложно отлаживать
  7. Не сочетается

1. Тип подрывной деятельности NULL

Статически типизированные языки могут проверять использование типов в программе без фактического выполнения программы и обеспечивать определенные гарантии поведения программы.

Например, в Java, если я напишуx.toUppercase(), Компилятор проверитx тип. в случае x Является String, То проверка типа прошла успешно; еслиx Является Socket, Тогда проверка типа не удалась.

При написании большого и сложного программного обеспечения статическая проверка типов является мощным инструментом. Но для Java эти отличные проверки во время компиляции имеют фатальный недостаток: любая ссылка может быть нулевой, а вызов метода нулевого объекта приведет кNullPointerException. и так,

  • toUppercase()Может быть произвольноStringВызов объекта. Пока неStringНулевой.
  • read()Может быть произвольноInputStreamВызов объекта. Пока неInputStreamНулевой.
  • toString()Может быть произвольноObjectВызов объекта. Пока неObjectНулевой.

Java — не единственный язык, вызывающий эту проблему; многие другие системы типов имеют те же недостатки, включая, конечно, язык AGOL W.

В этих языках NULL выходит за рамки проверки типов. Он незаметно превзошел проверку типа, дождался времени выполнения и, наконец, выпустил сразу большое количество ошибок. NULL — это ничто, и в то же время это ничто.

2. NULL беспорядочный

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

Программисты на Java пишут о риске синдрома запястного канала

if (str == null || str.equals(«»)) {

}

И добавляем в C #String.IsNullOrEmptyОбычная грамматика

if (string.IsNullOrEmpty(str)) {

}

Черт!

Каждый раз, когда вы пишете код, который путает пустые строки с пустыми строками, команда Guava плачет. -Google Guava

хорошо сказано. Но когда ваша система типов (например, Java или C #) допускает NULL везде, вы не можете надежно исключить возможность NULL и неизбежно где-то запутаетесь.

Возможность нулевого повсюду вызвала такую ​​проблему, добавлена ​​Java 8@NonNullМарк, попробуй ретроспективно устранить этот дефект в системе его типов.

3. NULL — это особый случай.

Учитывая, что NULL не является значением, но также играет роль значения, NULL, естественно, становится предметом различных специальных методов обработки.

указатель

Например, рассмотрим следующий код C ++:

char c = ‘A’;

char *myChar = &c;

std::cout << *myChar << std::endl;

myChar Является char *, Что означает, что это указатель, то есть сохранить адрес памяти вcharв. Компилятор это проверит. Следовательно, следующий код недействителен:

char *myChar = 123; // compile error

std::cout << *myChar << std::endl;

Потому что123Нет гарантии, что это одинchar, Итак, компиляция не удалась. В любом случае, если поменять номер на0(0 является NULL в C ++), тогда его можно скомпилировать с помощью:

char *myChar = 0;

std::cout << *myChar << std::endl; // runtime error

с 123То же самое, NULL на самом деле неcharадрес. Но на этот раз компилятор все еще позволяет его компилировать, потому что0(NULL) — особый случай.

Нить

Есть еще один особый случай, который встречается в символьной строке, оканчивающейся на NULL в языке C. Это немного отличается от других примеров, потому что здесь нет указателей или ссылок. Однако идея о том, что это не ценность, а также играет роль ценности, все еще существует, здесь нетcharНо это играетcharСуществуют в виде.

Строка C представляет собой последовательность байтов и заканчивается байтом NUL (0).

Следовательно, каждый символ строки C может быть любым из 256 байтов, кроме 0 (то есть символа NUL). Это не только делает длину строки линейной операцией времени; что еще хуже, это означает, что строки C не могут использоваться в ASCII или расширенном ASCII. Вместо этого они могут использоваться только для ASCIIZ, который обычно не используется.

Исключение одного символа NUL вызвало бесчисленное количество ошибок: странное поведение API, уязвимости безопасности и переполнение буфера.

NULL — это наихудшая ошибка в строках C; точнее, строка, заканчивающаяся на NUL, являетсяСамый дорогойОдин байтошибка。

4. NULL делает API плохим

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

Хранилище ключей и значений

Предположим, мы создаем класс Ruby, который будет действовать как хранилище значений ключей. Это может быть кеш, интерфейс для базы данных ключ-значение и т. Д. Создадим простой и универсальный API:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class Store

    ##

    # associate key with value

    #

    def set(key, value)

        ...

    end

    ##

    # get value associated with key, or return nil if there is no such key

    #

    def get(key)

        ...

    end

end

Мы можем представить подобные классы на многих языках (Python, JavaScript, Java, C # и т. Д.).

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

Чтобы повысить производительность, мы будем использовать локальное хранилище в качестве кеша для сопоставления имени человека с его номером телефона.

store = Store.new()

store.set(‘Bob’, ‘801-555-5555’)

store.get(‘Bob’) # returns ‘801-555-5555’, which is Bob’s number

store.get(‘Alice’) # returns nil, since it does not have Alice

Однако у некоторых людей нет номера телефона (т.е. их номер телефона равен нулю). Мы по-прежнему будем кэшировать эту информацию, поэтому нам не нужно ее пополнять позже.

store = Store.new()

store.set(‘Ted’, nil) # Ted has no phone number

store.get(‘Ted’) # returns nil, since Ted does not have a phone number

Но теперь это означает, что наши результаты неоднозначны! Это может означать:

  1. Этого человека нет в кеше (Алиса)
  2. Этот человек существует в кеше, но у него нет номера телефона (Том)

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

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

Используйте одинcontains()Способ ремонтаStoreКласс может быть полезным. Но это вводит повторные поиски, что приводит к снижению производительности и условиям гонки.

Двойные неприятности

У JavaScript такая же проблема, но она возникает вКаждый объект

Если атрибуты объекта не существуют, JS вернет значение, указывающее, что у объекта отсутствуют атрибуты. Разработчики JavaScript выбрали это значение равным нулю.

Они беспокоятся о том, когда свойство существует и для него установлено значение null. «Талант» в том, что JavaScript добавляет undefined, чтобы отличать свойства с нулевыми значениями от несуществующих свойств.

Но что, если свойство существует и его значение не определено? Странно то, что JavaScript остановился на этом и не предоставил «super undefined».

JavaScript предлагает не только одну, но и две формы NULL.

5. NULL ухудшает неправильные языковые решения

Java незаметно преобразует ссылки в основные типы. Добавление null делает ситуацию еще более странной.

Например, следующий код не компилируется:

int x = null; // compile error

Этот код компилируется и передается:

Integer i = null;

int x = i; // runtime error

Хотя он сообщит, когда код запускаетсяNullPointerException ошибка.

Обращение к нулевому методу члена достаточно плохо; еще хуже, если вы никогда не видели вызываемый метод.

6. NULL сложно отлаживать.

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

#include <iostream>

struct Foo {

    int x;

    void bar() {

        std::cout << «La la la» << std::endl;

    }

    void baz() {

        std::cout << x << std::endl;

    }

};

int main() {

    Foo *foo = NULL;

    foo->bar(); // okay

    foo->baz(); // crash

}

Когда я использую gcc для компиляции приведенного выше кода, первый вызов выполняется успешно, а второй — нет.

Почему?foo->bar()Это известно во время компиляции, поэтому компилятор избегает поиска в виртуальной таблице во время выполнения и преобразует его в статический вызов, аналогичныйFoo_bar(foo), Возьмите это как первый параметр. Потому чтоbarНет косвенной ссылки на указатель NULL, поэтому он работает успешно. НоbazИмеется ссылка на указатель NULL, который вызывает segfault.

Но мы будемbarСтаньте виртуальной функцией. Это означает, что его реализация может быть отменена подклассом.

    ...

    virtual void bar() {

    ...

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

int main() {

    Foo *foo = NULL;

    foo->bar(); // crash

    foo->baz();

}

NULL сделалmainДля программистов функций отладка этого кода становится очень сложной и неинтуитивной.

Действительно, ссылка на NULL не определена в стандарте C ++, поэтому технически мы не должнылюбая ситуацияУдивлен. Кроме того, это непатологический, распространенный, очень простой и реальный пример.Этот пример является одним из многих примеров, когда NULL на практике непостоянен.

7. NULL нельзя комбинировать

Языки программирования построены на возможности компоновки: способности применять одну абстракцию к другой. Это может быть наиболее важной особенностью любого языка, библиотеки, фреймворка, модели, API или шаблона проектирования: возможность использовать другие функции ортогонально.

Фактически, компоновка — действительно основная проблема, стоящая за многими из этих проблем. Например,StoreНет возможности компоновки между API, возвращающим nil несуществующему значению, и сохранением nil для несуществующего телефонного номера.

Для C #NullableЧтобы разобраться с некоторыми проблемами, связанными с NULL. Вы можете включить в тип необязательность (пустоту).

int a = 1;     // integer

int? b = 2;    // optional integer that exists

int? c = null; // optional integer that does not exist

Но это вызвало серьезный недостаток, то естьNullableНе применимо ни к какомуT. Применяется только к непустымT. Например, это не сделаетStoreПроблема исправлена ​​любым способом.

  1. Прежде всего stringМожет быть пустым; вы не можете создать непустойstring
  2. Даже еслиstringНе пусто, поэтому создайтеstring? Может быть, но все равно нельзя исключить неоднозначность нынешней ситуации. Нет string??

решение

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

не так! У вас может быть полный язык программирования без NULL. Проблема с NULL — это нечисловое значение, дозорный, особый случай, который концентрируется на всем остальном.

Вместо этого нам нужно, чтобы сущность содержала некоторую информацию о (1) содержит ли она значение и (2) содержащееся значение, если есть содержащееся значение. И эта сущность должна уметь «содержать» любой тип. Это идея Haskell’s Maybe, Java’s Optional, Swift’s Optional и т. Д.

Например, в ScalaSome[T]Сохранить одинTЗначение типа.NoneНет никакой ценности. Оба этиOption[T]Подтипы этих двух подтипов могут иметь значение или не иметь значения.

Читатели, не знакомые с Maybes / Options, могут подумать, что мы заменили одну форму (NULL) другой формой (None). Но есть одно отличие — обнаружить его непросто, но оно очень важно.

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

В динамически типизированном языке вы не можете спутать использование Maybes / Options с содержащимися значениями.

Вернемся к предыдущемуStore, Но на этот раз можно использовать рубин. Если есть значение, тоStoreКласс возвращается со значениемSome, В противном случае вернутьNone. Для телефонных номеровSomeЭто номер телефона,NoneУказывает на отсутствие номера телефона. Так что естьДва уровня присутствия / отсутствия:Внешний MaybeУказывает, что он существует вStoreВ; внутреннийMaybeУказывает номер телефона, соответствующий этому имени. Мы успешно объединили несколькоMaybe, Это то, что мы не можем сделать с nil.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

cache = Store.new()

cache.set(‘Bob’, Some(‘801-555-5555’))

cache.set(‘Tom’, None())

bob_phone = cache.get(‘Bob’)

bob_phone.is_some # true, Bob is in cache

bob_phone.get.is_some # true, Bob has a phone number

bob_phone.get.get # ‘801-555-5555’

alice_phone = cache.get(‘Alice’)

alice_phone.is_some # false, Alice is not in cache

tom_phone = cache.get(‘Tom’)

tom_phone.is_some # true, Tom is in cache

tom_phone.get.is_some #false, Tom does not have a phone number

Существенная разница в том, чтоБольше не существует союза между NULL и любым другим статически типизированным или динамически предполагаемым типом., Больше не существует объединения между существующим значением и несуществующим значением.

Используйте Maybes / Опции

Давайте продолжим обсуждение кода без NULL. Предположим, в Java 8+ у нас есть целое число, оно может существовать, а может и не существовать, и если оно существует, мы его распечатываем.

Optional<Integer> option = ...

if (option.isPresent()) {

   doubled = System.out.println(option.get());

}

Замечательно. Но большая частьMaybe/OptionalРеализация, включая Java, поддерживает более практичный метод:

option.ifPresent(x -> System.out.println(x));

// or option.ifPresent(System.out::println)

Этот практический метод не только более краткий, но и более безопасный. Необходимо помнить, что если это значение не существует, тоoption.get()Произойдет ошибка. В предыдущем примереget()Получите одинifЗащита. В этом примереifPresent()Это полностью устраняет наши опасенияget()Необходимость. Это делает код явно свободным от ошибок, а не без ошибок.

Параметры можно рассматривать как набор с максимальным значением 1. Например, если есть значение, то мы можем умножить его на 2, в противном случае оставить его пустым.

При желании мы можем выполнить операцию, которая возвращает необязательное значение и стремится «сгладить» результат.

option.flatMap(x -> methodReturningOptional(x))

Если такового не существует, мы можем указать значение по умолчанию:

В целом,Maybe/OptionРеальная стоимость

  1. Избавьтесь от небезопасных предположений о существовании и несуществовании ценностей.
  2. Управляйте дополнительными данными проще и безопаснее
  3. Явно заявляйте о любых предположениях о небезопасном существовании (например,.get()Метод)

Не быть NULL!

NULL — ужасный недостаток дизайна, постоянная и неизмеримая боль. Лишь немногим языкам удается избежать его ужаса.

Если вы все же выберете язык с NULL, то, по крайней мере, сознательно избегайте этой неприятности в своем собственном коде и используйте эквивалентMaybe/Option

NULL в распространенных языках:

«Балл» определяется на основании следующих критериев:

редактировать

счет

Не относитесь слишком серьезно к «оценкам» в приведенной выше таблице. Настоящая проблема состоит в том, чтобы суммировать статус NULL на разных языках и представить альтернативы NULL, а не ранжировать часто используемые языки.

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

  • Пример: Haskell’sForeign.Ptr.nullPtrОн используется в FFI (интерфейсе внешних функций) для маршалинга значений в Haskell и обратно.
  • Пример: SwiftUnsafePointerДолжен быть сunsafeUnwrapИли же!использовать вместе.
  • Контрпример: Scala, несмотря на то, что обычно избегает null, по-прежнему обрабатывает null, как Java, для улучшения взаимодействия.val x: String = null

Когда NULL ОК?

Стоит отметить, что при сокращении циклов ЦП специальное значение того же размера, например 0 или NULL, может быть очень полезным, жертвуя качеством кода на производительность. Когда это действительно важно, это удобно для низкоуровневых языков, таких как C, но на самом деле стоит оставить все как есть.

Настоящая проблема

Более распространенная проблема с NULL — это контрольные значения: эти значения такие же, как и другие значения, но имеют совершенно другое значение. Из indexOfХорошим примером является возврат индекса целого числа или целого числа -1. Другой пример — строки, оканчивающиеся на NULL. В этой статье основное внимание уделяется NULL, что придает его универсальность и реальное влияние, но так же, как Саурон — всего лишь слуга Моргота, NULL — это просто форма базовой проблемы дозорного устройства.

Joker burning a huge pile of money

Tony Hoare, the creator of NULL, now refers to NULL as The Billion Dollar Mistake. Even though NULL Reference Exceptions continue to haunt our code to this day, we still choose to continue using it.

And for some reason JavaScript decided to double down on the problems with null by also creating undefined.

Today I would like to demonstrate a solution to this problem with the Maybe.

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. — Tony Hoare

Do not underestimate the problems of NULL

Before you have even finish reading this article… I can already sense it, your desire to hit PAGE DOWN, rush straight to the comment section and blast out a «but NULL is never a problem for ME». But please pause, slow down, read and contemplate.

8 of 10 errors from Top 10 JavaScript errors from 1000+ projects (and how to avoid them) are null and undefined problems. Eight. Out. Of. Ten.

To underestimate NULL is to be defeated by NULL.

Null Guards

Because of the problems null brings with it, we have to constantly guard our code from it. Unguarded code might look something like this:

const toUpper = string => string.toUpperCase()

This code is susceptible to NULL Reference Exceptions.

toUpper(null) //=> ​​Cannot read property 'toUpperCase' of null​​

So we are forced to guard against null.

const toUpper = string => {
  if (string != null) {
//    --------------
//                   
//                    null guard
    return string.toUpperCase()
  }
}

But this quickly becomes verbose as everywhere that may encounter null has to be guarded.

const toUpper = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.toUpperCase()
  }
}

const toLower = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.toLowerCase()
  }
}

const trim = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.trim()
  }
}

If we think about a values as having a one-to-many relationship with code that may access it, then it makes more sense to place the guards on the one and not on the many.

Nullable Types

The .NET Framework 2.0 introduced Nullable Types into the .NET language. This new Nullable value, could be set to null without the reference being null. This meant if x was a Nullable Type, you could still do things like x.HasValue and x.Value without getting a NullReferenceException.

int? x = null
if (x.HasValue)
{
    Console.WriteLine($"x is {x.Value}")
}
else
{
    Console.WriteLine("x does not have a value")
}

The Maybe

The Maybe is similar to a Nullable Type. The variable will always have a value, and that value might represent a null, but it will never be set to null.

For these examples, I’ll be using the Maybe from MojiScript. (Also checkout monet and Sanctuary, Folktale for other Maybes). Use the following import:

import { fromNullable } from "mojiscript/type/Maybe"

The Maybe is a union type of either a Just or a Nothing. Just contains a value and Nothing is well… nothing.

But now the value is all wrapped up inside of the Maybe. To access the value of a Maybe, you would have touse a map function. Fun to Google: map is what makes the Maybe type a Functor.

If you are getting that feeling that you have seen this somewhere before that is because this exactly how a Promise works. The difference is Promise uses then and Maybe uses Map.

const promise = Promise.resolve(888)
const maybe = Just(888)

promise.then(double)
maybe.map(double)

Same same but different.

const toUpper = string => string.toUpperCase()

Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing

Notice how in both cases above, the toUpper function no longer throws an Error. That is because we are no longer calling toUpper directly with a String, but instead mapping it with our Maybe.

If we convert all types within our application to use a Maybe, then all null guards are no longer necessary.

The null is now guarded in a single place, in the Maybe type, instead of being sprinkled throughout the application, wherever the value might be accessed.

The Maybe is a guard on the one instead of the many!

Neo vs many agent Smiths

Getting in and out of Maybes

But what about the times when we are not in control of the code, when we must send or receive a null value? Some examples might be 3rd party libraries that will return a null or libraries that will require passing null as an argument.

In these cases, we can convert a null value to a Maybe using fromNullable and we can convert back to a nullable value using fromMaybe.

import { fromMaybe, fromNullable } from "mojiscript/type/Maybe"

// converting nullable values to a Maybe
fromNullable(undefined) //=> Nothing
fromNullable(null) //=> Nothing
fromNullable(123) //=> Just (123)
fromNullable("abc") //=> Just ("abc")

// converting Maybe to a nullable type
fromMaybe(Just("abc")) //=> 'abc'
fromMaybe(Nothing) //=> null

You could als guard a single function like this:

const toUpper = string =>
  fromNullable(string).map(s => s.toUpperCase()).value

But that is a little verbose and it’s much better to expand the safety of the Maybe type to the entire application. Put the guards in place at the gateways in and out of your application, not individual functions.

One example could be using a Maybe in your Redux.

// username is a Maybe, initially set to Nothing.
const initalState = {
  username: Nothing
}

// your reducer is the gateway that ensures the value will always be a maybe.
const reducer = (state = initialState, { type, value }) =>
  type === 'SET_USERNAME'
    ? { ...state, username: fromNullable(value) }
    : state

// somewhere in your render
render() {
  const userBlock = this.props.username.map(username => <h1>{username}</h1>)
  const noUserBlock = <div>Anonymous</div>

  return (
    <div>
    {fromMaybe (noUserBlock) (userBlock)}
    </div>
  )
}

JavaScript Type Coercion

MojiScript’s Maybe can use JavaScript’s implicit and explicit coercion to it’s advantage.

Maybe can be implicity coerced into a String.

// coercing to a String
console.log("a" + Just("b") + "c") //=> 'abc'
console.log("a" + Nothing + "c") //=> 'ac'

Maybe can be explicity coerced into a Number.

Number(Just(888)) //=> 888
Number(Nothing) //=> 0

Maybe can even be stringified.

const data = {
  id: Nothing,
  name: Just("Joel")
}

JSON.stringify(data)
//=> {"id":null,"name":"Joel"}

Accessing Nested Objects

Let’s take a look at the common task of accessing nested objects.

We’ll use these objects. One is lacking an address, which can yield nulls. Gross.

const user1 = {
  id: 100,
  address: {
    address1: "123 Fake st",
    state: "CA"
  }
}

const user2 = {
  id: 101
}

These are common ways to access nested objects.

user1.address.state //=> 'CA'
user2.address.state //=> Error: Cannot read property 'state' of undefined

// short circuit
user2 && user2.address && user2.address.state //=> undefined

// Oliver Steel's Nested Object Pattern
((user2||{}).address||{}).state //=> undefined

Prettier seems to hate both of those techniques, turning them into unreadable junk.

Now let’s try accessing nested objects with a Maybe.

import { fromNullable } from 'mojiscript/type/Maybe'

const prop = prop => obj =>
  fromNullable(obj).flatMap(o => fromNullable(o[prop]))

Just(user1)
  .flatMap(prop('address))
  .flatMap(prop('state)) //=> Just ("CA")

Just(user2)
  .flatMap(prop('address))
  .flatMap(prop('address)) //=> Nothing

A lot of this boiler plate can be reduced with some helper methods.

import pathOr from 'mojiscript/object/PathOr'
import { fromNullable } from 'mojiscript/type/Maybe'

const getStateFromUser = obj =>
  fromNullable(pathOr (null) ([ 'address', 'state' ]) (obj))

Just(user1).map(getStateFromUser) //=> Just ("CA")
Just(user2).map(getStateFromUser) //=> Nothing

Decoupled map function

A Map can also be decoupled from Maybe. There are many libs that have a map function, like Ramda, but I’ll be using the one from MojiScript for this example.

import map from 'mojiscript/list/map'

const toUpper = string => string.toUpperCase()

Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing
import map from 'mojiscript/list/map'

const toUpper = string => string.toUpperCase()

map (toUpper) (Just ("abc")) //=> Just ('ABC')
map (toUpper) (Nothing) //=> Nothing

This was getting far too big for this section, so it has been broken out into it’s own article here: An introduction to MojiScript’s enhanced map

Heavy Lifting

Lifting is a technique to apply Applicatives to a function. In English that means we can use «normal» functions with our Maybes. Fun to Google: ap is what makes the Maybe type an Applicative.

This code will use liftA2, A for Applicative and 2 for the number of arguments in the function.

import liftA2 from "mojiscript/function/liftA2"
import Just from "mojiscript/type/Just"
import Nothing from "mojiscript/type/Nothing"

const add = x => y => x + y
const ladd = liftA2 (add)

add (123) (765) //=> 888

ladd (Just (123)) (Just (765)) //=> Just (888)
ladd (Nothing) (Just (765)) //=> Nothing
ladd (Just (123)) (Nothing) //=> Nothing

Some things to notice:

  • The function add is curried. You can use any curry function to do this for you.
  • add consists of 2 parameters. If it was 3, we would use liftA3.
  • All arguments must be a Just, otherwise Nothing is returned.

So now we do not have to modify our functions to understand the Maybe type, we can use map and also lift to apply the function to our Maybes.

Continue Learning: Functors, Applicatives, And Monads In Pictures does an incredible job of explaining this and more!

Maybe Function Decorator

There are times when you would like to guard a single function against NULL. That is where the maybe Function Decorator comes in handy.

const maybe = func => (...args) =>
  !args.length || args.some(x => x == null)
    ? null
    : func(...args)

Guard your functions against null with the maybe function decorator:

const toUpper = string => string.toUpperCase()
const maybeToUpper = maybe(toUpper)
maybeToUpper("abc") //=> 'ABC'
maybeToUpper(null) //=> null

Can also be written like this:

const toUpper = maybe(string => string.toUpperCase())

Learn more about Function Decorators:

  • Function decorators: Transforming callbacks into promises and back again
  • Functional JavaScript: Function Decorators Part 2

TC39 Optional Chaining for JavaScript

This is a good time to mention the TC39 Optional Chaining Proposal that is currently in Stage 1.

Optional Chaining will allow you to guard against null with a shorter syntax.

// without Optional Chaining
const toUpper = string => string && string.toUpperCase()

// with Optional Chaining
const toUpper = string => string?.toUpperCase()

Even with Optional Chaining, the guards are still on the many and not the one, but at least the syntax is short.

Wisdoms

  • To underestimate NULL is to be defeated by NULL.
  • 8 out of the 10 top 10 errors are NULL and undefined errors.
  • If we think about a values as having a one-to-many relationship with code that may access it, then it makes more sense to place the guards on the one and not on the many.
  • It is possible to completely eliminate an entire class of bugs (NULL Reference Exceptions) by eliminating null.
  • Having NULL Reference Exceptions in your code is a choice.

End

Have questions or comments? I’d love to hear them!

Hop over to the MojiScript Discord chat and say hi!

This turned out a little longer than I originally thought it would. But this is a subject that is hard to sum up into a single article.

You can also use the Maybe with MojiScript’s map. Read more about how awesome MojiScript’s map is here…

My articles are very Functional JavaScript heavy, if you need more FP, follow me here, or on Twitter @joelnet!

Cheers!

В мире Javascript и как с этим работать

Какие ошибки в мире программного обеспечения обходятся в миллиарды долларов?

  • По словам Тони Хоара, ошибка 2000 года, класс ошибок, связанных с хранением и форматированием данных календаря, обойдется чуть менее чем в 4 миллиарда долларов.
  • CodeRed Virus, компьютерный червь, внедрившийся в компании по всему миру, вывел из строя все сети. Прерывание бизнеса и всего обычного банковского дела обошлось мировой экономике в 4 миллиарда долларов.
  • Null – ошибочное изобретение британского ученого-компьютерщика Тони Хоара (наиболее известного благодаря своему алгоритму быстрой сортировки) в 1964 году, который изобрел нулевые ссылки. как его «ошибка на миллиард долларов».

Кто придумал «обнулить» ошибку на миллиард долларов и почему?

Я называю это своей ошибкой на миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я разрабатывал первую всеобъемлющую систему типов для ссылок в объектно-ориентированном языке (ALGOL W). Моя цель состояла в том, чтобы гарантировать, что любое использование ссылок должно быть абсолютно безопасным, с автоматической проверкой компилятором. Но я не мог устоять перед искушением добавить нулевую ссылку просто потому, что это было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили миллиарды долларов боли и ущерба за последние сорок лет. — Тони Хоар

Ссылка на нулевой указатель может быть плохой идеей. Сравнивая ссылку нулевого указателя с неразборчивым в связях прелюбодеем, он заметил, что нулевое присвоение для каждого холостяка, представленного в объектной структуре, «будет выглядеть полиаморно женатым на одном и том же человеке Null. — Эдсгар Джикстра

Что такое ошибка на миллиард долларов в контексте мира Javascript?

У нас есть два кандидата в мире Javascript, подпадающие под эту категорию:

1⃣️ Нет

Значение null записывается литералом: null. null не является идентификатором свойства глобального объекта, как может быть undefined. Вместо этого null выражает отсутствие идентификации, указывая на то, что переменная не указывает ни на какой объект. В API-интерфейсах null часто извлекается в месте, где объект можно ожидать, но объект не является релевантным.

2⃣️ Не определено

undefined — это свойство глобального объекта. То есть это переменная в глобальной области видимости. Начальное значение undefined — это примитивное значение undefined.

примитивные типы данных Javascript (ES2020),

  1. Boolean
  2. Null
  3. Undefined
  4. Number
  5. String
  6. BigInt
  7. Symbol

Null и Undefined в Javascript называются «нулевыми» (ложными) значениями.

Ложные значения: Undefined, null, 0, NaN, empty string‘’, false

Нулевой или неопределенный

Несмотря на то, что поведение обоих значений является ложным, если кто-то думает Null vs Undefined как Declared vs Undeclared, это не совсем так!

Undefined может быть как объявленным, так и необъявленным.

А как насчет Null?

У Null есть свои проблемы, с которыми нужно разобраться… отлично! Давайте посмотрим на это,

Ну, typeof null == “object” — это ошибка 25-летней давности, начиная с первой версии Javascript.

В первой версии JavaScript значения хранились в 32-битных единицах, которые состояли из небольшого тега типа (1–3 бита) и фактических данных значения. Теги типа хранились в младших битах единиц. Их было пятеро:

  • 000: object. Данные являются ссылкой на объект.
  • 001: int. Данные представляют собой 31-битное целое число со знаком.
  • 010: double. Данные являются ссылкой на двойное число с плавающей запятой.
  • 100: string. Данные являются ссылкой на строку.
  • 110: boolean. Данные являются логическими.

Из исходного кода jsapi.h, (ссылка)

#define JSVAL_OBJECT      0x0     /* untagged reference to object */
#define JSVAL_INT         0x1     /* tagged 31-bit integer value */
#define JSVAL_DOUBLE      0x2     /* tagged reference to double */
#define JSVAL_STRING      0x4     /* tagged reference to string */
#define JSVAL_BOOLEAN     0x6     /* tagged boolean value */

Два значения были особенными:

  • undefined(JSVAL_VOID), представляет собой целое минус (-) JSVAL_INT_POW2 (30), то есть число вне целочисленного диапазона
  • null(JSVAL_NULL) — это указатель NULL машинного кода, тег типа объекта плюс ссылка, равная нулю(OBJECT_TO_JSVAL(0)).
#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
#define JSVAL_NULL              OBJECT_TO_JSVAL(0)
#define JSVAL_ZERO              INT_TO_JSVAL(0)
#define JSVAL_ONE               INT_TO_JSVAL(1)
#define JSVAL_FALSE             BOOLEAN_TO_JSVAL(JS_FALSE)
#define JSVAL_TRUE              BOOLEAN_TO_JSVAL(JS_TRUE)

Теперь, когда рассмотрел его тег типа, а тег типа сказал объект. («источник»)

  1. Строка № 10, сначала проверяет, является ли значение v undefined(VOID).
  2. Следующая проверка в строке № 12 проверяет наличие объекта JSVAL_IS_OBJECT,
  3. Кроме того, вызывает функциональный класс (строка № 18, 19).
  4. И, следовательно, оценивается как Object
  5. Впоследствии есть проверки на число, строку и логическое значение, даже не проверка на Null

Возможно, это одна из причин, по которой первая версия JavaScript была завершена за 10 дней. Позже, продолжал жить с этой проблемой, а не исправлять из-за ее тесно связанной логики в исходном исходном коде. Исправление может привести к поломке многих вещей в коде.

Работа с Null и Undefined

Начиная с ES2020, у нас есть лучший способ обработки значений Nullish в Javascript. Для текущих проектов того же можно добиться с помощью Babel.js и/или Typescript.

  • Необязательная цепочка (?.)

Также известен как безопасная оценка или оператор безопасности.

Длинные цепочки обращений к свойствам в Javascript приводят к ошибкам, вызывающим сбои, поскольку можно получить null или undefined (“nullish” values). Проверка наличия свойства в глубоко вложенной структуре — утомительная задача, например, рассмотрим ответ API погоды,

Чтобы получить данные о значении «Гроза», используются три подхода:

Теперь из ES2020 или TypeScript 3.7 или @babel/plugin-proposal-optional-chaining поддерживает необязательную цепочку, где можно написать так:

  • Нулевое объединение (??)

Оператор Nullish Coalescing (??) действует очень похоже на оператор ||, за исключением того, что мы используем не ложные значения, а nullish, что означает, что значение строго равно null или undefined.

Поддерживается с ES2020, Typescript 3.7 и @babel/plugin-proposal-nullish-coalescing-operator

Избегайте Null всеми возможными способами

Шаблон NullObject

❌ НЕПРАВИЛЬНО

✅ ВПРАВО, Шаблон NullObject

Заключительные слова

(любезно предоставлено: Максмиллиано Контьери)

Программисты используют Null как разные флаги. Он может намекать на отсутствие, неопределенность, значение, ошибку или ложное значение (значение “Nullish”). Множественная семантика приводит к ошибкам связывания.

Проблемы

  • Связь между вызывающими и отправляющими
  • Несоответствие между вызывающими и отправляющими
  • Если/переключатель/случай загрязняет окружающую среду
  • Null не полиморфен реальным объектам, поэтому NullPointerException (TypeError: null or undefined has no properties)
  • Null не существует в реальном мире. Таким образом, нарушается принцип биекции

Решения

  • Избегайте нуля
  • Использовать Шаблон нулевого объекта
  • Используйте необязательно

Исключения

  • API, базы данных, внешние системы, где существует NULL

Поддержка Линтера

Добавьте no-null и no-undef к вашему .eslintrc

Звук Нулевая Безопасность

В современных языках введена Sound Null Safetyили иначе известная как Void Safety для более безопасного и удобного кода, что означает, что по умолчанию язык предполагает, что переменные являются ненулевыми значениями, если явно не указано иное. .

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

Например, Dart, Swift и другие следуют Sound Null Safety.

использованная литература

Java и null неразрывно связаны. Трудно найти Java-программиста, который не сталкивался с NullPointerException. Если даже автор понятия нулевого указателя признал его «ошибкой на миллиард долларов», почему он сохранился в Java? null присутствует в Java уже давно, и я уверен, что разработчики языка знают, что он создает больше проблем, чем решает. Это удивительно, ведь философия Java — делать вещи как можно более простыми. Если разработчики отказались от указателей, перегрузки операторов и множественного наследования, то почему они оставили null? Я не знаю ответа на этот вопрос. Однако не имеет значения, насколько много критики идет в адрес null в Java, нам придется с этим смириться. Вместо того, чтобы жаловаться, давайте лучше научимся правильно его использовать. Если быть недостаточно внимательным при использовании null, Java заставит вас страдать с помощью ужасного java.lang.NullPointerException. Наиболее частая причина NullPointerException — недостаточное понимание тонкостей использования null. Давайте вспомним самые важные вещи о нем в Java.

Что такое null в Java

Как мы уже выяснили, null очень важен в Java. Изначально он служил, чтобы обозначить отсутствие чего-либо, например, пользователя, ресурса и т. п. Но уже через год выяснилось, что он приносит много проблем. В этой статье мы рассмотрим основные вещи, которые следует знать о нулевом указателе в Java, чтобы свести к минимуму проверки на null и избежать неприятных NullPointerException.

1. В первую очередь, null — это ключевое слово в Java, как public, static или final. Оно регистрозависимо, поэтому вы не сможете написать Null или NULL, компилятор этого не поймет и выдаст ошибку:

Object obj1 = NULL; // Неверно
Object obj2 = null; // ОК

Эта проблема часто возникает у программистов, которые переходят на Java с других языков, но с современными средами разработки это несущественно. Такие IDE, как Eclipse или Netbeans, исправляют эти ошибки, пока вы набираете код. Но во времена Блокнота, Vim или Emacs это было серьезной проблемой, которая отнимала много времени.

2. Так же, как и любой примитивный тип имеет значение по умолчанию (0 у int, false у boolean), null — значение по умолчанию любого ссылочного типа, а значит, и для любого объекта. Если вы объявляете булеву переменную, ей присваивается значение false. Если вы объявляете ссылочную переменную, ей присваивается значение null, вне зависимости от области видимости и модификаторов доступа. Единственное, компилятор предупредит о попытке использовать неинициализированную локальную переменную. Для того, чтобы убедиться в этом, вы можете создать ссылочную переменную, не инициализируя ее, и вывести ее на экран:

private static Object myObj;
public static void main(String args[]){
    System.out.println("Значение myObj : " + myObj);
}

// Значение myObjc: null

Это справедливо как для статических, так и для нестатических переменных. В данном случае мы объявили myObj как статическую переменную для того, чтобы ее можно было использовать в статическом методе main.

3. Несмотря на распространенное мнение, null не является ни объектом, ни типом. Это просто специальное значение, которое может быть присвоено любому ссылочному типу. Кроме того, вы также можете привести null к любому ссылочному типу:

String str = null; // null можно присвоить переменной типа String, ...
Integer itr = null; // ... и Integer, ...
Double dbl = null;  // ... и Double.

String myStr = (String) null; // null может быть приведен к String ...
Integer myItr = (Integer) null; // ... и к Integer
Double myDbl = (Double) null; // без ошибок.

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

4. null может быть присвоен только переменной ссылочного типа. Примитивным типам — int, double, float или boolean — значение null присвоить нельзя. Компилятор не допустит этого и выдаст ошибку:

int i = null; // type mismatch: cannot convert from null to int
short s = null; //  type mismatch: cannot convert from null to short
byte b = null: // type mismatch: cannot convert from null to byte
double d = null; // type mismatch: cannot convert from null to double

Integer itr = null; // все в порядке
int j = itr; // нет ошибки при компиляции, но NullPointerException при запуске

Итак, попытка присвоения значения null примитивному типу — ошибка времени компиляции, но вы можете присвоить null типу-обертке, а затем присвоить это значение соответствуему примитиву. Компилятор ругаться не будет, но при выполнении кода будет брошено NullPointerException. Это происходит из-за автоматического заворачивания (autoboxing) в Java

5. Любой объект класса-обертки со значением null кинет NullPointerException при разворачивании (unboxing). Некоторые программисты думают, что обертка автоматически присвоит примитиву значение по умолчанию (0 для int, false для boolean и т. д.), но это не так:

Integer iAmNull = null;
int i = iAmNull; // компиляция пройдет успешно

Если вы запустите этот код, вы увидите Exception in thread "main" java.lang.NullPointerException в консоли. Это часто случается при работе с HashMap с ключами типа Integer. Код ниже сломается, как только вы его запустите:

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String args[]) throws InterruptedException {

        Map<Integer, Integer> numberAndCount = new HashMap<>();

        int[] numbers = {3, 5, 7, 9, 11, 13, 17, 19, 2, 3, 5, 33, 12, 5};

        for (int i : numbers) {
            int count = numberAndCount.get(i); // NullPointerException
            numberAndCount.put(i, count++); 
        }
    }

}

Вывод:

Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:14)

Этот код выглядит простым и понятным. Мы ищем, сколько каждое число встречается в массиве, это классический способ поиска дубликатов в массиве в Java. Мы берем предыдущее значение количества, инкрементируем его и кладем обратно в HashMap. Мы полагаем, что Integer позаботится о том, чтобы вернуть значение по умолчанию для int, однако если числа нет в HashMap, метод get() вернет null, а не 0. И при оборачивании выбросит NullPoinerException. Представьте, что этот код завернут в условие и недостаточно протестирован. Как только вы его запустите на продакшен – УПС!

6. Оператор instanceof вернет false, будучи примененным к переменной со значением null или к литералу null:

Integer iAmNull = null;
if (iAmNull instanceof Integer) {
    System.out.println("iAmNull — экземпляр Integer");
} else {
    System.out.println("iAmNull не является экземпляром Integer");
}

Результат выполнения:

iAmNull не является экземпляром Integer

Это важное свойство оператора instanceof, которое делает его полезным при приведении типов.

7. Возможно, вы уже знаете, что если вызвать нестатический метод по ссылке со значением null, результатом будет NullPointerException. Но зато вы можете вызвать по ней статический метод класса:

public class Testing {
    public static void main(String args[]){
        Testing myObject = null;
        myObject.iAmStaticMethod();
        myObject.iAmNonStaticMethod();
    }

    private static void iAmStaticMethod(){
        System.out.println("I am static method, can be called by null reference");
    }

    private void iAmNonStaticMethod(){
        System.out.println("I am NON static method, don't date to call me by null");
    }

}

Результат выполнения этого кода:

I am static method, can be called by null reference
Exception in thread "main" java.lang.NullPointerException
               at Testing.main(Testing.java:5)

8. Вы можете передавать null в любой метод, который принимает ссылочный тип, например, public void print(Object obj) может быть вызван так: print(null). С точки зрения компилятора ошибки здесь нет, но поведение такого кода целиком зависит от реализации метода. Безопасный метод не кидает NullPointerException в этом случае, а тихо завершает работу. Если бизнес-логика позволяет, лучше писать безопасные методы.

9. Вы можете сравнивать null, используя оператор == («равно») и != («не равно»), но не с арифметическими или логическими операторами (такими как «больше» или «меньше»). В отличие от SQL, в Java null == null вернет true:

public class Test {

    public static void main(String args[]) throws InterruptedException {

        String abc = null;
        String cde = null;

        if (abc == cde) {
            System.out.println("null == null is true in Java");
        }

        if (null != null) {
            System.out.println("null != null is false in Java");
        }

        // classical null check
        if (abc == null) {
            // do something
        }

        // not ok, compile time error
        if (abc > null) {
            // do something
        }
    }
}

Вывод этого кода:

null == null is true in Java

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

Перевод статьи «9 Things about Null in Java»

Joker burning a huge pile of money

Tony Hoare, the creator of NULL, now refers to NULL as The Billion Dollar Mistake. Even though NULL Reference Exceptions continue to haunt our code to this day, we still choose to continue using it.

And for some reason JavaScript decided to double down on the problems with null by also creating undefined.

Today I would like to demonstrate a solution to this problem with the Maybe.

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. — Tony Hoare

Do not underestimate the problems of NULL

Before you have even finish reading this article… I can already sense it, your desire to hit PAGE DOWN, rush straight to the comment section and blast out a «but NULL is never a problem for ME». But please pause, slow down, read and contemplate.

8 of 10 errors from Top 10 JavaScript errors from 1000+ projects (and how to avoid them) are null and undefined problems. Eight. Out. Of. Ten.

To underestimate NULL is to be defeated by NULL.

Null Guards

Because of the problems null brings with it, we have to constantly guard our code from it. Unguarded code might look something like this:

const toUpper = string => string.toUpperCase()

This code is susceptible to NULL Reference Exceptions.

toUpper(null) //=> ​​Cannot read property 'toUpperCase' of null​​

So we are forced to guard against null.

const toUpper = string => {
  if (string != null) {
//    --------------
//                   
//                    null guard
    return string.toUpperCase()
  }
}

But this quickly becomes verbose as everywhere that may encounter null has to be guarded.

const toUpper = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.toUpperCase()
  }
}

const toLower = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.toLowerCase()
  }
}

const trim = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.trim()
  }
}

If we think about a values as having a one-to-many relationship with code that may access it, then it makes more sense to place the guards on the one and not on the many.

Nullable Types

The .NET Framework 2.0 introduced Nullable Types into the .NET language. This new Nullable value, could be set to null without the reference being null. This meant if x was a Nullable Type, you could still do things like x.HasValue and x.Value without getting a NullReferenceException.

int? x = null
if (x.HasValue)
{
    Console.WriteLine($"x is {x.Value}")
}
else
{
    Console.WriteLine("x does not have a value")
}

The Maybe

The Maybe is similar to a Nullable Type. The variable will always have a value, and that value might represent a null, but it will never be set to null.

For these examples, I’ll be using the Maybe from MojiScript. (Also checkout monet and Sanctuary, Folktale for other Maybes). Use the following import:

import { fromNullable } from "mojiscript/type/Maybe"

The Maybe is a union type of either a Just or a Nothing. Just contains a value and Nothing is well… nothing.

But now the value is all wrapped up inside of the Maybe. To access the value of a Maybe, you would have touse a map function. Fun to Google: map is what makes the Maybe type a Functor.

If you are getting that feeling that you have seen this somewhere before that is because this exactly how a Promise works. The difference is Promise uses then and Maybe uses Map.

const promise = Promise.resolve(888)
const maybe = Just(888)

promise.then(double)
maybe.map(double)

Same same but different.

const toUpper = string => string.toUpperCase()

Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing

Notice how in both cases above, the toUpper function no longer throws an Error. That is because we are no longer calling toUpper directly with a String, but instead mapping it with our Maybe.

If we convert all types within our application to use a Maybe, then all null guards are no longer necessary.

The null is now guarded in a single place, in the Maybe type, instead of being sprinkled throughout the application, wherever the value might be accessed.

The Maybe is a guard on the one instead of the many!

Neo vs many agent Smiths

Getting in and out of Maybes

But what about the times when we are not in control of the code, when we must send or receive a null value? Some examples might be 3rd party libraries that will return a null or libraries that will require passing null as an argument.

In these cases, we can convert a null value to a Maybe using fromNullable and we can convert back to a nullable value using fromMaybe.

import { fromMaybe, fromNullable } from "mojiscript/type/Maybe"

// converting nullable values to a Maybe
fromNullable(undefined) //=> Nothing
fromNullable(null) //=> Nothing
fromNullable(123) //=> Just (123)
fromNullable("abc") //=> Just ("abc")

// converting Maybe to a nullable type
fromMaybe(Just("abc")) //=> 'abc'
fromMaybe(Nothing) //=> null

You could als guard a single function like this:

const toUpper = string =>
  fromNullable(string).map(s => s.toUpperCase()).value

But that is a little verbose and it’s much better to expand the safety of the Maybe type to the entire application. Put the guards in place at the gateways in and out of your application, not individual functions.

One example could be using a Maybe in your Redux.

// username is a Maybe, initially set to Nothing.
const initalState = {
  username: Nothing
}

// your reducer is the gateway that ensures the value will always be a maybe.
const reducer = (state = initialState, { type, value }) =>
  type === 'SET_USERNAME'
    ? { ...state, username: fromNullable(value) }
    : state

// somewhere in your render
render() {
  const userBlock = this.props.username.map(username => <h1>{username}</h1>)
  const noUserBlock = <div>Anonymous</div>

  return (
    <div>
    {fromMaybe (noUserBlock) (userBlock)}
    </div>
  )
}

JavaScript Type Coercion

MojiScript’s Maybe can use JavaScript’s implicit and explicit coercion to it’s advantage.

Maybe can be implicity coerced into a String.

// coercing to a String
console.log("a" + Just("b") + "c") //=> 'abc'
console.log("a" + Nothing + "c") //=> 'ac'

Maybe can be explicity coerced into a Number.

Number(Just(888)) //=> 888
Number(Nothing) //=> 0

Maybe can even be stringified.

const data = {
  id: Nothing,
  name: Just("Joel")
}

JSON.stringify(data)
//=> {"id":null,"name":"Joel"}

Accessing Nested Objects

Let’s take a look at the common task of accessing nested objects.

We’ll use these objects. One is lacking an address, which can yield nulls. Gross.

const user1 = {
  id: 100,
  address: {
    address1: "123 Fake st",
    state: "CA"
  }
}

const user2 = {
  id: 101
}

These are common ways to access nested objects.

user1.address.state //=> 'CA'
user2.address.state //=> Error: Cannot read property 'state' of undefined

// short circuit
user2 && user2.address && user2.address.state //=> undefined

// Oliver Steel's Nested Object Pattern
((user2||{}).address||{}).state //=> undefined

Prettier seems to hate both of those techniques, turning them into unreadable junk.

Now let’s try accessing nested objects with a Maybe.

import { fromNullable } from 'mojiscript/type/Maybe'

const prop = prop => obj =>
  fromNullable(obj).flatMap(o => fromNullable(o[prop]))

Just(user1)
  .flatMap(prop('address))
  .flatMap(prop('state)) //=> Just ("CA")

Just(user2)
  .flatMap(prop('address))
  .flatMap(prop('address)) //=> Nothing

A lot of this boiler plate can be reduced with some helper methods.

import pathOr from 'mojiscript/object/PathOr'
import { fromNullable } from 'mojiscript/type/Maybe'

const getStateFromUser = obj =>
  fromNullable(pathOr (null) ([ 'address', 'state' ]) (obj))

Just(user1).map(getStateFromUser) //=> Just ("CA")
Just(user2).map(getStateFromUser) //=> Nothing

Decoupled map function

A Map can also be decoupled from Maybe. There are many libs that have a map function, like Ramda, but I’ll be using the one from MojiScript for this example.

import map from 'mojiscript/list/map'

const toUpper = string => string.toUpperCase()

Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing
import map from 'mojiscript/list/map'

const toUpper = string => string.toUpperCase()

map (toUpper) (Just ("abc")) //=> Just ('ABC')
map (toUpper) (Nothing) //=> Nothing

This was getting far too big for this section, so it has been broken out into it’s own article here: An introduction to MojiScript’s enhanced map

Heavy Lifting

Lifting is a technique to apply Applicatives to a function. In English that means we can use «normal» functions with our Maybes. Fun to Google: ap is what makes the Maybe type an Applicative.

This code will use liftA2, A for Applicative and 2 for the number of arguments in the function.

import liftA2 from "mojiscript/function/liftA2"
import Just from "mojiscript/type/Just"
import Nothing from "mojiscript/type/Nothing"

const add = x => y => x + y
const ladd = liftA2 (add)

add (123) (765) //=> 888

ladd (Just (123)) (Just (765)) //=> Just (888)
ladd (Nothing) (Just (765)) //=> Nothing
ladd (Just (123)) (Nothing) //=> Nothing

Some things to notice:

  • The function add is curried. You can use any curry function to do this for you.
  • add consists of 2 parameters. If it was 3, we would use liftA3.
  • All arguments must be a Just, otherwise Nothing is returned.

So now we do not have to modify our functions to understand the Maybe type, we can use map and also lift to apply the function to our Maybes.

Continue Learning: Functors, Applicatives, And Monads In Pictures does an incredible job of explaining this and more!

Maybe Function Decorator

There are times when you would like to guard a single function against NULL. That is where the maybe Function Decorator comes in handy.

const maybe = func => (...args) =>
  !args.length || args.some(x => x == null)
    ? null
    : func(...args)

Guard your functions against null with the maybe function decorator:

const toUpper = string => string.toUpperCase()
const maybeToUpper = maybe(toUpper)
maybeToUpper("abc") //=> 'ABC'
maybeToUpper(null) //=> null

Can also be written like this:

const toUpper = maybe(string => string.toUpperCase())

Learn more about Function Decorators:

  • Function decorators: Transforming callbacks into promises and back again
  • Functional JavaScript: Function Decorators Part 2

TC39 Optional Chaining for JavaScript

This is a good time to mention the TC39 Optional Chaining Proposal that is currently in Stage 1.

Optional Chaining will allow you to guard against null with a shorter syntax.

// without Optional Chaining
const toUpper = string => string && string.toUpperCase()

// with Optional Chaining
const toUpper = string => string?.toUpperCase()

Even with Optional Chaining, the guards are still on the many and not the one, but at least the syntax is short.

Wisdoms

  • To underestimate NULL is to be defeated by NULL.
  • 8 out of the 10 top 10 errors are NULL and undefined errors.
  • If we think about a values as having a one-to-many relationship with code that may access it, then it makes more sense to place the guards on the one and not on the many.
  • It is possible to completely eliminate an entire class of bugs (NULL Reference Exceptions) by eliminating null.
  • Having NULL Reference Exceptions in your code is a choice.

End

Have questions or comments? I’d love to hear them!

Hop over to the MojiScript Discord chat and say hi!

This turned out a little longer than I originally thought it would. But this is a subject that is hard to sum up into a single article.

You can also use the Maybe with MojiScript’s map. Read more about how awesome MojiScript’s map is here…

My articles are very Functional JavaScript heavy, if you need more FP, follow me here, or on Twitter @joelnet!

Cheers!

В мире Javascript и как с этим работать

Какие ошибки в мире программного обеспечения обходятся в миллиарды долларов?

  • По словам Тони Хоара, ошибка 2000 года, класс ошибок, связанных с хранением и форматированием данных календаря, обойдется чуть менее чем в 4 миллиарда долларов.
  • CodeRed Virus, компьютерный червь, внедрившийся в компании по всему миру, вывел из строя все сети. Прерывание бизнеса и всего обычного банковского дела обошлось мировой экономике в 4 миллиарда долларов.
  • Null – ошибочное изобретение британского ученого-компьютерщика Тони Хоара (наиболее известного благодаря своему алгоритму быстрой сортировки) в 1964 году, который изобрел нулевые ссылки. как его «ошибка на миллиард долларов».

Кто придумал «обнулить» ошибку на миллиард долларов и почему?

Я называю это своей ошибкой на миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я разрабатывал первую всеобъемлющую систему типов для ссылок в объектно-ориентированном языке (ALGOL W). Моя цель состояла в том, чтобы гарантировать, что любое использование ссылок должно быть абсолютно безопасным, с автоматической проверкой компилятором. Но я не мог устоять перед искушением добавить нулевую ссылку просто потому, что это было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили миллиарды долларов боли и ущерба за последние сорок лет. — Тони Хоар

Ссылка на нулевой указатель может быть плохой идеей. Сравнивая ссылку нулевого указателя с неразборчивым в связях прелюбодеем, он заметил, что нулевое присвоение для каждого холостяка, представленного в объектной структуре, «будет выглядеть полиаморно женатым на одном и том же человеке Null. — Эдсгар Джикстра

Что такое ошибка на миллиард долларов в контексте мира Javascript?

У нас есть два кандидата в мире Javascript, подпадающие под эту категорию:

1⃣️ Нет

Значение null записывается литералом: null. null не является идентификатором свойства глобального объекта, как может быть undefined. Вместо этого null выражает отсутствие идентификации, указывая на то, что переменная не указывает ни на какой объект. В API-интерфейсах null часто извлекается в месте, где объект можно ожидать, но объект не является релевантным.

2⃣️ Не определено

undefined — это свойство глобального объекта. То есть это переменная в глобальной области видимости. Начальное значение undefined — это примитивное значение undefined.

примитивные типы данных Javascript (ES2020),

  1. Boolean
  2. Null
  3. Undefined
  4. Number
  5. String
  6. BigInt
  7. Symbol

Null и Undefined в Javascript называются «нулевыми» (ложными) значениями.

Ложные значения: Undefined, null, 0, NaN, empty string‘’, false

Нулевой или неопределенный

Несмотря на то, что поведение обоих значений является ложным, если кто-то думает Null vs Undefined как Declared vs Undeclared, это не совсем так!

Undefined может быть как объявленным, так и необъявленным.

А как насчет Null?

У Null есть свои проблемы, с которыми нужно разобраться… отлично! Давайте посмотрим на это,

Ну, typeof null == “object” — это ошибка 25-летней давности, начиная с первой версии Javascript.

В первой версии JavaScript значения хранились в 32-битных единицах, которые состояли из небольшого тега типа (1–3 бита) и фактических данных значения. Теги типа хранились в младших битах единиц. Их было пятеро:

  • 000: object. Данные являются ссылкой на объект.
  • 001: int. Данные представляют собой 31-битное целое число со знаком.
  • 010: double. Данные являются ссылкой на двойное число с плавающей запятой.
  • 100: string. Данные являются ссылкой на строку.
  • 110: boolean. Данные являются логическими.

Из исходного кода jsapi.h, (ссылка)

#define JSVAL_OBJECT      0x0     /* untagged reference to object */
#define JSVAL_INT         0x1     /* tagged 31-bit integer value */
#define JSVAL_DOUBLE      0x2     /* tagged reference to double */
#define JSVAL_STRING      0x4     /* tagged reference to string */
#define JSVAL_BOOLEAN     0x6     /* tagged boolean value */

Два значения были особенными:

  • undefined(JSVAL_VOID), представляет собой целое минус (-) JSVAL_INT_POW2 (30), то есть число вне целочисленного диапазона
  • null(JSVAL_NULL) — это указатель NULL машинного кода, тег типа объекта плюс ссылка, равная нулю(OBJECT_TO_JSVAL(0)).
#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
#define JSVAL_NULL              OBJECT_TO_JSVAL(0)
#define JSVAL_ZERO              INT_TO_JSVAL(0)
#define JSVAL_ONE               INT_TO_JSVAL(1)
#define JSVAL_FALSE             BOOLEAN_TO_JSVAL(JS_FALSE)
#define JSVAL_TRUE              BOOLEAN_TO_JSVAL(JS_TRUE)

Теперь, когда рассмотрел его тег типа, а тег типа сказал объект. («источник»)

  1. Строка № 10, сначала проверяет, является ли значение v undefined(VOID).
  2. Следующая проверка в строке № 12 проверяет наличие объекта JSVAL_IS_OBJECT,
  3. Кроме того, вызывает функциональный класс (строка № 18, 19).
  4. И, следовательно, оценивается как Object
  5. Впоследствии есть проверки на число, строку и логическое значение, даже не проверка на Null

Возможно, это одна из причин, по которой первая версия JavaScript была завершена за 10 дней. Позже, продолжал жить с этой проблемой, а не исправлять из-за ее тесно связанной логики в исходном исходном коде. Исправление может привести к поломке многих вещей в коде.

Работа с Null и Undefined

Начиная с ES2020, у нас есть лучший способ обработки значений Nullish в Javascript. Для текущих проектов того же можно добиться с помощью Babel.js и/или Typescript.

  • Необязательная цепочка (?.)

Также известен как безопасная оценка или оператор безопасности.

Длинные цепочки обращений к свойствам в Javascript приводят к ошибкам, вызывающим сбои, поскольку можно получить null или undefined (“nullish” values). Проверка наличия свойства в глубоко вложенной структуре — утомительная задача, например, рассмотрим ответ API погоды,

Чтобы получить данные о значении «Гроза», используются три подхода:

Теперь из ES2020 или TypeScript 3.7 или @babel/plugin-proposal-optional-chaining поддерживает необязательную цепочку, где можно написать так:

  • Нулевое объединение (??)

Оператор Nullish Coalescing (??) действует очень похоже на оператор ||, за исключением того, что мы используем не ложные значения, а nullish, что означает, что значение строго равно null или undefined.

Поддерживается с ES2020, Typescript 3.7 и @babel/plugin-proposal-nullish-coalescing-operator

Избегайте Null всеми возможными способами

Шаблон NullObject

❌ НЕПРАВИЛЬНО

✅ ВПРАВО, Шаблон NullObject

Заключительные слова

(любезно предоставлено: Максмиллиано Контьери)

Программисты используют Null как разные флаги. Он может намекать на отсутствие, неопределенность, значение, ошибку или ложное значение (значение “Nullish”). Множественная семантика приводит к ошибкам связывания.

Проблемы

  • Связь между вызывающими и отправляющими
  • Несоответствие между вызывающими и отправляющими
  • Если/переключатель/случай загрязняет окружающую среду
  • Null не полиморфен реальным объектам, поэтому NullPointerException (TypeError: null or undefined has no properties)
  • Null не существует в реальном мире. Таким образом, нарушается принцип биекции

Решения

  • Избегайте нуля
  • Использовать Шаблон нулевого объекта
  • Используйте необязательно

Исключения

  • API, базы данных, внешние системы, где существует NULL

Поддержка Линтера

Добавьте no-null и no-undef к вашему .eslintrc

Звук Нулевая Безопасность

В современных языках введена Sound Null Safetyили иначе известная как Void Safety для более безопасного и удобного кода, что означает, что по умолчанию язык предполагает, что переменные являются ненулевыми значениями, если явно не указано иное. .

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

Например, Dart, Swift и другие следуют Sound Null Safety.

использованная литература

Java

В своем выступлении “Null References: The billion dollar mistake” (“Нулевые ссылки: ошибка на миллиард долларов”), Тони Хоар описывает реализацию нулевых ссылок в языках программирования ALGOL, что также по его словам стало ошибкой стоимостью в миллиард долларов. Такие авторитетные книги, как Clean Code: A Handbook of Agile Software Craftsmanship (“Чистый код: настольное руководство по гибкой разработке ПО”) рекомендуют использовать нуль как можно реже. В то же время в книге Bug Patterns in Java (“Шаблоны ошибок в Java”) проблемам, связанным с нулевыми значениями, посвящается аж целых три главы. Тема “What is a null pointer exception and how do I fix it” (“Что такое исключение нулевого значения и как его исправить”), обсуждаемая на Stack Overflow, набрала уже более 3 млн просмотров. Работа с нулевыми значениями и впрямь может вызвать немало сложностей.

Skillfactory.ru

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

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

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

Чем опасен null?

Null  —  это особое значение, поскольку оно не ассоциируется ни с каким типом (можете смело проверить это инструкцией instanceofв отношении любого другого класса в JRE) и с радостью занимает место любого другого объекта в присвоениях переменных и вызовах методов. Именно в этом и кроются две основных его опасности:

  1. Каждое возвращаемое значение сложного типа может быть null.
  2. Каждое значение параметра со сложным типом может также быть null.

В результате любое возвращаемое значение или объект параметра  —  это потенциальное исключение нулевого указателя (NPE), возникающее в случае неправильной обработки. 

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

Может тогда вообще никогда не присваивать значениям null? Тоже неудачное предположение. Если учесть тот факт, что в каждом языке программирования есть пустое значение (nil, undefined, None, void и т.д.), то наличие общего значения, обозначающего отсутствие чего-либо, чрезвычайно полезно. 

Ошибка в условии цикла while может породить бесконечный цикл в любой программе, но это не делает такие циклы плохими по природе. Аналогично будет неверным считать, что null всегда неуместен только из-за того, что его неправильное использование может привести к ошибкам. Null  —  это наиболее естественное значение для выражения конкретных вещей, но при этом очень неподходящее для выражения других. Компетентные разработчики должны уметь различать эти случаи. В следующем разделе я как раз перейду к объяснению этого.

Когда Null уместен, а когда нет

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

Возвращение null

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

Сейчас я работаю над проектом электронной записи пациентов Columna, в котором у нас есть классы, представляющие элементы рабочего процесса больницы вроде пациентов, медикаментов, врачей, больничных отделений, госпитализаций и пр. При моделировании любой области возникают случаи, когда нам нужно допустить для определенного элемента отсутствие значения. Предположим, что у нас есть класс, представляющий госпитализацию с атрибутами, которые ее описывают: больничное отделение, куда помещается пациент, причина госпитализации, ее время и т.д. Аналогичным образом у нас может быть класс, который представляет пациента с набором атрибутов вроде имени и номера социального страхования. В любой момент времени пациент может быть госпитализирован или нет. Говоря более формально, у нас есть связь типа “имеет” с мощностью 0..1.

Представим метод, извлекающий из базы данных информацию о госпитализации данного пациента:

public Admission getAdmission(Patient patient);

Что должен возвращать этот метод для не госпитализированного пациента, если не null? Есть ли для выражения этого более точное значение? Спорю, что нет. 

Существует и много других сценариев, в которых объект области напрямую ассоциирован с необязательными значениями в качестве атрибутов. Объект, представляющий медикаменты, содержит такие значения, как название препарата, его форму, активность, действующее вещество и т.д. Однако разнообразие медикаментов чрезвычайно обширно, начиная от антибактериальных кремов и заканчивая чаями из каннабиса (оставим или на ромашку заменим?:)), следовательно не все атрибуты будут актуальны для каждого. И снова возвращение null для отсутствующего атрибута выглядит очевидным способом сообщить, что допускается отсутствие значения. Есть ли для этого лучшая альтернатива?

Некоторые выступают за использование так называемого шаблона проектирования Null Object вместо null. Главная его идея в реализации пустого класса с минимумом или вообще без функциональности, т.е. нулевого объекта, который можно использовать вместо класса, содержащего действительную функциональность. Лучше всего данный шаблон показывает себя в сценариях, где нужно рекурсивно обойти структуру бинарного дерева в поиске суммы значений всех узлов. Для этого вы выполняете поиск в глубину и рекурсивно суммируете в каждом узле значения его левого и правого поддерева.

Дерево поиска обычно реализуется так, что каждый узел в нем имеет левого и правого потомка, которые являются либо также узлами, либо концевыми вершинами. Если такое представление дерева использует для концевых узлов null, то вам придется явно выполнять проверки на нулевых потомков, чтобы останавливать рекурсию в концевом узле, предотвращая попытку получения его значения. Вместо этого вам следует определить интерфейс Node с простым методом getValue() и реализовать его в представляющем узел классе, который вычисляет значение, складывая значения getValues() потомков, как показано на рисунке ниже. Реализуйте такой же интерфейс в классе, представляющем узел, и пусть класс концевого узла возвращает при вызове 0. Теперь нам больше не нужно различать код между концевым узлом и обычным. Необходимость явно проверять наличие null отпала вместе с риском получения исключения (NPE). 

Применение шаблона нулевого объекта к структуре дерева

Для применения этого шаблона к примеру с госпитализацией нам потребуется создать интерфейс Admission. Затем мы определим класс AdmissionImpl для случаев, когда мы можем вернуть данные фактической госпитализации и класс AdmissionNullObjectImpl для случаев, когда не можем. Это позволит методу getAdmission() возвращать либо реальный AdmissionImpl, либо AdmissionNullObjectImpl. Так как вызывающий код использует общий тип Admission, мы можем рассматривать оба объекта одинаково, не рискуя получить исключение и не загромождая код проверками обработки null.

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

Что будет возвращать класс AdmissionNullObject, когда в нем нет данных? Что он должен возвращать вместо объекта локации, когда вызывается getLocation()? Какой будет подходящая начальная дата для возврата?

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

Еще одна альтернатива  —  это использовать вместо null пустую string, если атрибутом является простая строка. Это избавляет нас от риска получить NPE, но при этом для правильной обработки пустого значения, скорее всего, потребуется столько же проверок, как и в случае с null. Кроме того, семантика будет отличаться: пустая строка представляет строку с пустым значением, а null не представляет ничего. Это становится актуальным, когда приложению нужно различать, ввел пользователь информацию в виде значения пустой строки или нет. Вы избавляетесь от риска получить NPE ценой применения несколько сбивающего с толку значения.

Теперь давайте рассмотрим применение null в коде, не моделирующем реальные концепции. Большинство классов в базе объектно-ориентированного кода не имеют соответствий в реальной жизни и существуют только в виде абстракции инфраструктуры, обработки и преобразования, а также для группировки связанной функциональности. При этом не до конца понятно, должно ли допускаться их представление нулевым значением. Если мы вызываем геттер для значения с очень абстрактным типом вроде TwoFactorComplexStrategyHandlerDelegateBean, стоит ли нам ожидать, что оно будет null?

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

Такие объекты де факто не должны быть представлены нулевым значением. Не только в нашей базе кода, но также и в JRE, библиотеках и фреймворках. Это можно назвать проблематичным, но вопрос насколько? Важно понимать, что несмотря на необходимость минимизировать использование null, мы не должны добиваться этого ценой переполнения баз кода сложными обходными маневрами с целью избежать появления таких значений. Как я уже отмечал, альтернативы в данном случае не всегда хороши. Однако есть случаи возвращения null, которых избежать легко, хотя встречаются они частенько. Возвращение null для классов в таких ситуациях вызывает подозрение. Например, иногда null возвращается из методов геттеров, где объект не может быть создан по определенным причинам вроде ошибки сервера при его вызове методом. Этот метод обрабатывает ошибку, регистрируя ее в инструкцию catch, и вместо создания экземпляра объекта возвращает null. Это легко исправить. Исключение должно использоваться для указания на неполадку и привлекать для ее обработки вызывающий код. Возвращение null в подобных сценариях будет сбивать с толку, не обеспечит обработку ошибки и может перенести проблему в другую часть кода, где было бы лучше использовать систему fail-fast, немедленно останавливающую работу приложения в случае потенциального сбоя. 

Еще одно ошибочное использование null  —  это представление связи “имеет” с мощностью 0..* (в начале статьи я говорил о связях 0..1). Если вернуться к примеру с объектом пациента, то в нем пациент может иметь одного/нескольких зарегистрированных родственников или не иметь их совсем. Тем не менее я часто вижу, что люди возвращают null, когда для заполнения списка или других типов коллекций нет данных. Аналогичным образом null в качестве аргумента используется в методах для замещения отсутствующих коллекций. Но его применение в данном случае нежелательно по ряду причин. Он сбивает с толку, поскольку коллекцией мощность связи представляется идеально, а присвоив null типу коллекции, вы только добавляете ненужные риски в код. Цикл for, основанный на коллекции, ничего не делает, если эта коллекция пуста. В противном же случае он перебирает каждый элемент, выполняя определенные действия. Если вы позволите коллекции быть null, то выразите, по сути, пустой список, означающий, что в нем обрабатывать нечего. Однако при этом вам придется обеспечить выполнение проверки на null для каждого последующего метода, использующего эту коллекцию, иначе может возникнуть NPE. В случаях, когда нет данных для представления — вызывайте методы с пустыми коллекциями. Это также легко, как вызвать Collections.emptyList(), emptyMap(), emptySet() и т.д. Ненамного больше работы, чем объявить null, зато намного лучше.

Передача нулевых параметров

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

Как же полностью избежать нулевых параметров?

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

В таких языках, как Python, сигнатуры методов могут содержать предустановленные значения параметров, используемые при отсутствии значения аргумента в вызове метода. Тем не менее в Java такое невозможно. Ближайшим аналогом этого будет использовать перегрузку метода, когда в классе одна и та же сигнатура метода определяется несколько раз с разными параметрами. Один метод будет содержать всю функциональность и принимать весь набор параметров, а другие будут просто “декораторами” для вызова этого метода, каждый из которых будет получать свой поднабор параметров. Методы-декораторы определяют, какие значения должны использовать вместо отсутствующих параметров, чтобы вызывающему компоненту не пришлось их предоставлять. Жестко прописывая, какие значения должны предоставляться, когда у вызывающего их не хватает, мы уменьшаем риск появления ошибок и делаем принимаемые значения параметров явными. 

Аналогичным образом можно разбирать конструкторы, но также можно использовать шаблон строитель. Он помогает минимизировать число параметров конструктора и удаляет необходимость передавать в него нулевые значения, предоставляя для создания класса объект Builder. Смысл данного шаблона в косвенном инстанцировании объекта через промежуточный класс builder. Для предоставления аргументов, которые вы могли бы передать в конструктор напрямую, вы вызываете соответствующий каждому сеттер. Если значение еще не было установлено, builder предоставит его. Затем вы вызываете для builder метод Create(), и он инстанцирует объект за вас. Как и в большинстве шаблонов, в строителе вводятся дополнительные классы и сложность, поэтому прежде, чем его использовать, убедитесь, что в этом есть смысл. Использование его только ради избежания вызова конструктора с парой нулевых значения, скорее всего, будет излишним. 

Skillfactory.ru

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

Воспринимайте null правильно

Все больше языков программирования начинают реализовывать определенные возможности с учетом безопасности. Например, в таких языках, как Clojure, F# и Rust переменные по умолчанию неизменяемы. Компилятор допускает изменение значений только для тех из них, которые объявлены со специальным модификатором. Такой способ использования опасных функций вынуждает программистов переопределять поведение по умолчанию, указывая тем самым, что они осознают степень риска и делают это не без весомых оснований. И к null нам стоит относиться аналогичным образом. Нужно придерживать это значение для особых случаев, где оно будет вполне уместно, ограничив при этом его использование в целом, опять же не ценой усложнения кода креативными обходными решениями. При каждом намерении использовать null вместо перемещающегося между методами значения следует учесть оправданность этого. В таком случае вы должны гарантировать, что в итоге оно не окажется в том месте, где может вызвать проблемы, и другие разработчики будут знать, что значение может быть null. Если же этого обеспечить нельзя, то лучше рассмотреть другие варианты.

Читайте также:

  • Фреймворк Executor в Java
  • Java. Вложенные классы
  • Портируем решатель судоку с Java на WebAssembly

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Jens Christian B. Madsen: Part 1: Avoiding Null-Pointer Exceptions in a Modern Java Application

Java и null неразрывно связаны. Трудно найти Java-программиста, который не сталкивался с NullPointerException. Если даже автор понятия нулевого указателя признал его «ошибкой на миллиард долларов», почему он сохранился в Java? null присутствует в Java уже давно, и я уверен, что разработчики языка знают, что он создает больше проблем, чем решает. Это удивительно, ведь философия Java — делать вещи как можно более простыми. Если разработчики отказались от указателей, перегрузки операторов и множественного наследования, то почему они оставили null? Я не знаю ответа на этот вопрос. Однако не имеет значения, насколько много критики идет в адрес null в Java, нам придется с этим смириться. Вместо того, чтобы жаловаться, давайте лучше научимся правильно его использовать. Если быть недостаточно внимательным при использовании null, Java заставит вас страдать с помощью ужасного java.lang.NullPointerException. Наиболее частая причина NullPointerException — недостаточное понимание тонкостей использования null. Давайте вспомним самые важные вещи о нем в Java.

Что такое null в Java

Как мы уже выяснили, null очень важен в Java. Изначально он служил, чтобы обозначить отсутствие чего-либо, например, пользователя, ресурса и т. п. Но уже через год выяснилось, что он приносит много проблем. В этой статье мы рассмотрим основные вещи, которые следует знать о нулевом указателе в Java, чтобы свести к минимуму проверки на null и избежать неприятных NullPointerException.

1. В первую очередь, null — это ключевое слово в Java, как public, static или final. Оно регистрозависимо, поэтому вы не сможете написать Null или NULL, компилятор этого не поймет и выдаст ошибку:

Object obj1 = NULL; // Неверно
Object obj2 = null; // ОК

Эта проблема часто возникает у программистов, которые переходят на Java с других языков, но с современными средами разработки это несущественно. Такие IDE, как Eclipse или Netbeans, исправляют эти ошибки, пока вы набираете код. Но во времена Блокнота, Vim или Emacs это было серьезной проблемой, которая отнимала много времени.

2. Так же, как и любой примитивный тип имеет значение по умолчанию (0 у int, false у boolean), null — значение по умолчанию любого ссылочного типа, а значит, и для любого объекта. Если вы объявляете булеву переменную, ей присваивается значение false. Если вы объявляете ссылочную переменную, ей присваивается значение null, вне зависимости от области видимости и модификаторов доступа. Единственное, компилятор предупредит о попытке использовать неинициализированную локальную переменную. Для того, чтобы убедиться в этом, вы можете создать ссылочную переменную, не инициализируя ее, и вывести ее на экран:

private static Object myObj;
public static void main(String args[]){
    System.out.println("Значение myObj : " + myObj);
}

// Значение myObjc: null

Это справедливо как для статических, так и для нестатических переменных. В данном случае мы объявили myObj как статическую переменную для того, чтобы ее можно было использовать в статическом методе main.

3. Несмотря на распространенное мнение, null не является ни объектом, ни типом. Это просто специальное значение, которое может быть присвоено любому ссылочному типу. Кроме того, вы также можете привести null к любому ссылочному типу:

String str = null; // null можно присвоить переменной типа String, ...
Integer itr = null; // ... и Integer, ...
Double dbl = null;  // ... и Double.

String myStr = (String) null; // null может быть приведен к String ...
Integer myItr = (Integer) null; // ... и к Integer
Double myDbl = (Double) null; // без ошибок.

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

4. null может быть присвоен только переменной ссылочного типа. Примитивным типам — int, double, float или boolean — значение null присвоить нельзя. Компилятор не допустит этого и выдаст ошибку:

int i = null; // type mismatch: cannot convert from null to int
short s = null; //  type mismatch: cannot convert from null to short
byte b = null: // type mismatch: cannot convert from null to byte
double d = null; // type mismatch: cannot convert from null to double

Integer itr = null; // все в порядке
int j = itr; // нет ошибки при компиляции, но NullPointerException при запуске

Итак, попытка присвоения значения null примитивному типу — ошибка времени компиляции, но вы можете присвоить null типу-обертке, а затем присвоить это значение соответствуему примитиву. Компилятор ругаться не будет, но при выполнении кода будет брошено NullPointerException. Это происходит из-за автоматического заворачивания (autoboxing) в Java

5. Любой объект класса-обертки со значением null кинет NullPointerException при разворачивании (unboxing). Некоторые программисты думают, что обертка автоматически присвоит примитиву значение по умолчанию (0 для int, false для boolean и т. д.), но это не так:

Integer iAmNull = null;
int i = iAmNull; // компиляция пройдет успешно

Если вы запустите этот код, вы увидите Exception in thread "main" java.lang.NullPointerException в консоли. Это часто случается при работе с HashMap с ключами типа Integer. Код ниже сломается, как только вы его запустите:

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String args[]) throws InterruptedException {

        Map<Integer, Integer> numberAndCount = new HashMap<>();

        int[] numbers = {3, 5, 7, 9, 11, 13, 17, 19, 2, 3, 5, 33, 12, 5};

        for (int i : numbers) {
            int count = numberAndCount.get(i); // NullPointerException
            numberAndCount.put(i, count++); 
        }
    }

}

Вывод:

Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:14)

Этот код выглядит простым и понятным. Мы ищем, сколько каждое число встречается в массиве, это классический способ поиска дубликатов в массиве в Java. Мы берем предыдущее значение количества, инкрементируем его и кладем обратно в HashMap. Мы полагаем, что Integer позаботится о том, чтобы вернуть значение по умолчанию для int, однако если числа нет в HashMap, метод get() вернет null, а не 0. И при оборачивании выбросит NullPoinerException. Представьте, что этот код завернут в условие и недостаточно протестирован. Как только вы его запустите на продакшен – УПС!

6. Оператор instanceof вернет false, будучи примененным к переменной со значением null или к литералу null:

Integer iAmNull = null;
if (iAmNull instanceof Integer) {
    System.out.println("iAmNull — экземпляр Integer");
} else {
    System.out.println("iAmNull не является экземпляром Integer");
}

Результат выполнения:

iAmNull не является экземпляром Integer

Это важное свойство оператора instanceof, которое делает его полезным при приведении типов.

7. Возможно, вы уже знаете, что если вызвать нестатический метод по ссылке со значением null, результатом будет NullPointerException. Но зато вы можете вызвать по ней статический метод класса:

public class Testing {
    public static void main(String args[]){
        Testing myObject = null;
        myObject.iAmStaticMethod();
        myObject.iAmNonStaticMethod();
    }

    private static void iAmStaticMethod(){
        System.out.println("I am static method, can be called by null reference");
    }

    private void iAmNonStaticMethod(){
        System.out.println("I am NON static method, don't date to call me by null");
    }

}

Результат выполнения этого кода:

I am static method, can be called by null reference
Exception in thread "main" java.lang.NullPointerException
               at Testing.main(Testing.java:5)

8. Вы можете передавать null в любой метод, который принимает ссылочный тип, например, public void print(Object obj) может быть вызван так: print(null). С точки зрения компилятора ошибки здесь нет, но поведение такого кода целиком зависит от реализации метода. Безопасный метод не кидает NullPointerException в этом случае, а тихо завершает работу. Если бизнес-логика позволяет, лучше писать безопасные методы.

9. Вы можете сравнивать null, используя оператор == («равно») и != («не равно»), но не с арифметическими или логическими операторами (такими как «больше» или «меньше»). В отличие от SQL, в Java null == null вернет true:

public class Test {

    public static void main(String args[]) throws InterruptedException {

        String abc = null;
        String cde = null;

        if (abc == cde) {
            System.out.println("null == null is true in Java");
        }

        if (null != null) {
            System.out.println("null != null is false in Java");
        }

        // classical null check
        if (abc == null) {
            // do something
        }

        // not ok, compile time error
        if (abc > null) {
            // do something
        }
    }
}

Вывод этого кода:

null == null is true in Java

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

Перевод статьи «9 Things about Null in Java»

Geniepro писал(а):

Ну хорошо, тогда Вы, наверное, сможете привести примеры ракет, взорвавшихся от сборщика мусора?

НЕТ, КОНЕЧНО!
Мне даже интересно будет посмотреть на того ИДИОТА, который систему управления с ПО, использующего современные СМ, всунет в разгонные блоки…

Geniepro писал(а):

А ещё я слышал про космический аппарат, в котором за сотни тысяч миль на ходу исправляли какую-то алгоритмическую ошибку в коде на лиспе, в котором есть GC и горячая замена кода…

Шикарно.
RAX (так, по-моему), конечно интересным проектом БЫЛ.
Теперь остаётся вопрос, подобный тому, что задают обероновцам: если всё такое белое и пушистое, чего ж это такой славный подход и опыт с такими чудесными результатами не применяют в остальных конструкциях NASA? Вот про тот вариант, который был после лисповского, на Си++, я знаю, что он (не всегда весь, частями, блоками) пошёл в остальные системы управления, был реализован на куче платформ и в нескольких исполняющих системах (осях)…
Учитывая год создания Гатовского лиспового варианта и прогресс техники (при том, как успешно справлялся ТОТ вариант на ТОЙ технике) и ПО, странно не видеть желание руководителей и управленцев не заметь столь сладкую конхветку… Где всё почти «само собой» и такое гибкое и настраиваемое, шо аж налету «переобувается и портянки перематывае»…
И что ж это Гат, такой выдающийся работник, оказался не востребованным в такое «сложное время», ушёл из NASA (афигеть! Скольких, интерсно, можно назвать ПО-шников, попавших туда, а потом уволившихся сами???.. :) ) и даже имя сменил?…

Владимир Лось писал(а):

в сотне байт что распределять-то динамически?

А вот — не надо! Это я в сотне байт управление заслонками движка по совокупности нескольких параметров делаю. Но системы уровня RAX — это совсем другое дело. Там на порядки параметров и состояний больше и пермалывающих это всё исполняемых сущностей.

Владимир Лось писал(а):

А Вы интересовались сборщиком мусора Эрланга? Он хоть и «soft real time», но всё же там вроде более-менее параметры должны быть. На каждый процесс — собственный GC, так что время каждой сборки незаметно и почти не сказывается на отзывчивость системы. Ну, может там и нет гарантии в +-5% (я просто не в курсе), но и на несколько порядков задержек тоже быть не должно…

Винда — тоже «мягкое» РВ.
Вот у нас смежники свою часть на Вин98 сделали. Их тесты все классно проходили. Но это когда на её экран просто смотрели, без оператора-прапорщика за пультом… А тут ему понадобилось посмотреть монитор производительности системы. И, именно, во время прохождения КА и съёма с него телеметрии и данных… Я вижу, что у меня ними по RS232 связь пропала, кричу: «я вас не вижу, пакеты не идут!», а у них лица вытянутые: триста страниц обоснования, как это выгодно было делать на винде (привычночть сред разработки, системы исполнения, «проверенный годами эксплуатации» код Оси…) и — одно нажатие мышки прапором… :)
Ну ладно, здесь я согласен, был фактор в виде прапорщика, а вдруг винде захочется со своп-файлом разобраться?! Вот тока не надо говорить «что такого никогда не будет»… Это — самая мрачная фраза из моего опыта программирования! Когда заказчик, начальство или коллеги её произносят, у меня инстинктивно, пардон, все сфинкторы сжимаются и хочется со свечкой по углам обойти и святой водой окропить… В данном случае, при имеющихся СМ и НЕМИНУЕМОСТИ наступания момента, когда вашей системе на ФЯ не хватит памяти, ОСи сильно понадобится поработать со страницами на винте… Со всеми вытекающими…
Я буду согласен, когда у меня ОЗУ безмерно будет, применять Лисп/Схему… Но! Опять-таки, ну нет строго определённых по времени работы алгоритмов сборки мусора… Хоть ты тресни! Придумают – хвала, а пока, что даже Эрланг прячется полустыдливо за словосочетанием «мягкое РВ»… Ага, — «мягкое»… Тока последствия бывают сильно жёсткими.

[Обновление 2015-10-31] Дополнительная трансляция, измененная из StackOverflowПочтовыйПараграф:

Чашка! В нашей компании фамилия сотрудника Null.При использовании его фамилии в качестве термина запроса все приложения запросов сотрудников вылетали из строя! Что я должен делать?

В 1965 году кто-то допустил худшую ошибку в области информатики. Ошибка уродливее, чем обратная косая черта в Windows, более странная, чем ===, более распространенная, чем PHP, более неудачная, чем CORS, и более тревожная, чем дженерики Java. XMLHttpRequest, более сложный для понимания, чем препроцессор C, более подверженный фрагментации, чем MongoDB, и более прискорбный, чем UTF-16.

«Я называю нулевую ссылку своей ошибкой на миллиард долларов. Она была изобретена в 1965 году, когда я разработал первую всеобъемлющую систему ссылочных типов на объектно-ориентированном языке (АЛГОЛ W). Моя цель — гарантировать, что использование всех ссылок абсолютно безопасно, компилятор проверит автоматически. Но я не смог устоять перед соблазном добавить нулевые ссылки только потому, что это очень легко реализовать. Это вызвало бесчисленное количество ошибок, уязвимостей и систем. Авария могла привести к убыткам в миллиарды долларов в следующий раз. 40 лет. В последние годы люди начали использовать различные программы анализа программ, такие как Microsoft PREfix и PREfast, чтобы проверять ссылки и предупреждать, если существует риск ненулевого значения. Новые языки программирования, такие как Spec #, имеют ввел объявление ненулевых ссылок. Это решение, которое я отверг в 1965 году »-« Нулевые ссылки: ошибка на миллиард долларов »Тони Хоар, лауреат премии Тьюринга

В ознаменование 50-летия нулевой ошибки мистера Хора в этой статье объясняется, что такое null, почему это так ужасно и как этого избежать.

Проще говоря: NULL — это значение, которое не является значением. Вот и проблема.

Эта проблема усугубилась в самом популярном языке всех времен, и теперь у него много имен: NULL, nil, null, None, Nothing, Nil и nullptr. У каждого языка есть свои нюансы.

Некоторые из проблем, вызванных NULL, связаны только с конкретным языком, в то время как другие универсальны; некоторые — просто разные аспекты проблемы.

NULL…

  1. Тип Subversion
  2. Это грязно
  3. Это особый случай
  4. Сделать API хуже
  5. Сделать неправильные языковые решения хуже
  6. Сложно отлаживать
  7. Не сочетается

1. Тип подрывной деятельности NULL

Статически типизированные языки могут проверять использование типов в программе без фактического выполнения программы и обеспечивать определенные гарантии поведения программы.

Например, в Java, если я напишуx.toUppercase(), Компилятор проверитx тип. в случае x Является String, То проверка типа прошла успешно; еслиx Является Socket, Тогда проверка типа не удалась.

При написании большого и сложного программного обеспечения статическая проверка типов является мощным инструментом. Но для Java эти отличные проверки во время компиляции имеют фатальный недостаток: любая ссылка может быть нулевой, а вызов метода нулевого объекта приведет кNullPointerException. и так,

  • toUppercase()Может быть произвольноStringВызов объекта. Пока неStringНулевой.
  • read()Может быть произвольноInputStreamВызов объекта. Пока неInputStreamНулевой.
  • toString()Может быть произвольноObjectВызов объекта. Пока неObjectНулевой.

Java — не единственный язык, вызывающий эту проблему; многие другие системы типов имеют те же недостатки, включая, конечно, язык AGOL W.

В этих языках NULL выходит за рамки проверки типов. Он незаметно превзошел проверку типа, дождался времени выполнения и, наконец, выпустил сразу большое количество ошибок. NULL — это ничто, и в то же время это ничто.

2. NULL беспорядочный

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

Программисты на Java пишут о риске синдрома запястного канала

if (str == null || str.equals(«»)) {

}

И добавляем в C #String.IsNullOrEmptyОбычная грамматика

if (string.IsNullOrEmpty(str)) {

}

Черт!

Каждый раз, когда вы пишете код, который путает пустые строки с пустыми строками, команда Guava плачет. -Google Guava

хорошо сказано. Но когда ваша система типов (например, Java или C #) допускает NULL везде, вы не можете надежно исключить возможность NULL и неизбежно где-то запутаетесь.

Возможность нулевого повсюду вызвала такую ​​проблему, добавлена ​​Java 8@NonNullМарк, попробуй ретроспективно устранить этот дефект в системе его типов.

3. NULL — это особый случай.

Учитывая, что NULL не является значением, но также играет роль значения, NULL, естественно, становится предметом различных специальных методов обработки.

указатель

Например, рассмотрим следующий код C ++:

char c = ‘A’;

char *myChar = &c;

std::cout << *myChar << std::endl;

myChar Является char *, Что означает, что это указатель, то есть сохранить адрес памяти вcharв. Компилятор это проверит. Следовательно, следующий код недействителен:

char *myChar = 123; // compile error

std::cout << *myChar << std::endl;

Потому что123Нет гарантии, что это одинchar, Итак, компиляция не удалась. В любом случае, если поменять номер на0(0 является NULL в C ++), тогда его можно скомпилировать с помощью:

char *myChar = 0;

std::cout << *myChar << std::endl; // runtime error

с 123То же самое, NULL на самом деле неcharадрес. Но на этот раз компилятор все еще позволяет его компилировать, потому что0(NULL) — особый случай.

Нить

Есть еще один особый случай, который встречается в символьной строке, оканчивающейся на NULL в языке C. Это немного отличается от других примеров, потому что здесь нет указателей или ссылок. Однако идея о том, что это не ценность, а также играет роль ценности, все еще существует, здесь нетcharНо это играетcharСуществуют в виде.

Строка C представляет собой последовательность байтов и заканчивается байтом NUL (0).

Следовательно, каждый символ строки C может быть любым из 256 байтов, кроме 0 (то есть символа NUL). Это не только делает длину строки линейной операцией времени; что еще хуже, это означает, что строки C не могут использоваться в ASCII или расширенном ASCII. Вместо этого они могут использоваться только для ASCIIZ, который обычно не используется.

Исключение одного символа NUL вызвало бесчисленное количество ошибок: странное поведение API, уязвимости безопасности и переполнение буфера.

NULL — это наихудшая ошибка в строках C; точнее, строка, заканчивающаяся на NUL, являетсяСамый дорогойОдин байтошибка。

4. NULL делает API плохим

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

Хранилище ключей и значений

Предположим, мы создаем класс Ruby, который будет действовать как хранилище значений ключей. Это может быть кеш, интерфейс для базы данных ключ-значение и т. Д. Создадим простой и универсальный API:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class Store

    ##

    # associate key with value

    #

    def set(key, value)

        ...

    end

    ##

    # get value associated with key, or return nil if there is no such key

    #

    def get(key)

        ...

    end

end

Мы можем представить подобные классы на многих языках (Python, JavaScript, Java, C # и т. Д.).

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

Чтобы повысить производительность, мы будем использовать локальное хранилище в качестве кеша для сопоставления имени человека с его номером телефона.

store = Store.new()

store.set(‘Bob’, ‘801-555-5555’)

store.get(‘Bob’) # returns ‘801-555-5555’, which is Bob’s number

store.get(‘Alice’) # returns nil, since it does not have Alice

Однако у некоторых людей нет номера телефона (т.е. их номер телефона равен нулю). Мы по-прежнему будем кэшировать эту информацию, поэтому нам не нужно ее пополнять позже.

store = Store.new()

store.set(‘Ted’, nil) # Ted has no phone number

store.get(‘Ted’) # returns nil, since Ted does not have a phone number

Но теперь это означает, что наши результаты неоднозначны! Это может означать:

  1. Этого человека нет в кеше (Алиса)
  2. Этот человек существует в кеше, но у него нет номера телефона (Том)

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

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

Используйте одинcontains()Способ ремонтаStoreКласс может быть полезным. Но это вводит повторные поиски, что приводит к снижению производительности и условиям гонки.

Двойные неприятности

У JavaScript такая же проблема, но она возникает вКаждый объект

Если атрибуты объекта не существуют, JS вернет значение, указывающее, что у объекта отсутствуют атрибуты. Разработчики JavaScript выбрали это значение равным нулю.

Они беспокоятся о том, когда свойство существует и для него установлено значение null. «Талант» в том, что JavaScript добавляет undefined, чтобы отличать свойства с нулевыми значениями от несуществующих свойств.

Но что, если свойство существует и его значение не определено? Странно то, что JavaScript остановился на этом и не предоставил «super undefined».

JavaScript предлагает не только одну, но и две формы NULL.

5. NULL ухудшает неправильные языковые решения

Java незаметно преобразует ссылки в основные типы. Добавление null делает ситуацию еще более странной.

Например, следующий код не компилируется:

int x = null; // compile error

Этот код компилируется и передается:

Integer i = null;

int x = i; // runtime error

Хотя он сообщит, когда код запускаетсяNullPointerException ошибка.

Обращение к нулевому методу члена достаточно плохо; еще хуже, если вы никогда не видели вызываемый метод.

6. NULL сложно отлаживать.

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

#include <iostream>

struct Foo {

    int x;

    void bar() {

        std::cout << «La la la» << std::endl;

    }

    void baz() {

        std::cout << x << std::endl;

    }

};

int main() {

    Foo *foo = NULL;

    foo->bar(); // okay

    foo->baz(); // crash

}

Когда я использую gcc для компиляции приведенного выше кода, первый вызов выполняется успешно, а второй — нет.

Почему?foo->bar()Это известно во время компиляции, поэтому компилятор избегает поиска в виртуальной таблице во время выполнения и преобразует его в статический вызов, аналогичныйFoo_bar(foo), Возьмите это как первый параметр. Потому чтоbarНет косвенной ссылки на указатель NULL, поэтому он работает успешно. НоbazИмеется ссылка на указатель NULL, который вызывает segfault.

Но мы будемbarСтаньте виртуальной функцией. Это означает, что его реализация может быть отменена подклассом.

    ...

    virtual void bar() {

    ...

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

int main() {

    Foo *foo = NULL;

    foo->bar(); // crash

    foo->baz();

}

NULL сделалmainДля программистов функций отладка этого кода становится очень сложной и неинтуитивной.

Действительно, ссылка на NULL не определена в стандарте C ++, поэтому технически мы не должнылюбая ситуацияУдивлен. Кроме того, это непатологический, распространенный, очень простой и реальный пример.Этот пример является одним из многих примеров, когда NULL на практике непостоянен.

7. NULL нельзя комбинировать

Языки программирования построены на возможности компоновки: способности применять одну абстракцию к другой. Это может быть наиболее важной особенностью любого языка, библиотеки, фреймворка, модели, API или шаблона проектирования: возможность использовать другие функции ортогонально.

Фактически, компоновка — действительно основная проблема, стоящая за многими из этих проблем. Например,StoreНет возможности компоновки между API, возвращающим nil несуществующему значению, и сохранением nil для несуществующего телефонного номера.

Для C #NullableЧтобы разобраться с некоторыми проблемами, связанными с NULL. Вы можете включить в тип необязательность (пустоту).

int a = 1;     // integer

int? b = 2;    // optional integer that exists

int? c = null; // optional integer that does not exist

Но это вызвало серьезный недостаток, то естьNullableНе применимо ни к какомуT. Применяется только к непустымT. Например, это не сделаетStoreПроблема исправлена ​​любым способом.

  1. Прежде всего stringМожет быть пустым; вы не можете создать непустойstring
  2. Даже еслиstringНе пусто, поэтому создайтеstring? Может быть, но все равно нельзя исключить неоднозначность нынешней ситуации. Нет string??

решение

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

не так! У вас может быть полный язык программирования без NULL. Проблема с NULL — это нечисловое значение, дозорный, особый случай, который концентрируется на всем остальном.

Вместо этого нам нужно, чтобы сущность содержала некоторую информацию о (1) содержит ли она значение и (2) содержащееся значение, если есть содержащееся значение. И эта сущность должна уметь «содержать» любой тип. Это идея Haskell’s Maybe, Java’s Optional, Swift’s Optional и т. Д.

Например, в ScalaSome[T]Сохранить одинTЗначение типа.NoneНет никакой ценности. Оба этиOption[T]Подтипы этих двух подтипов могут иметь значение или не иметь значения.

Читатели, не знакомые с Maybes / Options, могут подумать, что мы заменили одну форму (NULL) другой формой (None). Но есть одно отличие — обнаружить его непросто, но оно очень важно.

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

В динамически типизированном языке вы не можете спутать использование Maybes / Options с содержащимися значениями.

Вернемся к предыдущемуStore, Но на этот раз можно использовать рубин. Если есть значение, тоStoreКласс возвращается со значениемSome, В противном случае вернутьNone. Для телефонных номеровSomeЭто номер телефона,NoneУказывает на отсутствие номера телефона. Так что естьДва уровня присутствия / отсутствия:Внешний MaybeУказывает, что он существует вStoreВ; внутреннийMaybeУказывает номер телефона, соответствующий этому имени. Мы успешно объединили несколькоMaybe, Это то, что мы не можем сделать с nil.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

cache = Store.new()

cache.set(‘Bob’, Some(‘801-555-5555’))

cache.set(‘Tom’, None())

bob_phone = cache.get(‘Bob’)

bob_phone.is_some # true, Bob is in cache

bob_phone.get.is_some # true, Bob has a phone number

bob_phone.get.get # ‘801-555-5555’

alice_phone = cache.get(‘Alice’)

alice_phone.is_some # false, Alice is not in cache

tom_phone = cache.get(‘Tom’)

tom_phone.is_some # true, Tom is in cache

tom_phone.get.is_some #false, Tom does not have a phone number

Существенная разница в том, чтоБольше не существует союза между NULL и любым другим статически типизированным или динамически предполагаемым типом., Больше не существует объединения между существующим значением и несуществующим значением.

Используйте Maybes / Опции

Давайте продолжим обсуждение кода без NULL. Предположим, в Java 8+ у нас есть целое число, оно может существовать, а может и не существовать, и если оно существует, мы его распечатываем.

Optional<Integer> option = ...

if (option.isPresent()) {

   doubled = System.out.println(option.get());

}

Замечательно. Но большая частьMaybe/OptionalРеализация, включая Java, поддерживает более практичный метод:

option.ifPresent(x -> System.out.println(x));

// or option.ifPresent(System.out::println)

Этот практический метод не только более краткий, но и более безопасный. Необходимо помнить, что если это значение не существует, тоoption.get()Произойдет ошибка. В предыдущем примереget()Получите одинifЗащита. В этом примереifPresent()Это полностью устраняет наши опасенияget()Необходимость. Это делает код явно свободным от ошибок, а не без ошибок.

Параметры можно рассматривать как набор с максимальным значением 1. Например, если есть значение, то мы можем умножить его на 2, в противном случае оставить его пустым.

При желании мы можем выполнить операцию, которая возвращает необязательное значение и стремится «сгладить» результат.

option.flatMap(x -> methodReturningOptional(x))

Если такового не существует, мы можем указать значение по умолчанию:

В целом,Maybe/OptionРеальная стоимость

  1. Избавьтесь от небезопасных предположений о существовании и несуществовании ценностей.
  2. Управляйте дополнительными данными проще и безопаснее
  3. Явно заявляйте о любых предположениях о небезопасном существовании (например,.get()Метод)

Не быть NULL!

NULL — ужасный недостаток дизайна, постоянная и неизмеримая боль. Лишь немногим языкам удается избежать его ужаса.

Если вы все же выберете язык с NULL, то, по крайней мере, сознательно избегайте этой неприятности в своем собственном коде и используйте эквивалентMaybe/Option

NULL в распространенных языках:

«Балл» определяется на основании следующих критериев:

редактировать

счет

Не относитесь слишком серьезно к «оценкам» в приведенной выше таблице. Настоящая проблема состоит в том, чтобы суммировать статус NULL на разных языках и представить альтернативы NULL, а не ранжировать часто используемые языки.

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

  • Пример: Haskell’sForeign.Ptr.nullPtrОн используется в FFI (интерфейсе внешних функций) для маршалинга значений в Haskell и обратно.
  • Пример: SwiftUnsafePointerДолжен быть сunsafeUnwrapИли же!использовать вместе.
  • Контрпример: Scala, несмотря на то, что обычно избегает null, по-прежнему обрабатывает null, как Java, для улучшения взаимодействия.val x: String = null

Когда NULL ОК?

Стоит отметить, что при сокращении циклов ЦП специальное значение того же размера, например 0 или NULL, может быть очень полезным, жертвуя качеством кода на производительность. Когда это действительно важно, это удобно для низкоуровневых языков, таких как C, но на самом деле стоит оставить все как есть.

Настоящая проблема

Более распространенная проблема с NULL — это контрольные значения: эти значения такие же, как и другие значения, но имеют совершенно другое значение. Из indexOfХорошим примером является возврат индекса целого числа или целого числа -1. Другой пример — строки, оканчивающиеся на NULL. В этой статье основное внимание уделяется NULL, что придает его универсальность и реальное влияние, но так же, как Саурон — всего лишь слуга Моргота, NULL — это просто форма базовой проблемы дозорного устройства.

Joker burning a huge pile of money

Tony Hoare, the creator of NULL, now refers to NULL as The Billion Dollar Mistake. Even though NULL Reference Exceptions continue to haunt our code to this day, we still choose to continue using it.

And for some reason JavaScript decided to double down on the problems with null by also creating undefined.

Today I would like to demonstrate a solution to this problem with the Maybe.

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. — Tony Hoare

Do not underestimate the problems of NULL

Before you have even finish reading this article… I can already sense it, your desire to hit PAGE DOWN, rush straight to the comment section and blast out a «but NULL is never a problem for ME». But please pause, slow down, read and contemplate.

8 of 10 errors from Top 10 JavaScript errors from 1000+ projects (and how to avoid them) are null and undefined problems. Eight. Out. Of. Ten.

To underestimate NULL is to be defeated by NULL.

Null Guards

Because of the problems null brings with it, we have to constantly guard our code from it. Unguarded code might look something like this:

const toUpper = string => string.toUpperCase()

This code is susceptible to NULL Reference Exceptions.

toUpper(null) //=> ​​Cannot read property 'toUpperCase' of null​​

So we are forced to guard against null.

const toUpper = string => {
  if (string != null) {
//    --------------
//                   
//                    null guard
    return string.toUpperCase()
  }
}

But this quickly becomes verbose as everywhere that may encounter null has to be guarded.

const toUpper = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.toUpperCase()
  }
}

const toLower = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.toLowerCase()
  }
}

const trim = string => {
  if (string != null) {
//    --------------
//                   
//                    duplication
    return string.trim()
  }
}

If we think about a values as having a one-to-many relationship with code that may access it, then it makes more sense to place the guards on the one and not on the many.

Nullable Types

The .NET Framework 2.0 introduced Nullable Types into the .NET language. This new Nullable value, could be set to null without the reference being null. This meant if x was a Nullable Type, you could still do things like x.HasValue and x.Value without getting a NullReferenceException.

int? x = null
if (x.HasValue)
{
    Console.WriteLine($"x is {x.Value}")
}
else
{
    Console.WriteLine("x does not have a value")
}

The Maybe

The Maybe is similar to a Nullable Type. The variable will always have a value, and that value might represent a null, but it will never be set to null.

For these examples, I’ll be using the Maybe from MojiScript. (Also checkout monet and Sanctuary, Folktale for other Maybes). Use the following import:

import { fromNullable } from "mojiscript/type/Maybe"

The Maybe is a union type of either a Just or a Nothing. Just contains a value and Nothing is well… nothing.

But now the value is all wrapped up inside of the Maybe. To access the value of a Maybe, you would have touse a map function. Fun to Google: map is what makes the Maybe type a Functor.

If you are getting that feeling that you have seen this somewhere before that is because this exactly how a Promise works. The difference is Promise uses then and Maybe uses Map.

const promise = Promise.resolve(888)
const maybe = Just(888)

promise.then(double)
maybe.map(double)

Same same but different.

const toUpper = string => string.toUpperCase()

Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing

Notice how in both cases above, the toUpper function no longer throws an Error. That is because we are no longer calling toUpper directly with a String, but instead mapping it with our Maybe.

If we convert all types within our application to use a Maybe, then all null guards are no longer necessary.

The null is now guarded in a single place, in the Maybe type, instead of being sprinkled throughout the application, wherever the value might be accessed.

The Maybe is a guard on the one instead of the many!

Neo vs many agent Smiths

Getting in and out of Maybes

But what about the times when we are not in control of the code, when we must send or receive a null value? Some examples might be 3rd party libraries that will return a null or libraries that will require passing null as an argument.

In these cases, we can convert a null value to a Maybe using fromNullable and we can convert back to a nullable value using fromMaybe.

import { fromMaybe, fromNullable } from "mojiscript/type/Maybe"

// converting nullable values to a Maybe
fromNullable(undefined) //=> Nothing
fromNullable(null) //=> Nothing
fromNullable(123) //=> Just (123)
fromNullable("abc") //=> Just ("abc")

// converting Maybe to a nullable type
fromMaybe(Just("abc")) //=> 'abc'
fromMaybe(Nothing) //=> null

You could als guard a single function like this:

const toUpper = string =>
  fromNullable(string).map(s => s.toUpperCase()).value

But that is a little verbose and it’s much better to expand the safety of the Maybe type to the entire application. Put the guards in place at the gateways in and out of your application, not individual functions.

One example could be using a Maybe in your Redux.

// username is a Maybe, initially set to Nothing.
const initalState = {
  username: Nothing
}

// your reducer is the gateway that ensures the value will always be a maybe.
const reducer = (state = initialState, { type, value }) =>
  type === 'SET_USERNAME'
    ? { ...state, username: fromNullable(value) }
    : state

// somewhere in your render
render() {
  const userBlock = this.props.username.map(username => <h1>{username}</h1>)
  const noUserBlock = <div>Anonymous</div>

  return (
    <div>
    {fromMaybe (noUserBlock) (userBlock)}
    </div>
  )
}

JavaScript Type Coercion

MojiScript’s Maybe can use JavaScript’s implicit and explicit coercion to it’s advantage.

Maybe can be implicity coerced into a String.

// coercing to a String
console.log("a" + Just("b") + "c") //=> 'abc'
console.log("a" + Nothing + "c") //=> 'ac'

Maybe can be explicity coerced into a Number.

Number(Just(888)) //=> 888
Number(Nothing) //=> 0

Maybe can even be stringified.

const data = {
  id: Nothing,
  name: Just("Joel")
}

JSON.stringify(data)
//=> {"id":null,"name":"Joel"}

Accessing Nested Objects

Let’s take a look at the common task of accessing nested objects.

We’ll use these objects. One is lacking an address, which can yield nulls. Gross.

const user1 = {
  id: 100,
  address: {
    address1: "123 Fake st",
    state: "CA"
  }
}

const user2 = {
  id: 101
}

These are common ways to access nested objects.

user1.address.state //=> 'CA'
user2.address.state //=> Error: Cannot read property 'state' of undefined

// short circuit
user2 && user2.address && user2.address.state //=> undefined

// Oliver Steel's Nested Object Pattern
((user2||{}).address||{}).state //=> undefined

Prettier seems to hate both of those techniques, turning them into unreadable junk.

Now let’s try accessing nested objects with a Maybe.

import { fromNullable } from 'mojiscript/type/Maybe'

const prop = prop => obj =>
  fromNullable(obj).flatMap(o => fromNullable(o[prop]))

Just(user1)
  .flatMap(prop('address))
  .flatMap(prop('state)) //=> Just ("CA")

Just(user2)
  .flatMap(prop('address))
  .flatMap(prop('address)) //=> Nothing

A lot of this boiler plate can be reduced with some helper methods.

import pathOr from 'mojiscript/object/PathOr'
import { fromNullable } from 'mojiscript/type/Maybe'

const getStateFromUser = obj =>
  fromNullable(pathOr (null) ([ 'address', 'state' ]) (obj))

Just(user1).map(getStateFromUser) //=> Just ("CA")
Just(user2).map(getStateFromUser) //=> Nothing

Decoupled map function

A Map can also be decoupled from Maybe. There are many libs that have a map function, like Ramda, but I’ll be using the one from MojiScript for this example.

import map from 'mojiscript/list/map'

const toUpper = string => string.toUpperCase()

Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing
import map from 'mojiscript/list/map'

const toUpper = string => string.toUpperCase()

map (toUpper) (Just ("abc")) //=> Just ('ABC')
map (toUpper) (Nothing) //=> Nothing

This was getting far too big for this section, so it has been broken out into it’s own article here: An introduction to MojiScript’s enhanced map

Heavy Lifting

Lifting is a technique to apply Applicatives to a function. In English that means we can use «normal» functions with our Maybes. Fun to Google: ap is what makes the Maybe type an Applicative.

This code will use liftA2, A for Applicative and 2 for the number of arguments in the function.

import liftA2 from "mojiscript/function/liftA2"
import Just from "mojiscript/type/Just"
import Nothing from "mojiscript/type/Nothing"

const add = x => y => x + y
const ladd = liftA2 (add)

add (123) (765) //=> 888

ladd (Just (123)) (Just (765)) //=> Just (888)
ladd (Nothing) (Just (765)) //=> Nothing
ladd (Just (123)) (Nothing) //=> Nothing

Some things to notice:

  • The function add is curried. You can use any curry function to do this for you.
  • add consists of 2 parameters. If it was 3, we would use liftA3.
  • All arguments must be a Just, otherwise Nothing is returned.

So now we do not have to modify our functions to understand the Maybe type, we can use map and also lift to apply the function to our Maybes.

Continue Learning: Functors, Applicatives, And Monads In Pictures does an incredible job of explaining this and more!

Maybe Function Decorator

There are times when you would like to guard a single function against NULL. That is where the maybe Function Decorator comes in handy.

const maybe = func => (...args) =>
  !args.length || args.some(x => x == null)
    ? null
    : func(...args)

Guard your functions against null with the maybe function decorator:

const toUpper = string => string.toUpperCase()
const maybeToUpper = maybe(toUpper)
maybeToUpper("abc") //=> 'ABC'
maybeToUpper(null) //=> null

Can also be written like this:

const toUpper = maybe(string => string.toUpperCase())

Learn more about Function Decorators:

  • Function decorators: Transforming callbacks into promises and back again
  • Functional JavaScript: Function Decorators Part 2

TC39 Optional Chaining for JavaScript

This is a good time to mention the TC39 Optional Chaining Proposal that is currently in Stage 1.

Optional Chaining will allow you to guard against null with a shorter syntax.

// without Optional Chaining
const toUpper = string => string && string.toUpperCase()

// with Optional Chaining
const toUpper = string => string?.toUpperCase()

Even with Optional Chaining, the guards are still on the many and not the one, but at least the syntax is short.

Wisdoms

  • To underestimate NULL is to be defeated by NULL.
  • 8 out of the 10 top 10 errors are NULL and undefined errors.
  • If we think about a values as having a one-to-many relationship with code that may access it, then it makes more sense to place the guards on the one and not on the many.
  • It is possible to completely eliminate an entire class of bugs (NULL Reference Exceptions) by eliminating null.
  • Having NULL Reference Exceptions in your code is a choice.

End

Have questions or comments? I’d love to hear them!

Hop over to the MojiScript Discord chat and say hi!

This turned out a little longer than I originally thought it would. But this is a subject that is hard to sum up into a single article.

You can also use the Maybe with MojiScript’s map. Read more about how awesome MojiScript’s map is here…

My articles are very Functional JavaScript heavy, if you need more FP, follow me here, or on Twitter @joelnet!

Cheers!

В мире Javascript и как с этим работать

Какие ошибки в мире программного обеспечения обходятся в миллиарды долларов?

  • По словам Тони Хоара, ошибка 2000 года, класс ошибок, связанных с хранением и форматированием данных календаря, обойдется чуть менее чем в 4 миллиарда долларов.
  • CodeRed Virus, компьютерный червь, внедрившийся в компании по всему миру, вывел из строя все сети. Прерывание бизнеса и всего обычного банковского дела обошлось мировой экономике в 4 миллиарда долларов.
  • Null – ошибочное изобретение британского ученого-компьютерщика Тони Хоара (наиболее известного благодаря своему алгоритму быстрой сортировки) в 1964 году, который изобрел нулевые ссылки. как его «ошибка на миллиард долларов».

Кто придумал «обнулить» ошибку на миллиард долларов и почему?

Я называю это своей ошибкой на миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я разрабатывал первую всеобъемлющую систему типов для ссылок в объектно-ориентированном языке (ALGOL W). Моя цель состояла в том, чтобы гарантировать, что любое использование ссылок должно быть абсолютно безопасным, с автоматической проверкой компилятором. Но я не мог устоять перед искушением добавить нулевую ссылку просто потому, что это было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили миллиарды долларов боли и ущерба за последние сорок лет. — Тони Хоар

Ссылка на нулевой указатель может быть плохой идеей. Сравнивая ссылку нулевого указателя с неразборчивым в связях прелюбодеем, он заметил, что нулевое присвоение для каждого холостяка, представленного в объектной структуре, «будет выглядеть полиаморно женатым на одном и том же человеке Null. — Эдсгар Джикстра

Что такое ошибка на миллиард долларов в контексте мира Javascript?

У нас есть два кандидата в мире Javascript, подпадающие под эту категорию:

1⃣️ Нет

Значение null записывается литералом: null. null не является идентификатором свойства глобального объекта, как может быть undefined. Вместо этого null выражает отсутствие идентификации, указывая на то, что переменная не указывает ни на какой объект. В API-интерфейсах null часто извлекается в месте, где объект можно ожидать, но объект не является релевантным.

2⃣️ Не определено

undefined — это свойство глобального объекта. То есть это переменная в глобальной области видимости. Начальное значение undefined — это примитивное значение undefined.

примитивные типы данных Javascript (ES2020),

  1. Boolean
  2. Null
  3. Undefined
  4. Number
  5. String
  6. BigInt
  7. Symbol

Null и Undefined в Javascript называются «нулевыми» (ложными) значениями.

Ложные значения: Undefined, null, 0, NaN, empty string‘’, false

Нулевой или неопределенный

Несмотря на то, что поведение обоих значений является ложным, если кто-то думает Null vs Undefined как Declared vs Undeclared, это не совсем так!

Undefined может быть как объявленным, так и необъявленным.

А как насчет Null?

У Null есть свои проблемы, с которыми нужно разобраться… отлично! Давайте посмотрим на это,

Ну, typeof null == “object” — это ошибка 25-летней давности, начиная с первой версии Javascript.

В первой версии JavaScript значения хранились в 32-битных единицах, которые состояли из небольшого тега типа (1–3 бита) и фактических данных значения. Теги типа хранились в младших битах единиц. Их было пятеро:

  • 000: object. Данные являются ссылкой на объект.
  • 001: int. Данные представляют собой 31-битное целое число со знаком.
  • 010: double. Данные являются ссылкой на двойное число с плавающей запятой.
  • 100: string. Данные являются ссылкой на строку.
  • 110: boolean. Данные являются логическими.

Из исходного кода jsapi.h, (ссылка)

#define JSVAL_OBJECT      0x0     /* untagged reference to object */
#define JSVAL_INT         0x1     /* tagged 31-bit integer value */
#define JSVAL_DOUBLE      0x2     /* tagged reference to double */
#define JSVAL_STRING      0x4     /* tagged reference to string */
#define JSVAL_BOOLEAN     0x6     /* tagged boolean value */

Два значения были особенными:

  • undefined(JSVAL_VOID), представляет собой целое минус (-) JSVAL_INT_POW2 (30), то есть число вне целочисленного диапазона
  • null(JSVAL_NULL) — это указатель NULL машинного кода, тег типа объекта плюс ссылка, равная нулю(OBJECT_TO_JSVAL(0)).
#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
#define JSVAL_NULL              OBJECT_TO_JSVAL(0)
#define JSVAL_ZERO              INT_TO_JSVAL(0)
#define JSVAL_ONE               INT_TO_JSVAL(1)
#define JSVAL_FALSE             BOOLEAN_TO_JSVAL(JS_FALSE)
#define JSVAL_TRUE              BOOLEAN_TO_JSVAL(JS_TRUE)

Теперь, когда рассмотрел его тег типа, а тег типа сказал объект. («источник»)

  1. Строка № 10, сначала проверяет, является ли значение v undefined(VOID).
  2. Следующая проверка в строке № 12 проверяет наличие объекта JSVAL_IS_OBJECT,
  3. Кроме того, вызывает функциональный класс (строка № 18, 19).
  4. И, следовательно, оценивается как Object
  5. Впоследствии есть проверки на число, строку и логическое значение, даже не проверка на Null

Возможно, это одна из причин, по которой первая версия JavaScript была завершена за 10 дней. Позже, продолжал жить с этой проблемой, а не исправлять из-за ее тесно связанной логики в исходном исходном коде. Исправление может привести к поломке многих вещей в коде.

Работа с Null и Undefined

Начиная с ES2020, у нас есть лучший способ обработки значений Nullish в Javascript. Для текущих проектов того же можно добиться с помощью Babel.js и/или Typescript.

  • Необязательная цепочка (?.)

Также известен как безопасная оценка или оператор безопасности.

Длинные цепочки обращений к свойствам в Javascript приводят к ошибкам, вызывающим сбои, поскольку можно получить null или undefined (“nullish” values). Проверка наличия свойства в глубоко вложенной структуре — утомительная задача, например, рассмотрим ответ API погоды,

Чтобы получить данные о значении «Гроза», используются три подхода:

Теперь из ES2020 или TypeScript 3.7 или @babel/plugin-proposal-optional-chaining поддерживает необязательную цепочку, где можно написать так:

  • Нулевое объединение (??)

Оператор Nullish Coalescing (??) действует очень похоже на оператор ||, за исключением того, что мы используем не ложные значения, а nullish, что означает, что значение строго равно null или undefined.

Поддерживается с ES2020, Typescript 3.7 и @babel/plugin-proposal-nullish-coalescing-operator

Избегайте Null всеми возможными способами

Шаблон NullObject

❌ НЕПРАВИЛЬНО

✅ ВПРАВО, Шаблон NullObject

Заключительные слова

(любезно предоставлено: Максмиллиано Контьери)

Программисты используют Null как разные флаги. Он может намекать на отсутствие, неопределенность, значение, ошибку или ложное значение (значение “Nullish”). Множественная семантика приводит к ошибкам связывания.

Проблемы

  • Связь между вызывающими и отправляющими
  • Несоответствие между вызывающими и отправляющими
  • Если/переключатель/случай загрязняет окружающую среду
  • Null не полиморфен реальным объектам, поэтому NullPointerException (TypeError: null or undefined has no properties)
  • Null не существует в реальном мире. Таким образом, нарушается принцип биекции

Решения

  • Избегайте нуля
  • Использовать Шаблон нулевого объекта
  • Используйте необязательно

Исключения

  • API, базы данных, внешние системы, где существует NULL

Поддержка Линтера

Добавьте no-null и no-undef к вашему .eslintrc

Звук Нулевая Безопасность

В современных языках введена Sound Null Safetyили иначе известная как Void Safety для более безопасного и удобного кода, что означает, что по умолчанию язык предполагает, что переменные являются ненулевыми значениями, если явно не указано иное. .

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

Например, Dart, Swift и другие следуют Sound Null Safety.

использованная литература

Java и null неразрывно связаны. Трудно найти Java-программиста, который не сталкивался с NullPointerException. Если даже автор понятия нулевого указателя признал его «ошибкой на миллиард долларов», почему он сохранился в Java? null присутствует в Java уже давно, и я уверен, что разработчики языка знают, что он создает больше проблем, чем решает. Это удивительно, ведь философия Java — делать вещи как можно более простыми. Если разработчики отказались от указателей, перегрузки операторов и множественного наследования, то почему они оставили null? Я не знаю ответа на этот вопрос. Однако не имеет значения, насколько много критики идет в адрес null в Java, нам придется с этим смириться. Вместо того, чтобы жаловаться, давайте лучше научимся правильно его использовать. Если быть недостаточно внимательным при использовании null, Java заставит вас страдать с помощью ужасного java.lang.NullPointerException. Наиболее частая причина NullPointerException — недостаточное понимание тонкостей использования null. Давайте вспомним самые важные вещи о нем в Java.

Что такое null в Java

Как мы уже выяснили, null очень важен в Java. Изначально он служил, чтобы обозначить отсутствие чего-либо, например, пользователя, ресурса и т. п. Но уже через год выяснилось, что он приносит много проблем. В этой статье мы рассмотрим основные вещи, которые следует знать о нулевом указателе в Java, чтобы свести к минимуму проверки на null и избежать неприятных NullPointerException.

1. В первую очередь, null — это ключевое слово в Java, как public, static или final. Оно регистрозависимо, поэтому вы не сможете написать Null или NULL, компилятор этого не поймет и выдаст ошибку:

Object obj1 = NULL; // Неверно
Object obj2 = null; // ОК

Эта проблема часто возникает у программистов, которые переходят на Java с других языков, но с современными средами разработки это несущественно. Такие IDE, как Eclipse или Netbeans, исправляют эти ошибки, пока вы набираете код. Но во времена Блокнота, Vim или Emacs это было серьезной проблемой, которая отнимала много времени.

2. Так же, как и любой примитивный тип имеет значение по умолчанию (0 у int, false у boolean), null — значение по умолчанию любого ссылочного типа, а значит, и для любого объекта. Если вы объявляете булеву переменную, ей присваивается значение false. Если вы объявляете ссылочную переменную, ей присваивается значение null, вне зависимости от области видимости и модификаторов доступа. Единственное, компилятор предупредит о попытке использовать неинициализированную локальную переменную. Для того, чтобы убедиться в этом, вы можете создать ссылочную переменную, не инициализируя ее, и вывести ее на экран:

private static Object myObj;
public static void main(String args[]){
    System.out.println("Значение myObj : " + myObj);
}

// Значение myObjc: null

Это справедливо как для статических, так и для нестатических переменных. В данном случае мы объявили myObj как статическую переменную для того, чтобы ее можно было использовать в статическом методе main.

3. Несмотря на распространенное мнение, null не является ни объектом, ни типом. Это просто специальное значение, которое может быть присвоено любому ссылочному типу. Кроме того, вы также можете привести null к любому ссылочному типу:

String str = null; // null можно присвоить переменной типа String, ...
Integer itr = null; // ... и Integer, ...
Double dbl = null;  // ... и Double.

String myStr = (String) null; // null может быть приведен к String ...
Integer myItr = (Integer) null; // ... и к Integer
Double myDbl = (Double) null; // без ошибок.

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

4. null может быть присвоен только переменной ссылочного типа. Примитивным типам — int, double, float или boolean — значение null присвоить нельзя. Компилятор не допустит этого и выдаст ошибку:

int i = null; // type mismatch: cannot convert from null to int
short s = null; //  type mismatch: cannot convert from null to short
byte b = null: // type mismatch: cannot convert from null to byte
double d = null; // type mismatch: cannot convert from null to double

Integer itr = null; // все в порядке
int j = itr; // нет ошибки при компиляции, но NullPointerException при запуске

Итак, попытка присвоения значения null примитивному типу — ошибка времени компиляции, но вы можете присвоить null типу-обертке, а затем присвоить это значение соответствуему примитиву. Компилятор ругаться не будет, но при выполнении кода будет брошено NullPointerException. Это происходит из-за автоматического заворачивания (autoboxing) в Java

5. Любой объект класса-обертки со значением null кинет NullPointerException при разворачивании (unboxing). Некоторые программисты думают, что обертка автоматически присвоит примитиву значение по умолчанию (0 для int, false для boolean и т. д.), но это не так:

Integer iAmNull = null;
int i = iAmNull; // компиляция пройдет успешно

Если вы запустите этот код, вы увидите Exception in thread "main" java.lang.NullPointerException в консоли. Это часто случается при работе с HashMap с ключами типа Integer. Код ниже сломается, как только вы его запустите:

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String args[]) throws InterruptedException {

        Map<Integer, Integer> numberAndCount = new HashMap<>();

        int[] numbers = {3, 5, 7, 9, 11, 13, 17, 19, 2, 3, 5, 33, 12, 5};

        for (int i : numbers) {
            int count = numberAndCount.get(i); // NullPointerException
            numberAndCount.put(i, count++); 
        }
    }

}

Вывод:

Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:14)

Этот код выглядит простым и понятным. Мы ищем, сколько каждое число встречается в массиве, это классический способ поиска дубликатов в массиве в Java. Мы берем предыдущее значение количества, инкрементируем его и кладем обратно в HashMap. Мы полагаем, что Integer позаботится о том, чтобы вернуть значение по умолчанию для int, однако если числа нет в HashMap, метод get() вернет null, а не 0. И при оборачивании выбросит NullPoinerException. Представьте, что этот код завернут в условие и недостаточно протестирован. Как только вы его запустите на продакшен – УПС!

6. Оператор instanceof вернет false, будучи примененным к переменной со значением null или к литералу null:

Integer iAmNull = null;
if (iAmNull instanceof Integer) {
    System.out.println("iAmNull — экземпляр Integer");
} else {
    System.out.println("iAmNull не является экземпляром Integer");
}

Результат выполнения:

iAmNull не является экземпляром Integer

Это важное свойство оператора instanceof, которое делает его полезным при приведении типов.

7. Возможно, вы уже знаете, что если вызвать нестатический метод по ссылке со значением null, результатом будет NullPointerException. Но зато вы можете вызвать по ней статический метод класса:

public class Testing {
    public static void main(String args[]){
        Testing myObject = null;
        myObject.iAmStaticMethod();
        myObject.iAmNonStaticMethod();
    }

    private static void iAmStaticMethod(){
        System.out.println("I am static method, can be called by null reference");
    }

    private void iAmNonStaticMethod(){
        System.out.println("I am NON static method, don't date to call me by null");
    }

}

Результат выполнения этого кода:

I am static method, can be called by null reference
Exception in thread "main" java.lang.NullPointerException
               at Testing.main(Testing.java:5)

8. Вы можете передавать null в любой метод, который принимает ссылочный тип, например, public void print(Object obj) может быть вызван так: print(null). С точки зрения компилятора ошибки здесь нет, но поведение такого кода целиком зависит от реализации метода. Безопасный метод не кидает NullPointerException в этом случае, а тихо завершает работу. Если бизнес-логика позволяет, лучше писать безопасные методы.

9. Вы можете сравнивать null, используя оператор == («равно») и != («не равно»), но не с арифметическими или логическими операторами (такими как «больше» или «меньше»). В отличие от SQL, в Java null == null вернет true:

public class Test {

    public static void main(String args[]) throws InterruptedException {

        String abc = null;
        String cde = null;

        if (abc == cde) {
            System.out.println("null == null is true in Java");
        }

        if (null != null) {
            System.out.println("null != null is false in Java");
        }

        // classical null check
        if (abc == null) {
            // do something
        }

        // not ok, compile time error
        if (abc > null) {
            // do something
        }
    }
}

Вывод этого кода:

null == null is true in Java

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

Перевод статьи «9 Things about Null in Java»

Лера Кудрявцева, Александр Морозов

Лера Кудрявцева, Александр Морозов

Источник изображения

Фотобанк — legion-media.ru

Скриншот «Секрет на миллион» / НТВ

Лера Кудрявцева, Александр Морозов

Источник изображения

Фотобанк — legion-media.ru

Скриншот «Секрет на миллион» / НТВ

Ни один выпуск программы Леры Кудрявцевой не обходится без грубых ошибок. В случае с Александром Морозовым звезда умудрилась мимоходом задеть самое святое — мать юмориста.

В беседе с порталом BLITZ+ Пчелка из «Кривого зеркала» сообщил об оплошности, которую пропустила телеведущая и продюсеры шоу «Секрет на миллион» на канале НТВ. Добродушный герой интервью отнесся к просчету довольно спокойно.

«Одна ошибка была. Они сказали, что мама в Саратове живет, а она в Самаре. Претензий никаких нет, я Леру знаю 150 тысяч лет, мы в прекрасных отношениях, и я ее обожаю».

Время на прочтение
1 мин

Количество просмотров 64K

Молодой человек из Казани обнаружил баг в приложении банка «Ак Барс». Благодаря ошибке он перевёл себе 68 млн рублей за несколько дней, сообщает Security Lab.

Клиент был владельцем двух банковских карт, одна из которых была заблокирована. На заблокированную карту он перевёл сумму денег с букмекерского счёта, вероятно, по ошибке. Банк отклонил операцию, вернув сумму обратно на букмекерский счёт, но эта же сумма появилась и на другой карте клиента. 

Баг позволил провести эти манипуляции огромное количество раз: в течение десяти дней молодой человек совершил 30 тысяч переводов на заблокированную карту на ₽68 млн. Средства каждый раз дублировались на рабочей карте.

Вскоре специалисты банка обнаружили происходящее и заблокировали счёт. Тем не менее, молодой человек успел вывести часть средств, купить машину и скрыться. Сейчас он находится в розыске.

В конце февраля 2022 года «Тинькофф» обвинил клиентов в незаконном обогащении за счёт скачков курса валют. В конце февраля в приложении банка появилась возможность выгодного обмена валюты: при конвертации рубля в фунт и фунта в доллар можно было приобрести $1 за ₽88 вместо ₽150 при прямом обмене. Позже «Тинькофф» начал блокировать счета клиентов, которые воспользовались этой возможностью, а также списывать многомиллионные суммы, обвиняя людей в мошенничестве.

В итоге недовольные клиенты начали обращаться в суды. В объяснении суду банк говорит, что возможность «заработать» на курсах валют появилась «в результате ошибок сотрудников банка». Ущерб компания оценила в $7 млн.

Ошибки на миллион долларов Павел Анненков

04:58
6.54 MB
14.1K

Самые Дорогие Ошибки За Всю Историю

13:57
18.36 MB
5.4M

Самые Дорогие Строительные Ошибки

25:53
34.06 MB
2M

Основные ошибки предпринимателей по книге Ошибки на миллион долларов I 2 часть I Подкаст 29

11:59
15.77 MB
1.3K

Самые Дорогие Ошибки За Всю Историю Часть 2

12:44
16.76 MB
2.3M

Павел Анненков автор Ошибки на миллион долларов в программе Уроки менеджмента с Романом Дусенко

34:09
44.94 MB
1.3K

ГЕНДЕР ПАТИ НА МИЛЛИОН ДОЛЛАРОВ И ОШИБКА ВСЕ В ШОКЕ

06:49
8.97 MB
133.1K

Как я потерял миллион долларов на трейдинге Не совершайте мою ошибку

09:03
11.91 MB
133.3K

Ошибка на 1 миллион долларов при продаже NFT

14
314.45 KB
10

Ошибка в инвестиционном договоре на один миллион долларов Barbershop интервью

17
381.84 KB
3K

ошибка на миллион долларов

08
179.69 KB
4

Чему я научился потеряв миллион долларов книга

03:12
4.21 MB
4.6K

Нейроученый Привычки на Миллион Долларов Которые Вы Можете Скопировать

08:05
10.64 MB
226K

Ошибка на миллион долларов Как умышленно лишить себя богатства

11:43
15.42 MB
41

Last To Take Hand Off Jet Keeps It

15:37
20.55 MB
117.6M

Ошибки в бизнесе Павел Анненков Ошибки на миллион долларов Уроки менеджмента с Романом Дусенко

12:50
16.89 MB
1.2K

Что я делал чтобы заработать Миллион Долларов Распорядок дня успешных людей

12:06
15.92 MB
119.8K

Как Заработать МИЛЛИОН Долларов В Год Гарантированная Формула Успеха Бет Дэвида

22:02
29.00 MB
230.6K

Как из 1 сделать 1 000 000 Как заработать миллион долларов Саидмурод Давлатов 2023

11:10
14.70 MB
9K

Работы на Миллион Долларов

03:01
3.97 MB
30.8K

Что Готов Сделать Парень За Миллион Долларов

02:26
3.20 MB
47.8K

Стив Харви Как заработать миллион долларов Пошаговая формула по правилу 10X

06:01
7.92 MB
138.7K

Егор Крид про миллион долларов на карте интервью вДудь Shorts

29
651.37 KB
3.4M

КАК АМЕРИКА МЕНЯ ОПУСТИЛА И МОЙ ПУТЬ К МИЛЛИОНУ ДОЛЛАРОВ

09:35
12.61 MB
179K

Ошибки на миллион долларов Павла Анненкова рецензия на книгу

03:47
4.98 MB
193

Сисун взял кредитов на миллион долларов США

10:16
13.51 MB
117.9K

Почему большинство людей НИКОГДА не заработают свой миллион долларов

25:38
33.74 MB
5.5K

Наварили на полтора миллиона долларов

29
651.37 KB
82.8K

Брайан Трейси Привычки на Миллион долларов

06:38
8.73 MB
14.2K

Как он украл миллион долларов Откровения бывшего киберпреступника Как крадут деньги с карт

42:57
56.53 MB
128K

История на миллион долларов Р Макки Как написать сценарий

05:18
6.98 MB
3.2K

Продать машин на 1 миллион долларов

31:43
41.74 MB
4.1M

Чтобы выиграть миллион долларов она должна делать все что скажет мужчина Краткий Пересказ Фильма

17:40
23.25 MB
363.5K

Один миллион долларов Хайлайты покер стримов Minthon19

08:13
10.81 MB
27.6K

Как стать долларовым миллионером Статистика проекта Миллион долларов за 8 долларов в день

06:31
8.58 MB
3.5K

Инвестиции Инвестиционный проект Максима Петрова Миллион долларов за 8 долларов в день

02:53
3.79 MB
6.4K

Как разбогател Уоррен Баффет Что нужно делать чтобы заработать миллион долларов

13:10
17.33 MB
7.7K

Чичваркин как заработать миллион долларов

12
269.53 KB
368.6K

1 миллион долларов за девушку на 15 минут кино фильм сериал

49
1.07 MB
158.3K

Заплатил Налоги в США в размере 1 миллиона 354 Тысячи Долларов Косяки бухгалтера на 400 000 баксов

10:44
14.13 MB
8.1K

КУПИЛ СКИНОВ НА 1 МИЛЛИОН ДОЛЛАРОВ ПРОВЕРКА МИФОВ В Standoff 2

15:50
20.84 MB
1.5M

Реакция на Я воспользовался ИИ за миллион долларов чтобы доказать что названия ранобэ тупые

29:48
39.22 MB
14.2K

ТРАЧУ МИЛЛИОН ДОЛЛАРОВ ЗА МИНУТУ В ГТА БЛЭК РАША

32
718.75 KB
9.3K

Как заработать МИЛЛИОН ДОЛЛАРОВ

52
1.14 MB
2.3K

Миллион долларов в 14 лет Shorts

32
718.75 KB
75.4K

Хук на миллион долларов Дота лучший момент Твич Icebergdoto

10
224.61 KB
20.4K

Правила нетворкинга Как находить контакты на миллион долларов MPSELLERS

18:24
24.22 MB
2K

Как стать миллионером или чем заняться чтобы заработать первый миллион долларов Саидмурод Давлатов

13:57
18.36 MB
3.6K

Старый дворец за 1 миллион долларов в Нью Джерси

49:14
64.79 MB
347.6K

Миллион долларов много ли это на самом деле

17:25
22.92 MB
9.4K

Понравилась статья? Поделить с друзьями:
  • Ошибка в программе коде называется
  • Ошибка в программе или коде это
  • Ошибка в программе жди меня
  • Ошибка в программе жаргон 3 буквы
  • Ошибка в программе декларация 2020