Ошибка на миллиард долларов

Ущерб от null: взгляд легендарного Тони Хоара.
История IT

Как готовилась статья

Вообще статей уже куча на просторах интернета, но хотелось найти первоисточник и дать референсы на речь Тони Хоара "Null References: The Billion Dollar Mistake".

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

Поэтому пришлось прибегнуть к помощи нейросетей.

# Вообще скачивать чужой контент плохо, пнятненко? Авторские права и все такое, ясненько? Но было интересно как это реализовать технически.
# И сложного тут ничего нет, все инструменты уже в открытом доступе.
#
# Понадобятся curl, python и ffmpeg
#
# Дальше все укладывается в несколько команд (делал все под WSL):

# Устанавливаем whisper
pip install openai-whisper

# Заходим на Rutube и в девтулзах ищем ссылку на ".m3u8".
# При желании и этот шаг можно автоматизировать каким‑нибудь Selenium.

# Курлом дергаем Rutube и склеиваем все с помощью ffmpeg
curl -o video.m3u8 "<полная ссылка на .m3u8 вместе с query string>" && ffmpeg -protocol_whitelist https,file,tls,tcp -i video.m3u8 -c copy output.mp4

# Запускаем транскрибацию
~/.local/bin/whisper output.mp4 --model large-v3-turbo --language English --output_format txt

# В консоли видим что-то типа
# [00:00.000 --> 00:09.160]  Well, thank you all very much. Thank you.
# [00:10.220 --> 00:13.900]  Very nice to see so many of you here again.
# [00:15.320 --> 00:24.680]  It wasn't me that decided to give two talks at this wonderful conference.
#
# Модель можно подобрать под себя, large мне за ночь работы так и не обработала видео до конца, поэтому взял large-v3-turbo.
# 
# Как процесс будет полностью завершен появится файл output.txt

Кто такой Тони Хоар

Если одним словом - ЛЕГЕНДА.

Чарльз Э́нтони Ри́чард Хо́ар (11 января 1934 - 5 марта 2026)
Британский учёный в области информатики и вычислительной техники
  • 1956 - получил степень бакалавра по классическим языкам в Оксфордском университете.
  • 1959 - обучался в МГУ компьютерному переводу, а также теории вероятностей в школе Колмогорова.
  • 1960 - разработчик алгоритма «быстрой сортировки».
  • 1960 - занимался реализацией языка Алгол-60.
  • 1968 - стал профессором информатики и вычислительной техники в университете Квинс в Белфасте.
  • 1977 - вернулся в Оксфорд на должность профессора вычислительной техники и возглавил исследовательскую группу по программированию.
  • 1980 - лауреат Тьюринговской премии.
  • 1985 - Медаль Фарадея.
  • 1999 - вышел на пенсию в звании почётного профессора, до последних дней работал на должности ведущего исследователя в Microsoft Research в Кембридже.
  • 2000 - рыцарский титул за заслуги в области образования и компьютерных наук, Премия Киото.
  • 2006 - Fellow Awards от Музея компьютерной истории.
  • 2011 - Медаль Джона фон Неймана.
  • 2013 - удостоен звания почётного доктора Санкт-Петербургского национального исследовательского университета информационных технологий, механики и оптики.

В чем суть проблемы

В старых языках программирования (таких как C, C++ или ранние версии Java) значение null является скрытым «призраком». Оно означает отсутствие объекта, но при этом тип системы позволяет подставить его везде, где ожидается реальный объект.

Если программа пытается вызвать метод или прочитать данные у ссылки, которая оказалась null, она мгновенно падает:

  • в Java это приводит к знаменитой ошибке NullPointerException
  • в C/C++ это приводит к аварийному завершению (Segmentation fault)

#include <iostream>
#include <string>

struct User {
    std::string name;
};

// Опасная функция: она верит, что указатель всегда правильный
void printUsername(const User* user) {
    // Если user равен nullptr, здесь произойдет жесткий сбой программы!
    std::cout << "Имя пользователя: " << user->name << std::endl; 
}

int main() {
    User* myUser = nullptr; // Указатель пустой
    
    printUsername(myUser); // КРАШ! Программа аварийно завершается
    return 0;
}

Что говорил Хоар

О стоимости ошибки

За всё это время — лет сорок или около того, — как вы думаете, во сколько обошлись ошибки из‑за нулевых ссылок с точки зрения времени программистов, дискомфорта пользователей и общего ущерба?

Думаете, это меньше или больше миллиарда долларов?

Миллиард долларов за 40 лет?

Ну, сколько человек проголосовали бы за сумму больше миллиарда долларов?

Американский миллиард.

Думаю, да, американский миллиард.

Ещё, ещё, пожалуйста.

Больше людей за большую сумму.

Нет?

Верно, хорошо, примерно половина.

А кто считает, что это обошлось дешевле этой суммы?

И большинство со мной согласны в том, что они не знают.

Впрочем, скажем так: порядка миллиарда долларов, вероятно, больше десятой доли миллиарда, вероятно, меньше десяти миллиардов.

О реализации

Одна из вещей, которую хочется иметь в языке высокого уровня, — это возможность знать, что при создании объекта вся его структура данных инициализирована. В этом случае нулевая ссылка (null reference) может использоваться для указания на то, что данные отсутствуют или на данный момент неизвестны. Фактически это единственное значение, которое можно присвоить, если у вас есть указатель на определённый тип.

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

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

Признание ошибки

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

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

И каждый раз, когда вы обращаетесь к любому из этих компонентов объекта, вам придётся делать это в рамках дискриминационного условия, которое проверяется. Например, рассматривая транспортное средство, программа будет говорить: «Если это автобус, то можно посмотреть количество пассажиров; если это автомобиль — можно посмотреть вместимость багажника».

Но быстро становится очевидно, что размер программы (исходного кода) заметно увеличивается из‑за необходимости прописывать все эти дискриминационные условия — и вам придётся обрабатывать оба случая отдельно.

Теперь, если мы настаиваем на использовании дискриминационного условия, то следует сделать так, чтобы null был не значением указателя, а классом — классом, в котором никогда не бывает объектов и который имеет только один указатель, который, очевидно, ни на какой объект не указывает.

Таким образом, всякий раз, когда вам нужен нулевой указатель, вы будете объявлять свой указатель либо как указатель на класс null, либо как указатель на класс «транспортное средство» или какой‑нибудь другой класс. Это даст способ указать, должен ли этот указатель иметь возможность принимать значение null или он должен обязательно указывать на какой‑то объект.

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

Что произойдёт, если вы присвоите новое значение указателю, который в данный момент анализируете и считаете принадлежащим к классу «автобус»? Например, внутри кода, который обрабатывает автобусы, вы присвоите самому указателю значение указателя на автомобиль.

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

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

Null — очень удобное (по сути, единственное надёжное) значение, которое можно присвоить в качестве значения атрибута недавно созданному указателю или атрибуту‑указателю.

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

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

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

Итог

Тони Хоар — очень умный и уважаемый учёный, его вклад в программирование огромен. Но особенно ценно то, что он не побоялся открыто сказать: «Я ошибся». В мире IT часто стараются не показывать свои промахи, будто ошибка — это что-то стыдное. А пример Хоара показывает другое: признавать, что ты не прав, — это нормально и даже правильно. Так наука и технологии и двигаются вперёд: не потому, что кто‑то никогда не ошибается, а потому, что люди готовы честно это признавать и учиться на своих ошибках.