Слоны не кусаются: почему надо бояться мелких багов и что с ними делать
Слоны не кусаются, зато укусы малярийных комаров уносят 400 тысяч жизней ежегодно. Пиши код и помни: маленькие ошибки рождают самые большие проблемы.
Кадр: мультфильм «Хортон»
Закари Минотт
(Zachary Minott)
Об авторе
Разработчик облачных решений, философ, заядлый читатель, вечный студент, спортсмен.
4 июня 1996 года беспилотная ракета Европейского космического агентства Ariane 5 взорвалась всего через 37 секунд после запуска. 370 миллионов долларов и десятки лет работы были пущены по ветру в один момент.
В чём причина? Виноват всего лишь один маленький баг: 64-битная переменная с плавающей запятой и с миллиардом потенциальных значений попыталась сохраниться в 16-битное целое число, у которого может быть только 65 535 потенциальных значений. Переменной буквально не хватило места, чтобы попасть в космос. Какие уроки можно из этого извлечь?
Слоны не кусаются, они просто хотят быть вашими друзьями
Грубые ошибки в коде вызывают сбой компилятора или выполнения программы и потому легко обнаруживаются. Они почти не вызывают беспокойства — очевидно же, что разработчики их увидят и сразу исправят. Никто не станет выпускать бракованный продукт, явно не соответствующий ожиданиям пользователей.
Поэтому грубые ошибки никогда не вызывают проблем в долгосрочной перспективе и не наносят серьёзного ущерба — если им не позволять, конечно. Большие ошибки легко идентифицируются и как будто говорят: «Привет, друг. Посмотри на меня! Удели мне внимание, видишь, какая я милая. Ты не пожалеешь, что встретил меня!»
Комары могут ослепить, вызвать болезнь Лайма или убить
Иное дело — ошибки-комары: вроде бы несущественные фрагменты кода, которые не опознаются как неверные и могут маскироваться под обычные данные. Однако именно они набрасываются на ваш код в самый неожиданный момент и превращают его в хаос.
Такие комары могут скрываться за логическими ошибками, немасштабируемой или громоздкой структурой, грязным и беспорядочным кодом, а также неэффективными или неоптимизированными алгоритмами.
Проблема, сорвавшая запуск Ariane 5, возникла из-за того, что разработчики скопировали рабочий код из программы предыдущей, успешно запущенной ракеты Ariane 4. Они думали, что он отлично им подойдёт, но оказалось, что он не соответствовал новым требованиям обновлённой программной оболочки Ariane 5.
Ошибки-комары малы, но появляются куда чаще.
Логические ошибки могут вызвать проблемы с обработкой и отображением информации, что сказывается на функциональных возможностях и приводит к ухудшению пользовательского опыта. Одно это может вызвать потерю интереса пользователей, а вы, как разработчик, не заметите никаких проблем в работе своего приложения.
Немасштабируемая и громоздкая структура кода может вызвать такие же проблемы, как в случае со взрывом Ariane 5, когда крошечный фрагмент кода, который работал в средах с меньшей вычислительной мощностью, не смог справиться с интенсивными вычислениями.
Игнорирование таких сущностей может привести к сбою системы, не предназначенной для обработки более серьёзных операций. Подобные ошибки обычно выявляются при тестировании хорошо написанного кода под максимальной нагрузкой. Но если не прописать тестовые классы, которые обрабатывают такие случаи, может возникнуть «чёрный лебедь», только и ждущий случая неприятно удивить вас.
Грязный и беспорядочный код затрудняет поиск ошибок и проблем. Кроме того, он существенно увеличивает затраты на разработку — его труднее расширять и изменять. Поэтому ошибки становятся более частыми и распространёнными, а продукт быстрее крашится. При виде запутанного кода у вас должна возникать паника и желание провести немедленный рефакторинг.
Неоптимизированные алгоритмы приводят к снижению производительности, когда дело доходит до ресурсоёмких операций. Этот момент легко упустить, особенно если вы ещё не рефакторили код с точки зрения оптимизации алгоритмов.
Обычно подобные проблемы замечают, когда увеличивается время загрузки приложения, появляются задержки или ограничения (особенно при работе с облачными серверными компонентами). Если в стандартных условиях у вас что-то работает хорошо, не факт, что так же хорошо оно будет работать на большом наборе данных или в сочетании с другими компонентами.
Будете пропускать эти комариные укусы — дождётесь большого неприятного сюрприза. Но существует множество способов смягчить и минимизировать влияние этих ошибок.
Воспользуйтесь средством от насекомых
Метафора, может, не самая удачная, но суть вы поняли. В каком-то смысле я сочувствую программистам, вовлечённым в инцидент с Ariane 5, но такие вещи заставляют осознать важность аккуратного и осознанного написания кода, подкреплённого огромным количеством стресс-тестов.
Программирование — это гораздо больше, чем просто создание кода, который нормально компилируется и исполняется. Программирование требует тщательного и вдумчивого изучения, в нём неуместен метод «пьяного матроса», преследующий как начинающих, так и многих опытных программистов, — когда вы просто складываете вместе разные куски кода, будто пытаясь вставить цилиндр в квадратное отверстие. Цилиндр, конечно, войдёт в квадратное отверстие, но долго в нём не удержится — и в любом случае это неправильное решение.
Вот почему при написании кода лучше всего постоянно задавать себе следующие вопросы:
- Мой код слишком сложен? Как я могу его упростить?
- Написал ли я для своего кода строгие тестовые классы со множеством различных сценариев с наполнением данными и интенсивными вычислениями? Знаю ли я обо всех ограничениях?
- Мои функции не слишком большие? Могу ли я абстрагировать методы до более мелких?
- У моих переменных, классов и функций очень чёткие и конкретные имена? Может ли кто-нибудь точно понять, что делает мой код, просто прочитав их названия?
- Не копирую ли я слишком много методов, которые вместо этого можно было бы использовать повторно, чтобы несколько разных процессов имели общую функциональность? Абсолютно ли необходимы мои повторяющиеся методы и не требуют ли они другого сценария работы?
- Как я обрабатываю ошибки? Кидаю ли я ошибки, использую ли блоки try-catch и проверяю ли в переменных значение null? Существует ли алгоритм для обеспечения бесперебойной работы при обнаружении ошибки?
- Легко ли мой код расширяется и масштабируется? Если будут внесены изменения, нужно ли мне беспокоиться о каких-либо зависимостях?
- Мой код упорядочен и оптимизирован для обработки больших объёмов данных? Будет ли мой код вызывать ошибку или превышать время ожидания при слишком большой нагрузке?
Список, разумеется, не исчерпывающий, и, более того, у каждого из нас он будет своим. Однако я считаю, что самые важные вопросы — те, что перечислены выше. Минимизируйте вероятность неизвестных ошибок в вашем коде, чтобы он был понятным сразу, а не как кот Шрёдингера — «неизвестно, пока не открыто».
На посошок
Именно крошечные вещи, которые вы делаете постоянно, приносят самые большие результаты. Крупные дела редко оказываются в центре внимания. Так что хватит беспокоиться о серьёзных ошибках. Их легко предупредить, заметить и исправить.
А вот крошечные ошибки и несоответствия, которые возникают ежедневно, должны стать поводом для беспокойства. Ищите способы исправления этих ошибок — или хотя бы минимизируйте их последствия, учитывая разнообразные сценарии работы кода.