![image](https://habrastorage.org/getpro/habr/post_images/6ec/52a/708/6ec52a708db489b1efe3740486416c67.png)
Когда в приложении происходит ошибка, есть два диаметрально противоположных подхода к обработке этой ошибки:
Forgive! подход: приложение продолжает выполняться и старается минимизировать последствия ошибки.
Fail Fast! подход: приложение немедленно прекращает работу и сообщает об ошибке.
- Какой из подходов лучше?
- Какой подход стоит реализовать в своем приложении?
Чтобы ответить на эти вопросы посмотрим на простой пример.
Предположим мы должны написать простую веб-страницу, которая отображает рядом с фонтаном предупреждение о том, что вода в нём загрязнена.
Следующий HTML-код выполняет эту задачу:
<html>
<body>
<h2 style="color:red;">Important!</h2>
<p>Please <b>DO NOT</b> drink this water!</p>
</body>
</html>
Результат работы этого кода в браузере будет выглядеть следующим образом:
![image](https://habrastorage.org/getpro/habr/post_images/a4f/1fe/465/a4f1fe4653b3ebb34ba1319dfac7c6a3.png)
Теперь мы сделаем небольшую ошибку внутри HTML-кода.
Вместо тэга мы используем тэг
после слов DO NOT, как в этом примере:
<p>Please <b>DO NOT<b> drink this water!</p>
Возникают два вопроса:
- Что должно произойти?
- Что произойдет?
На второй вопрос легко ответить. Достаточно выполнить ошибочный HTML-код в браузере. На момент написания статьи браузеры Firefox, Google Chrome, Internet Explorer, Opera и Safari покажут следующий результат:
![image](https://habrastorage.org/r/w1560/getpro/habr/post_images/8ab/ed7/43e/8abed743eb2d3be1b8c301e598a9fcca.png)
Очевидно, что в браузерах используется подход Forgive!, так как наш сайт продолжил работу и не было никаких сообщений об ошибке. Единственное отличие в том, что теперь стало больше текста, выделенного жирным шрифтом. Но всё сообщение всё ещё отображается целиком и люди предупреждены. Незачем сильно беспокоиться!
Делаем вывод: подход Forgive! работает хорошо!
Давайте попробуем воспроизвести другую ошибку.Вместо тэга мы напишем незаконченный тэг <b
перед словами DO NOT, следующим образом:
<p>Please <b DO NOT</b> drink this water!</p>
Ранее перечисленные браузеры покажут следующий результат:
![](https://habrastorage.org/r/w1560/webt/hm/qu/vv/hmquvvhnfzsq4gumc-enhkl79mi.png)
Есть повод паниковать! Теперь наша программа делает абсолютно обратное тому, что мы хотим, чтобы она делала. Последствия ужасны. Наше приложение, призванное спасать жизни, мутирует в приложение-убийцу.
Делаем вывод: подход Forgive! работает плохо!
Обращаю Ваше внимание на тот факт, что вышеприведённый пример - не просто теоретическое упражнение. Существует огромное количество реальных примеров, когда "маленькая ошибка" приводит к катастрофическим последствиям. Например, космический аппарат Mariner 1 взорвался после отрыва от земли из-за "отсутствующего дефиса". Больше примеров Вы можете найти на странице List of software bugs.
Как видно из приведённых примеров, последствия ошибки при использования Forgive! подхода очень отличаются и могут варьироваться от полностью безобидных до катастрофических. Итак, каким будет корректный ответ на вопрос "Что должно произойти?"
Как это обычно бывает, всё зависит от ситуации. Есть, однако, несколько основных правил.
Первое правило:
- В процессе разработки всегда надо использовать Fail fast! подход
Рациональность данного правила описывается двумя простыми фактами:
- Подход Fail fast! помогает в отладке. Как только что-то пошло не так, приложение останавливается и показывает сообщение об ошибке, которое позволяет зафиксировать, диагностировать и исправить ошибку. Таким образом, Fail fast! подход помогает писать более надёжное ПО. В результате значительно уменьшается стоимость разработки и поддержки, а также снижаются риски поломки приложения после релиза.
- Последствия ошибок, возникающих в процессе разработки, минимальны и не критичны. Клиенты не жалуются, деньги не переводятся на неверный аккаунт и ракеты не взрываются.
Однако, ситуация кардинально меняется, когда приложение выполняется у клиента после релиза. К сожалению, не существует правила-на-все-времена. Практика показывает, что обычно лучше и после релиза использовать подход Fail fast! по умолчанию. Конечный негативный результат выполнения приложения, которое игнорирует ошибки и просто продолжает выполняться непредсказуемо, обычно хуже, чем негативный результат от приложения, которое внезапно прекратило работу. Если приложение бухгалтерского учёта внезапно "упало", пользователь будет зол. Если приложение продолжило работу после возникновения ошибки и создало неверный результат, пользователь будет очень зол. "Зол" лучше чем "очень зол". В этой ситуации подход Fail fast! лучше.
Есть исключения и каждая ситуация требует отдельной оценки. Это особенно актуально когда возможность серьёзных негативных последствий требует от нас тщательной оценки каждой ситуации, например как в случае с медицинскими приложениями, приложениями по переводу денег или приложениями космической программы. Например, применение подхода Fail fast! оправдано до тех пор, пока мы не отправляем ракету на Марс. Но как только ракета стартовала - остановка приложения больше не вариант. Теперь должен применяться подход Forgive! в комбинации с режимом "делай лучшее что ты можешь".
Иногда хороший вариант - использовать Fail fast!, но при этом минимизировать негативный эффект от ошибки. Например, если произошла ошибка в текстовом редакторе, приложение должно сначала автоматически сохранить набранный текст во временный файл, затем вывести сообщение для пользователя ("Извините, ... но ваша работа сохранена в файл abc.tmp"), опционально послать отчёт разработчику и только потом прекратить работу.
Можно заключить:
- В процессе разработки всегда стоит использовать Fail fast! подход.
- После релиза:
- По умолчанию стоит всегда отдавать предпочтение подходу Fail fast!.
- В случае с критичными приложениями, которые имеют риск создания серьёзного негативного эффекта в случае возникновения ошибки, должны применяться индивидуальные решения, зависящие от контекста с целью минимализации негативного эффекта. В случае, когда ситуация после ошибки чётко просчитана, должен применяться подход Forgive! с правильной реакцией на произошедшее.
Та же идея описана в главе Rule of Repair книги The Art of Unix Programming , написанной Эриком Реймондом:
Почини когда можешь - но когда ты должен упасть, падай громко и как можно скорее.
В этом контексте также имеет смысл вспомнить заповедь номер 6 из Десяти заповедей С-программиста , написанных Гарри Спенсером:
Если функция возвращает код ошибки в случае возникновения трудностей, ты должен проверить код этой ошибки, даже если эта проверка троекратно увеличит размер кода твоего и вызовет боль в твоих пальцах, потому что если ты помыслишь "это не может случиться со мной", боги обязательно накажут тебя за высокомерие.
В любом случае, твой лучший друг - это среда разработки, которая поддерживает Fail fast! подход. Например, компилируемые языки придерживаются правила Fail fast! потому, что компиляторы могут немедленно сообщить о всём изобилии ошибок в коде. Приведу пример тупой ошибки, которая может быть легко не замечена человеческим глазом и может привести к неприятным сюрпризам в процессе выполнения, но при этом немедленно и наверняка вылавливается компилятором:
var table_row_index = 1
...
table_row_indx = table_row_index + 1
Контрактное программирование ещё один пример использования особенностей Fail fast!. Потому что неверные входные/выходные аргументы и атрибуты объектов немедленно определяются и вызывают ошибки в процессе выполнения.
Есть еще множество особенностей реализации Fail fast! , которые могут быть встроены в язык программирования. Они основываются на следующем правиле:
- Желательно, чтобы ошибка автоматически выявлялась на этапе компиляции или, как можно проще и быстрее, в процессе выполнения.
Если Вы выбрали среду программирования (= язык программирования + библиотеки + фреймворки), которая придерживается этого важного правила, то Вы будете отлаживать меньше и создавать более надёжный код за меньшее время.
Дополение от переводчика:
Вспыло утверждение, дескать никто не будет делать две версии кода, одну с fail fast для разработки и вторую без fail fast для релиза.
Меня это утверждение немного удивляет.
Есть весьма распространенное решение этого вопроса. Assert
В релизе он убирается автоматически. Таким образом код являющися fail fast в дебаге перестает быть таковым в релизе.
Никаких проблем, и уж тем более переписывания кода - нет.