case-studiesintegration

Mars Climate Orbiter: $327 миллионов из-за фунтов и ньютонов

23 сентября 1999 года NASA потеряла космический аппарат на подлёте к Марсу. Зонд должен был выйти на орбиту 226 км над поверхностью, но фактически прошёл на 57 км и сгорел в атмосфере. Не было аппаратной поломки, не было проблем со связью. Это был бенчмарк-баг про интеграционное тестирование.

Что произошло

Lockheed Martin (производитель аппарата) поставил софт расчёта импульса от двигателей в фунт-силах на секунду (lbf·s) — имперская система. JPL (управление полётом) ожидал данные в ньютон-секундах (N·s) — метрическая. Разница: 1 lbf·s = 4.45 N·s.

В течение 9 месяцев полёта каждое корректирующее действие двигателей вычислялось с коэффициентом ошибки ~4.5x. Накопленное отклонение по траектории составило около 170 км — достаточно, чтобы миссия закончилась катастрофой.

Что упустил QA

⚠️ Не было contract-теста между двумя командами. У каждого были свои unit-тесты, в обеих системах всё проходило. Но никто не проверял, что данные на стыке имеют одну и ту же единицу измерения.

⚠️ Спецификация была. ICD (Interface Control Document) явно указывал N·s. Lockheed его проигнорировал — внутри использовали имперскую систему по привычке. Документация без автоматизированной верификации — просто текст.

⚠️ End-to-end проверка не нашла бага. На земле тесты показывали небольшое расхождение траектории, но интерпретировали как «обычные погрешности модели». Никто не задал вопрос «а почему расхождение именно такого размера?». Если бы кто-то посчитал коэффициент 4.45 — увидел бы знакомый множитель конверсии.

⚠️ Не было фиктивных «канареечных» прогонов — где данные специально проверяются на разумность диапазона. Если двигатель включается на 5 секунд и выдаёт «X», то X должен лежать в известном окне. Любое значение в 4 раза выше — флаг.

Что отсюда можно унести в свой проект

Юниты в типах, а не в комментариях. Не float impulse, а NewtonSeconds impulse (или хотя бы impulse_ns в имени). Компилятор и ревьюер сразу увидят несоответствие.

Contract-тесты на каждом межсистемном интерфейсе. Pact, OpenAPI-валидация, схемные тесты в CI — что угодно, что не даст одному сервису поменять формат без поломки другого.

Sanity-чеки в проде на численные значения. Если значение «должно быть в диапазоне X..Y» — assert в коде или alarm в мониторинге. Когда запустим что-то в 4× нормы — узнаем сразу, а не через 9 месяцев.

Регулярный «sanity day»: команда идёт по интерфейсам и спрашивает «а что если эта величина в неожиданных юнитах? а если null? а если negative?». Час раз в спринт.

Подробнее: NASA Lessons Learned — MCO Mishap Investigation Board Report.