Первоначально опубликовано в моем блоге.
В начале этой недели при работе над скриптами конвейера блогов мне пришлось углубиться в документацию Mailchimp Marketing API. Каждая из конечных точек там предоставляет фрагмент следующей формы (пример для Python):
import mailchimp_marketing as MailchimpMarketing
from mailchimp_marketing.api_client import ApiClientError
try:
client = MailchimpMarketing.Client()
client.set_config({
"api_key": "YOUR_API_KEY",
"server": "YOUR_SERVER_PREFIX"
})
response = client.accountExports.list_account_exports()
print(response)
except ApiClientError as error:
print("Error: {}".format(error.text))
В этом 14-строчном фрагменте 4 строки и 1 импорт модуля предназначены исключительно для перехвата ошибки и превращения ее в оператор печати. Возможно, это сделано для того, чтобы проиллюстрировать существование этой ошибки на случай, если вы захотите ее использовать, но на практике все просто копируют весь блок try: ... except: ...
дословно, как видно из быстрого поиска на GitHub.
Эту модель я вижу повсюду. Разработчики считают, что если вы не отлавливаете каждую ошибку, то вы неряха, дилетант. На самом деле все обстоит с точностью до наоборот. Давайте рассмотрим несколько сценариев на примере вышеприведенного примера.
Наименее вредный случай
В наименее вредном случае, возможно, самом срочном, это будет скопировано непосредственно в интерпретатор Python или в быстрый хаки-скрипт, пока разработчик тестирует библиотеку API, чтобы привыкнуть к ней. В этом случае ущерб от преобразования этой ошибки в оператор печати будет незначительным. Разработчик только что написал код, и, как мы надеемся, ему не составит труда найти ошибку. Однако вы все равно выбрасываете:
- информацию о том, на какой строке произошла ошибка
- Информация о других файлах и функциях, через которые проходит код, чтобы получить ошибку.
Опытные разработчики Python знают, как читать трассировку стека исключений. Для них это будет гораздо привычнее, чем напечатанная строка текста. А список функций, через которые прошел интерпретатор при возникновении ошибки, вполне может подсказать разработчику, где возникла проблема.
Отлавливать отдельные ошибки в коде приложения гораздо опаснее.
Представьте себе, что это происходит внутри функции представления во Flask, популярном веб-микрофреймворке. Flask — довольно пустой продукт, но одна вещь в нем есть — это обработка ошибок. Если приложение выдает ошибку в процессе разработки, оно с готовностью выведет трассировку стека в браузер. Если это произойдет на производстве, то будет показана базовая страница ошибки сервера.
Приведенный выше код выведет ошибку в журнал сервера, пропустит ее мимо ушей и попытается выдать обычную страницу успеха, как будто ничего не случилось. Это плохо. Хуже, чем плохо. Это кошмар для отладки.
То же самое происходит с любым фреймворком приложения. Обработка ошибок будет разумно управляться на уровне приложения. Вылавливание отдельных ошибок и наивная обработка могут привести к тому, что ошибки будет невозможно отследить.
Не ловите ошибки по произволу
Итак, в базовом случае правильным решением будет не ловить ошибки. Таким образом, слой над вашим кодом будет поступать правильно. Если это скрипт или интерпретатор Python, он покажет разработчику полную трассировку. Если это приложение, оно передаст ошибку стандартному уровню обработки исключений приложения, который почти наверняка выполнит работу лучше, чем какой-нибудь наивный блок try: ... except: print()
.
Когда нужно ловить ошибки
Сказав все это, можно найти множество случаев, когда улавливатели ошибок в приложении будут работать плохо. Например, давайте рассмотрим наш пример с Flask view и Mailchimp API. Одна из возможных ошибок, которую мы можем здесь получить, — это ошибка тайм-аута API. Вполне вероятно, что API работает нормально при локальной разработке сайта, но при переносе кода в продакшн брандмауэры или другие сетевые конфигурации не позволяют приложению получить доступ к API. Или же API мог стать неповоротливым при высокой нагрузке спустя долгое время после выпуска кода.
В этом случае может быть весьма желательно показать это на результирующей веб-странице, а не общую ошибку сервера. Поэтому мы можем захотеть специально отлавливать такие типы ошибок и дать указание Flask отображать страницу ошибки с определенным сообщением «API timeout» или подобным.
Дело не в том, что мы никогда не должны ловить ошибки, а в том, что нам нужно понимать, зачем мы это делаем. Какова здесь история пользователя, почему перехват ошибки может дать лучший опыт, чем опыт по умолчанию?
Другими словами:
все должно быть настолько простым, насколько это возможно, но не проще.