Когда данные теряют смысл: MCO, YouTube, PayPal и Therac-25 как учебные кейсы

от автора

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


Редакция Python Times, 09:47

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

— Вы ВИДЕЛИ это?! — она на ходу выудила из пакета два круассана, один плюхнула на стол прямо перед Тимом, а второй прижала к груди. — NASA угробило Mars Climate Orbiter! Триста двадцать семь миллионов долларов просто СГОРЕЛИ в атмосфере Марса!

Тим, стажёр, который недавно устроился в редакцию, оторвался от блокнота и уставился на круассан.

— Это мне?

— Ну да, я же… там было два, в общем. — Элиф махнула рукой и отвернулась, сбрасывая сумку на пол. — Не важно сейчас! Слышал про зонд-то?

Зен, умное ИИ-устройство на стене, негромко загудел, а его индикатор мигнул жёлтым.

— Доброе утро, Элиф. Опоздание — девятнадцать минут. Третий раз за неделю.

— Да какая разница! — Элиф плюхнулась на стул рядом с Тимом и развернулась к нему. — Тим, ну представь себе: сотни инженеров, годы работы — и всё насмарку!

Тим осторожно отломил кусочек круассана.

— Ну… космос — дело тонкое. Может, метеорит попал? Или датчик отказал?

— Одна строчка, — Элиф придвинулась ближе, накручивая прядь волос на палец. — Понимаешь? ОДНА. Точнее, даже не строчка, а то, что её не было вовсе.

Трейси медленно перевернула страницу в потрёпанном блокноте, не поднимая глаз.

— Сначала факты, — произнесла она. — Триста двадцать семь миллионов — это вся программа Mars Surveyor ’98: орбитер, посадочный модуль Mars Polar Lander, два запуска и операции. Сам сгоревший аппарат стоил около ста двадцати пяти миллионов. Впрочем, лендер через два с половиной месяца тоже разбился, так что списывай всю сумму, не ошибёшься.

— Ты умеешь утешить, — фыркнула Элиф.

— И не строчка, а единицы измерения, — продолжила Трейси. — Наземный софт от Lockheed Martin выдавал данные об импульсах двигателей в фунт-сила-секундах. Навигационная команда JPL читала эти же числа как ньютон-секунды. Девять с половиной месяцев полёта — и каждая коррекция траектории чуть-чуть врала.

— Стоп, — Тим нахмурился. — Ну перепутали единицы. Разве это такая проблема?

Элиф всплеснула руками.

— ТАКАЯ?! Да это же трагедия космического масштаба! Буквально! Корабль летел девять месяцев, и в самый последний момент…

— Элиф, — спокойно оборвала Трейси, разворачивая ноутбук. — Факты лучше драмы.

На экране появился код:

# Команда А (наземное ПО, расчёт импульса)impulse = 100  # фунт-секунды... но это нигде не написано# Команда Б (навигация)correction = impulse * k  # думает, что это ньютон-секунды!

Тим вгляделся в экран.

— Но ведь сто — это просто число. Что тут может пойти не так?

Зен ответил:

— Один фунт-сила равен 4.45 ньютона. Каждая коррекция траектории врала примерно в четыре с половиной раза.

— Вот видишь! — Элиф ткнула пальцем в воздух. — Это как если ты думаешь, что едешь шестьдесят километров в час, а реально летишь под двести семьдесят!

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

Повисла пауза. Тим постучал ручкой по блокноту.

— Значит, хватило бы одного комментария в коде?

Трейси кивнула и набрала:

impulse = 100  # pound-force-seconds, НЕ newton-seconds!

— Или хотя бы проверки:

if unit != "newton_seconds":    impulse = convert_to_newton_seconds(impulse)

— Сто двадцать пять миллионов долларов, — тихо повторил Тим, и в его голосе прозвучало что-то между ужасом и благоговением.

— И потерянный ретранслятор для посадочного модуля, который летел следом, — сухо добавила Трейси. — Так что косвенно — сильно больше.

Дверь снова распахнулась. На пороге возник Флэш с двумя дымящимися стаканами кофе и болтающейся камерой на груди.

— О, вы про марсианский облом? — он поставил стаканы на край стола, а сам уселся рядом, свесив ноги. — Классика! А хотите историю попроще и повеселее?

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

— YouTube, декабрь две тысячи четырнадцатого. Gangnam Style.

Поймал маркер и размашисто вывел: 2_147_483_647

— Узнаёте цифру?

Тим покачал головой.

— Максимум для тридцатидвухбитного знакового целого! — Флэш щёлкнул пальцами. — YouTube хранил счётчик просмотров именно так. Инженеры думали — ну два миллиарда, этого ж хватит до скончания веков!

Элиф фыркнула.

— А потом появился PSY с лошадкой.

— Ага! — Флэш дописал:

2_147_483_647 + 1 = ???

Тим почесал переносицу.

— Ну… два миллиарда сто сорок семь миллионов четыреста восемьдесят три тысячи шестьсот сорок восемь?

— В Python — да, — Трейси перелистнула страницу. — Но не в большинстве других языков.

Зен добавил:

— В языках с фиксированными типами переполнение знакового целого превращает число в отрицательное. Максимум плюс один становится -2_147_483_648.

— Погоди, — Тим выпрямился. — То есть если бы видео перевалило за предел, YouTube показывал бы…

— Минус два миллиарда! — Элиф рассмеялась, откидываясь на спинку стула и случайно задев локтем Тима. — Самое популярное видео в мире вдруг стало бы самым непопулярным!

Флэш развернулся к доске и быстро нарисовал карикатуру — человечка с вытаращенными глазами:

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

— Только вот минуса на экране никто не увидел. Это, Флэш, любимый миф интернета — «Gangnam Style сломал YouTube». На самом деле инженеры заметили проблему за месяцы и тихо перевели счётчик на шестьдесят четыре бита заранее. А первого декабря просто пошутили об этом в Google+: мол, мы не думали, что видео можно посмотреть больше двух миллиардов раз, пока не встретили PSY. У видео тогда было около двух и полутора десятых миллиарда просмотров.

— То есть никакой паники? — разочарованно протянул Флэш.

— Никакой паники. Зато Книга рекордов Гиннесса выдала PSY титул: первое видео, которому понадобился шестидесятичетырёхбитный счётчик. Верхний предел теперь — девять квинтиллионов. Чтобы столько набрать, каждый человек на планете должен посмотреть видео больше миллиарда раз.

Она развернула ноутбук к Тиму:

views = 2_147_483_647views = views + 1print(views)  # 2147483648 - в Python работает

— Тогда в чём загвоздка? — спросил Тим. — Если в Python всё нормально?

— Загвоздка, — Трейси набрала новый код, — в том, что Python общается с внешним миром. Базы данных, API, библиотеки на C: там типы жёсткие, фиксированные.

import numpy as npviews = np.int32(2_147_483_647)views = views + 1print(views)  # -2147483648 - переполнение!

Тим кивнул, будто что-то улеглось в голове.

— Значит, надо сразу закладывать запас. Использовать типы с большим диапазоном.

— Бинго! — Флэш подбросил маркер и не поймал — тот покатился по полу. — И заметь: YouTube выкрутился красиво именно потому, что заметил проблему ДО того, как она случилась. Если бы ждали минуса на экране — вот тогда была бы паника. Элиф полезла в сумку, вытащила телефон и что-то там полистала.

— Раз уж про большие числа… — она придвинула стул ближе к Тиму, развернула телефон к нему. — Две тысячи тринадцатый, PayPal. Парень по имени Крис Рейнольдс открывает почту с ежемесячной выпиской и видит баланс…

На экране:

$92_233_720_368_547_800

— Девяносто два квадриллиона долларов, — выдохнула Элиф, и глаза её блеснули. — Мировой ВВП тогда был около семидесяти пяти триллионов. У Криса на счету лежало больше тысячи мировых экономик!

Тим уставился на цифры и расхохотался.

— Не может быть. Это же розыгрыш!

— Чистая правда, — подтвердил Зен. — Правда, когда Рейнольдс зашёл в аккаунт онлайн, баланс уже показывал ноль. Ошибка прожила ровно одну выписку. PayPal извинился и предложил пожертвовать деньги на благотворительность по его выбору. Технических деталей компания так и не раскрыла.

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

Она записала в блокноте:

Максимум int64:  9_223_372_036_854_775_807Баланс Криса:       92_233_720_368_547_800

— Улавливаешь паттерн? — она подняла глаза. — Девяносто два, двадцать три, тридцать семь… Это максимум int64, поделённый примерно на сто, с округлением.

Тим сдвинул брови.

— А зачем на сто делить?

— Деньги хранят в центах, — объяснила Трейси, и в её голосе промелькнуло лёгкое раздражение. — Чтобы не мучиться с дробями. Сто долларов в базе — это десять тысяч центов. Максимум int64 в центах — это как раз девяносто два квадриллиона долларов на экране.

— В интернете гуляет версия, что где-то стояло магическое число, — добавила Элиф. — Минус один вместо «счёт не найден».

balance = None  # счёт не найденif balance is None:    balance = -1  # "магическое" значение вместо данных

— Ну и что? — Тим пожал плечами. — Минус один как минус один.

— И вот тут популярная версия хромает, — Трейси развернула ноутбук. — Минус один, приведённый к БЕЗЗНАКОВОМУ шестидесятичетырёхбитному типу, дал бы два в шестьдесят четвёртой минус один цент. Это сто восемьдесят четыре квадриллиона, а не девяносто два. А у Криса — ровно максимум ЗНАКОВОГО int64. Значит, где-то в системе что-то записало в баланс предельное значение типа. Как именно — знает только PayPal, а PayPal молчит. Поэтому число выдаёт тип данных с головой, механизм — нет.

— Постой-ка, — Тим поднял руку. — А зачем вообще использовать минус один для «нет данных»? Есть же None!

Трейси посмотрела на него и впервые за утро улыбнулась по-настоящему.

— Вот именно.

Набрала:

balance = None  # данных нетbalance = 0     # данные есть, баланс нулевой

— Чувствуешь? Оба похожи на «пусто». Но смысл разный в корне.

Тим почесал затылок.

— А разве? Если проверить через if…

balance = Noneif balance:    print("Есть деньги")else:    print("Нет денег")  # выведет этоbalance = 0if balance:    print("Есть деньги")else:    print("Нет денег")  # и это тоже!

— Результат одинаковый.

— Ответ один, — Трейси отпила из большого термоса, — а причины разные. None значит «мы не знаем, есть ли деньги». Ноль значит «мы точно знаем: денег ноль».

Элиф встала, подошла к окну. Молча постояла, накручивая прядь волос на палец. За стеклом моросил дождь.

Обернулась. Её голос стал тише и серьёзнее.

— Март две тысячи девятого. Лондон, конференция QCon. На сцену выходит сэр Тони Хоар — живая легенда Computer Science. Изобретатель быстрой сортировки.

Пауза. Элиф посмотрела прямо на Тима.

— И изобретатель null.

Зен процитировал:

Я называю это своей ошибкой на миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. Я проектировал первую всеобъемлющую систему типов для ссылок в ALGOL W и не смог устоять перед искушением добавить null просто потому, что это было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и сбоям систем, которые, вероятно, причинили ущерба на миллиард долларов за последние сорок лет.

Тим замер.

— Он сожалел, что придумал None?

— Он сожалел, — Трейси захлопнула блокнот, — что создал концепцию «отсутствие значения», которую слишком просто перепутать с «пустым значением». Он умер этой весной, в марте. Ему было девяносто два. И половина современных языков — Rust с его Option, Kotlin с nullable-типами — это, по сути, ответ на ту его лекцию.

Элиф медленно вернулась к столу. Остановилась за спиной Тима, и он почувствовал лёгкий запах её духов.

— Тим, — она положила руку ему на плечо, и её пальцы чуть сжались на ткани толстовки. — Вся история программирования — это история доверия.

Смахнула крошку круассана с его плеча.

— Мы пишем код. Код работает с данными. Мы доверяем, что данные говорят правду. Что сто — это действительно сто, а не сто фунт-сила-секунд. Что None означает «нет данных», а не «возьми максимум». Что счётчик не переполнится.

Помолчала. Её рука всё ещё лежала на его плече.

— Но данные контекста не знают. Не знают, что ты имел в виду. Они знают только то, что написано чёрным по белому. — Голос стал тише. — И если написано недостаточно…

Рука скользнула с плеча. Элиф отступила.

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

Элиф встрепенулась, будто очнулась.

— Да. Крис Рейнольдс. — Она глянула на часы, поморщилась. — Чёрт, такси через пять минут.

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

— Тот самый? С квадриллионами? — Флэш поднял брови. — Он согласился на интервью?

— Эксклюзив, — кивнула Элиф, натягивая дождевик. — Для серии «Дорогие ошибки». Окно всего два часа.

— Ладно, побежали, — Флэш сунул маркер в карман. — Я с тобой, мне фото нужны.

Развернулась и почти выбежала в коридор.

— Эй, погоди же! — голос Флэша удалялся.

Тишина упала на редакцию как одеяло.

— Зен, — позвал Тим тихо, не отрывая взгляда от двери.

— Слушаю.

— Что ещё можно потерять? Кроме денег?

Зен немного подумал и ответил:

— Время, которое не вернуть. Доверие, которое не восстановить. — Долгая пауза. — Людей, которые могли бы вернуться, но не вернулись.

Трейси перевернула страницу блокнота. Голос стал тише, жёстче.

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

— Из-за кода?

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

— Люди умерли… из-за доверия?

— Из-за того, — Трейси резко захлопнула блокнот, — что кто-то доверился. Не проверил. Решил, что «и так сойдёт».

За окном дождь усилился, капли всё быстрее колотили по стеклу…


P.S. От автора.

Честно признаюсь: все технические истории в этом уроке — реальные. И раз уж урок про доверие к данным, вот пруфы:

Mars Climate Orbiter действительно погиб 23 сентября 1999 года из-за путаницы фунт-секунд и ньютон-секунд в наземном ПО. Знаменитые $327 млн — это стоимость всей программы, орбитера и посадочного модуля вместе; сам аппарат оценивают примерно в $125 миллионов. Источник: пресс-релиз JPL от 30.09.1999

Gangnam Style действительно упёрся в int32 в декабре 2014-го. Знаменитый пост Google в блоге — это просто легенда. Реальный первоисточник — пост в Google+ от 1 декабря 2014, который умер вместе с Google+. Источник: архив поста Google+

PayPal действительно показал Крису Рейнольдсу $92 квадриллиона — в июльской email-выписке 2013 года. Онлайн-баланс уже был нулевым, а компания предложила пожертвование на выбранную им благотворительность. Гипотеза про int64 и «-1 в беззнаковом типе» — правдоподобная реконструкция сообщества, официально причина не раскрыта. Источник: Fox13seattle

Тони Хоар действительно назвал null своей «ошибкой на миллиард долларов» — на QCon London в марте 2009 (запись опубликована InfoQ в августе). Печальное примечание 2026 года: сэр Тони Хоар ушёл из жизни 5 марта 2026 года. В сцене выше — 2009 год, и тогда «живая легенда» было буквальной правдой. Источник: InfoQ

Therac-25 — это не художественный вымысел. Минимум шесть передозировок в 1985–1987 годах, дозы до 250 раз выше нормы, смерти и тяжёлые увечья. Число погибших в источниках разнится от двух до четырёх. Каноничный разбор — исследование Нэнси Левесон и Кларка Тёрнера (IEEE Computer, 1993). Источники: полный текст статьи Левесон (PDF)

Но.

Элиф, Тим, Трейси, Флэш и Зен — художественные персонажи. Их разговоры, взаимоотношения, личные истории — сторителлинг и инструмент для обучения.

Почему я учу Python через истории персонажей?

Лет пятнадцать назад я открыл очередной учебник по программированию, дошёл до двадцатой страницы и захлопнул. Не потому что было сложно. А потому что стало невыносимо скучно. Переменные a и b. Задачи про яблоки, которые никто в жизни не считает. Код ради кода.

Я задал себе вопрос: а что если каждая техническая концепция появляется не абстрактно, а когда без неё настоящую проблему не решить? Что если каждая строчка кода — ответ на реальный вопрос?

Так родилась редакция Python Times.

Это не «роман с уроками Python». Это курс программирования через призму человеческих историй, потому что наш мозг запоминает не абстракции, а эмоции и контекст.

Вы не будете складывать безымянные числа. Вы узнаете, например, почему Netflix случайно «состарил» подростков до 127 лет и другие странные случаи.

Персонажи меняются от урока к уроку. Влюбляются. Ошибаются. Исчезают и возвращаются.

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

Если зацепило — Python Times: интерактивный курс-детектив для начинающих

P.P.S. Дойдёте до финала — узнаете, что на самом деле между Тимом и Элиф. Но это уже совсем другая история.

ссылка на оригинал статьи https://habr.com/ru/articles/1060840/