Мы используем исключения, чтобы позволить потребителю кода обработать неожиданное поведение полезным способом. Обычно исключения строятся вокруг сценария «что случилось», например FileNotFound
(мы не смогли найти указанный вами файл) или ZeroDivisionError
(мы не смогли выполнить 1/0
операцию).
Что, если есть возможность указать ожидаемое поведение потребителя?
Например, представьте, что у нас есть fetch
ресурс, который выполняет HTTP-запрос и возвращает полученные данные. И вместо ошибок типа ServiceTemporaryUnavailable
или RateLimitExceeded
мы просто RetryableError
предложили бы потребителю, чтобы он просто повторил запрос и не заботился о конкретной ошибке. Таким образом, мы в основном предлагаем действия вызывающей стороне - «что делать».
Мы делаем это не часто, потому что мы не знаем всех случаев использования потребителей. Но представьте себе, что это какой-то конкретный компонент, который мы знаем, как лучше всего действовать для вызывающего абонента - поэтому должны ли мы тогда использовать подход «что делать»?
источник
if( ! function() ) handle_something();
, но имея возможность обработать ошибку там, где вы на самом деле знаете контекст вызова - то есть сообщить клиенту, чтобы он вызывал системного администратора, если ваш сервер вышел из строя, или перезагрузите автоматически, если соединение разорвано, но предупредите вас в В случае звонящего это еще один микросервис. Пусть блоки захвата справятся с ловлей.Ответы:
Это почти всегда терпит неудачу по крайней мере для одного из ваших абонентов, для которого это поведение невероятно раздражает. Не думай, что ты знаешь лучше. Расскажите своим пользователям, что происходит, а не то, что вы думаете, что они должны делать с этим. Во многих случаях уже ясно, каким должен быть разумный курс действий (и, если это не так, внесите предложение в руководство пользователя).
Например, даже исключения, приведенные в вашем вопросе, демонстрируют ваше ошибочное предположение: a
ServiceTemporaryUnavailable
приравнивается к «попробуйте еще раз позже» иRateLimitExceeded
приравнивается к «вау, успокойтесь, возможно, отрегулируйте параметры таймера и повторите попытку через несколько минут». Но пользователь может также захотеть поднять какую-то тревогуServiceTemporaryUnavailable
(которая указывает на проблему с сервером), а не дляRateLimitExceeded
(которая не делает).Дайте им выбор .
источник
RetryableError
.Учитывая эту идею:
... я хотел бы предположить, что вы можете смешивать опасения, связанные с сообщением об ошибке, с курсами действий, чтобы реагировать на нее таким образом, что это может ухудшить универсальность вашего кода или потребовать много «точек перевода» для исключений. ,
Например, если я смоделирую транзакцию, связанную с загрузкой файла, она может завершиться неудачей по ряду причин. Возможно, загрузка файла включает в себя загрузку плагина, который не существует на компьютере пользователя. Возможно, файл просто поврежден, и мы столкнулись с ошибкой при его анализе.
Независимо от того, что произойдет, скажем, что действие состоит в том, чтобы сообщить о том, что случилось с пользователем, и подсказать ему, что он хочет с этим сделать («повторите попытку, загрузите другой файл, отмените»).
Метатель против Ловца
Такой порядок действий применяется независимо от того, с какой ошибкой мы столкнулись в этом случае. Он не встроен в общую идею ошибки синтаксического анализа, он не встроен в общую идею неудачной загрузки плагина. Он встроен в идею возникновения таких ошибок в точном контексте загрузки файла (сочетание загрузки файла и сбоя). Поэтому, как правило, я рассматриваю это, грубо говоря, как
catcher's
ответственность за определение направления действий в ответ на выброшенное исключение (например: подсказка пользователю параметров), а не заthrower's
.Иными словами, сайты,
throw
исключения которых обычно не имеют такого рода контекстной информации, особенно если функции, которые выдают, обычно применимы. Даже в полностью выродившемся контексте, когда у них есть эта информация, вы в конечном итоге заглядываете с точки зрения поведения восстановления, встраивая ее вthrow
сайт. Сайтыcatch
, которые обычно имеют наибольшее количество информации, доступной для определения курса действий, и дают вам одно центральное место для изменения, если этот курс действий когда-либо изменится для данной транзакции.Когда вы начинаете пытаться генерировать исключения, которые больше не сообщают о том, что не так, а пытаются определить, что делать, это может ухудшить универсальность и гибкость вашего кода. Ошибка синтаксического анализа не всегда должна приводить к такому виду приглашения, она зависит от контекста, в котором выдается такое исключение (транзакция, в которой оно было выдано).
Метатель слепых
В целом, дизайн обработки исключений часто вращается вокруг идеи слепого метателя. Он не знает, как будет поймано исключение или где. То же самое относится и к более старым формам восстановления ошибок с использованием ручного распространения ошибок. Сайты, которые сталкиваются с ошибками, не включают в себя действия пользователя, они только встраивают минимальную информацию, чтобы сообщить, с какой ошибкой они столкнулись.
Перевернутые обязанности и обобщение ловца
Подумав об этом более тщательно, я пытался представить себе тот тип кодовой базы, где это может стать соблазном. Мое воображение (возможно, ошибочное) состоит в том, что ваша команда по-прежнему играет роль «потребителя» и реализует большую часть вызывающего кода. Возможно, у вас есть много разрозненных транзакций (много
try
блоков), которые могут столкнуться с одинаковыми наборами ошибок, и все они, с точки зрения проектирования, должны привести к единому курсу действий по восстановлению.Принимая во внимание мудрый совет из
Lightness Races in Orbit's
тонкого ответа (который, я думаю, на самом деле исходит из продвинутого библиотечно-ориентированного мышления), у вас все еще может возникнуть искушение создавать исключения «что делать», только ближе к сайту восстановления транзакций.Из этого здесь можно найти промежуточный, общий сайт обработки транзакций, который фактически централизует проблемы «что делать», но все же в контексте отлова.
Это применимо только в том случае, если вы можете разработать какую-то общую функцию, которую используют все эти внешние транзакции (например: функция, которая вводит другую функцию для вызова, или абстрактный класс базовых транзакций с переопределенным поведением, моделирующий этот сайт промежуточной транзакции, который выполняет сложный перехват). ).
Тем не менее, он может быть ответственным за централизацию действий пользователя в ответ на множество возможных ошибок, и все же в контексте ловли, а не выбрасывания. Простой пример (псевдокод Python-ish, и я совсем не опытный разработчик Python, так что может быть более идиоматический способ сделать это):
[Надеюсь, с лучшим именем, чем
general_catcher
]. В этом примере вы можете передать функцию, содержащую задачу, которую нужно выполнить, но при этом воспользоваться преимуществами обобщенного / унифицированного поведения перехвата для всех типов исключений, которые вас интересуют, и продолжать расширять или изменять часть «что делать». Вам нравится из этого центрального местоположения и все еще вcatch
контексте, где это обычно поощряется. Лучше всего то, что мы можем помешать участкам метания относиться к себе с тем, «что делать» (сохраняя понятие «метатель слепых»).Если вы не найдете ни одного из этих предложений здесь полезным и у вас есть сильное искушение выбросить исключения «что делать» в любом случае, в основном имейте в виду, что это, как минимум, очень антиидиоматично, а также потенциально препятствует обобщенному мышлению.
источник
Я думаю, что большую часть времени было бы лучше передать аргументы функции, говорящей ей, как обращаться с такими ситуациями.
Например, рассмотрим функцию:
Я могу передать RetryPolicy.noRetries () или RetryPolicy.retries (3) или что угодно. В случае повторяющейся ошибки, он будет консультироваться с политикой, чтобы решить, следует ли повторить попытку.
источник
new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)
или что-то в этом роде, потому что этоRateLimitExceeded
может потребовать обработки иначеServiceTemporaryUnavailable
. После этого я подумал: лучше бросить исключение, потому что оно дает более гибкий контроль.