Вообще статей уже куча на просторах интернета, но хотелось найти первоисточник и дать референсы на речь Тони Хоара "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
Если одним словом - ЛЕГЕНДА.
В старых языках программирования (таких как C, C++ или ранние версии Java) значение null является скрытым «призраком». Оно означает отсутствие объекта, но при этом тип системы позволяет подставить его везде, где ожидается реальный объект.
Если программа пытается вызвать метод или прочитать данные у ссылки, которая оказалась null, она мгновенно падает:
#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 лет?
Ну, сколько человек проголосовали бы за сумму больше миллиарда долларов?
Американский миллиард.
Думаю, да, американский миллиард.
Ещё, ещё, пожалуйста.
Больше людей за большую сумму.
Нет?
Верно, хорошо, примерно половина.
А кто считает, что это обошлось дешевле этой суммы?
И большинство со мной согласны в том, что они не знают.
Впрочем, скажем так: порядка миллиарда долларов, вероятно, больше десятой доли миллиарда, вероятно, меньше десяти миллиардов.
In all that time, 40 years or so, how much do you think that null reference violations have cost in terms of programmer time, user discomfort, and general damage?
Do you think it was less than or more than a billion dollars?
A billion dollars in 40 years?
Well, how many people would vote for more than a billion dollars?
American billion.
American billion, I think, yes.
More, more, please.
More people for more.
No?
Right, good, a good half.
And who would think it would have cost less than that amount?
And most people agree with me that they don't know.
However, let's just say of the order of a billion dollars, probably more than a tenth of a billion, probably less than ten billion.
Одна из вещей, которую хочется иметь в языке высокого уровня, — это возможность знать, что при создании объекта вся его структура данных инициализирована. В этом случае нулевая ссылка (null reference) может использоваться для указания на то, что данные отсутствуют или на данный момент неизвестны. Фактически это единственное значение, которое можно присвоить, если у вас есть указатель на определённый тип.
Если вы не хотите использовать null, вам придётся реализовать подъязык для представления того, как инициализировать объекты нужного типа. Если структура данных представляет собой древовидное представление, этого можно достичь, если сначала создать листья — потому что их можно полностью сформировать. С помощью этого метода невозможно создать циклическую структуру: если в структуре данных есть цикл, можно начать с нулевого указателя, а затем присвоить ему значение после того, как будет завершена остальная часть цикла.
Это навело меня на мысль о том, что значение null является членом каждого типа, и проверка на null требуется при каждом использовании ссылочной переменной, — и, возможно, это ошибка на миллиард долларов.
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 initialized. 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.
If you don’t want to use null, you have to implement a sublanguage for representing how to initialize 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. 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.
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
Например, если у вас есть класс «транспортные средства», вы можете классифицировать транспортные средства как автобусы либо как частные автомобили и отдельно объявить разные структуры атрибутов для каждого из этих двух классов.
Так, у автобуса может быть атрибут, указывающий максимальное количество пассажиров, а у частного автомобиля — атрибут, отражающий вместимость багажника (то есть вместимость для багажа в отличие от вместимости для пассажиров).
И каждый раз, когда вы обращаетесь к любому из этих компонентов объекта, вам придётся делать это в рамках дискриминационного условия, которое проверяется. Например, рассматривая транспортное средство, программа будет говорить: «Если это автобус, то можно посмотреть количество пассажиров; если это автомобиль — можно посмотреть вместимость багажника».
Но быстро становится очевидно, что размер программы (исходного кода) заметно увеличивается из‑за необходимости прописывать все эти дискриминационные условия — и вам придётся обрабатывать оба случая отдельно.
Теперь, если мы настаиваем на использовании дискриминационного условия, то следует сделать так, чтобы null был не значением указателя, а классом — классом, в котором никогда не бывает объектов и который имеет только один указатель, который, очевидно, ни на какой объект не указывает.
Таким образом, всякий раз, когда вам нужен нулевой указатель, вы будете объявлять свой указатель либо как указатель на класс null, либо как указатель на класс «транспортное средство» или какой‑нибудь другой класс. Это даст способ указать, должен ли этот указатель иметь возможность принимать значение null или он должен обязательно указывать на какой‑то объект.
И вам никогда не придётся выполнять проверку. Попробуйте выполнить несколько упражнений с использованием этой конкретной нотации. Вы увидите, что это действительно довольно громоздко и существует множество пограничных случаев.
Что произойдёт, если вы присвоите новое значение указателю, который в данный момент анализируете и считаете принадлежащим к классу «автобус»? Например, внутри кода, который обрабатывает автобусы, вы присвоите самому указателю значение указателя на автомобиль.
Проектирование языка — ужасная работа: всегда приходится продумывать такие пограничные случаи. Возможно, мы могли бы это запретить.
Но есть ещё более серьёзная проблема — с инициализацией. Одна из вещей, которую вы хотите от языка высокого уровня, — это защита от неинициализированных переменных. Стандартный способ добиться этого — присвоить каждому переменной или атрибуту фиксированное известное значение.
Null — очень удобное (по сути, единственное надёжное) значение, которое можно присвоить в качестве значения атрибута недавно созданному указателю или атрибуту‑указателю.
Поэтому, если вы хотите избежать использования null, вам придётся изобрести целый вспомогательный язык для инициализации указателей, которым не разрешено быть нулевыми. И это нормально, пока все ваши объекты образуют древовидную структуру: тогда вы можете начать с листьев дерева и в иерархическом, чётко упорядоченном порядке сформировать значение небольшой древовидной сети — оно станет начальным значением вашей новой переменной.
Но если вы захотите создать циклическую структуру таким способом, в обычном программировании это невозможно. Обычно вы присваиваете указателю значение null, а позже вставляете циклическую ссылку, возможно, куда‑нибудь в другое место дерева.
В любом случае, в конечном счёте я не захотел разбираться со всеми этими проблемами — и это привело меня к мысли, что нулевой указатель — это возможное значение любой ссылочной переменной и возможная ошибка при каждом использовании этой ссылочной переменной. И, возможно, это была ошибка на миллиард долларов.
For example, if you had a class of vehicles, you might classify the vehicles as either buses or private cars and separately declare different structures of attributes for each of those two classes.
So a bus might have an attribute which indicated the maximum number of passengers, and the private car might have an attribute which gave you the capacity of the trunk or the boot — the luggage carrying capacity as opposed to the passenger carrying capacity.
And every time you accessed either of those components of the object, you would have to do it within a discrimination clause which are tested. It looked at a vehicle with a vehicle — it would say when it is a bus, you do you, you can look at its number of passengers, and when it is a car, you can look at the capacity of the trunk or the boot.
But you rapidly see that the size of the program — the source program — gets quite a bit larger by having to make all these discrimination clauses, and you’re going to have to deal with both cases separately.
Now, if we insisted on a discrimination clause, then we should make null not into a value of a pointer but rather into a class — a class which never had any objects in it and which only had one pointer which obviously didn’t point to any object.
So now, whenever you wanted to have a null pointer, you would declare your pointer to be either a pointer to the null class or a pointer to the vehicle class or the wife class. And that would give you a way of specifying whether you wanted this pointer to be able to take a null value or not, or whether you wanted it to remain definitely pointing to something.
And you would never have to test — do a few exercises using this particular notation. You see, again, it’s really quite cumbersome and there are a lot of corner cases.
What happens if you assign a new value to the pointer which you are currently analysing and assuming to be a member of the bus class? So inside, so inside the code which processes the buses, you assign to the pointer itself a pointer to a car.
Language design is a terrible job — you always have to think of these corner cases. Well, perhaps we could forbid that.
Then there’s an even worse problem with initialisation. One of the things you want a high-level language to do for you is to protect you against uninitialised variables. And the standard way of doing that is to assign a fixed known value to every variable or attribute.
Null is a very well — it’s really the only certain thing that you can assign as a value of an attribute to a recently created pointer or pointer attribute.
And so, if you want to avoid using null, then you have to invent a whole sort of sub-language to use for initialising pointers that aren’t allowed to be null. And this is alright as long as all your objects are in a tree structure because you can then start with the leaves of the tree and build up in a hierarchical and well-ordered fashion the value of a little tree network to be the initial value of your new variable.
But if you wanted to create a cyclic structure this way, there’s no way of doing it in normal programming. You would assign a null value to the pointer and later on you would insert the cyclic pointer perhaps to somewhere else in the tree.
Anyway, all those problems in the end I didn’t want to deal with, and that led me to suggest that the null pointer was a possible value of every reference variable and a possible mistake on every use of that reference variable. And perhaps it was a billion-dollar mistake.
Тони Хоар — очень умный и уважаемый учёный, его вклад в программирование огромен. Но особенно ценно то, что он не побоялся открыто сказать: «Я ошибся». В мире IT часто стараются не показывать свои промахи, будто ошибка — это что-то стыдное. А пример Хоара показывает другое: признавать, что ты не прав, — это нормально и даже правильно. Так наука и технологии и двигаются вперёд: не потому, что кто‑то никогда не ошибается, а потому, что люди готовы честно это признавать и учиться на своих ошибках.