Usamos exceções para permitir que o consumidor do código lide com comportamentos inesperados de uma maneira útil. Geralmente, as exceções são criadas em torno do cenário "o que aconteceu" - como FileNotFound
(não foi possível encontrar o arquivo que você especificou) ou ZeroDivisionError
(não foi possível executar a 1/0
operação).
E se houver a possibilidade de especificar o comportamento esperado do consumidor?
Por exemplo, imagine que temos um fetch
recurso, que executa uma solicitação HTTP e retorna dados recuperados. E, em vez de erros como ServiceTemporaryUnavailable
ou RateLimitExceeded
gostaríamos de RetryableError
sugerir ao consumidor que ele deveria apenas tentar novamente a solicitação e não se importar com falhas específicas. Então, estamos basicamente sugerindo uma ação para o chamador - o "o que fazer".
Não fazemos isso com frequência porque não conhecemos todos os casos de uso dos consumidores. Mas imagine que seja algum componente específico que conhecemos o melhor curso de ações para um chamador - então devemos usar a abordagem "o que fazer"?
fonte
if( ! function() ) handle_something();
, mas poder lidar com o erro em algum lugar em que você realmente conhece o contexto de chamada - por exemplo, dizer ao cliente para ligar para um administrador do sistema se o servidor falhar ou recarregar automaticamente se a conexão cair, mas alertá-lo em caso o chamador seja outro microsserviço. Deixe os blocos de captura lidar com a captura.Respostas:
Isso quase sempre falha em pelo menos um dos chamadores, para o qual esse comportamento é incrivelmente irritante. Não presuma que você conhece melhor. Diga a seus usuários o que está acontecendo, não o que você supõe que eles devam fazer a respeito. Em muitos casos, já está claro o que deve ser um curso de ação sensato (e, se não for, faça uma sugestão no manual do usuário).
Por exemplo, mesmo as exceções fornecidas em sua pergunta demonstram sua suposição quebrada: a
ServiceTemporaryUnavailable
equivale a "tente novamente mais tarde" eRateLimitExceeded
equivale a "uau, relaxe, talvez ajuste os parâmetros do temporizador e tente novamente em alguns minutos". Mas o usuário pode querer acionar algum tipo de alarmeServiceTemporaryUnavailable
(o que indica um problema no servidor), e não paraRateLimitExceeded
(o que não).Dê a eles a escolha .
fonte
RetryableError
.Dada esta ideia:
... uma coisa que eu sugiro é que você possa estar misturando preocupações de relatar um erro com cursos de ação para responder a ele de uma maneira que possa degradar a generalidade do seu código ou exigir muitos "pontos de conversão" para exceções .
Por exemplo, se eu modelar uma transação envolvendo o carregamento de um arquivo, ela poderá falhar por vários motivos. Talvez o carregamento do arquivo envolva o carregamento de um plug-in que não existe na máquina do usuário. Talvez o arquivo esteja simplesmente corrompido e encontramos um erro ao analisá-lo.
Não importa o que aconteça, digamos que o curso de ação seja relatar o que aconteceu com o usuário e avisá-lo sobre o que ele deseja fazer ("tente novamente, carregue outro arquivo, cancele").
Lançador vs. Apanhador
Esse curso de ação se aplica independentemente do tipo de erro que encontramos neste caso. Ele não está incorporado na idéia geral de um erro de análise, não está incorporado na idéia geral de não carregar um plug-in. Ele está incorporado na idéia de encontrar esses erros durante o contexto preciso de carregar um arquivo (a combinação de carregar e falhar). Então, tipicamente, eu vejo isso de maneira grosseira como a
catcher's
responsabilidade de determinar o curso da ação em resposta a uma exceção lançada (por exemplo: solicitando opções ao usuário), não athrower's
.Dito de outra forma, os sites em que as
throw
exceções geralmente não têm esse tipo de informação contextual, especialmente se as funções lançadas forem geralmente aplicáveis. Mesmo em um contexto totalmente degeneralizado quando eles têm essas informações, você acaba se esquivando em termos de comportamento de recuperação, incorporando-o aothrow
site. Os sites quecatch
geralmente têm a maior quantidade de informações disponíveis para determinar um curso de ação e oferecem a você um local central para modificar se esse curso de ação mudar alguma vez para a transação em questão.Quando você começa a tentar lançar exceções, não relatando mais o que está errado, mas tentando determinar o que fazer, isso pode degradar a generalidade e a flexibilidade do seu código. Um erro de análise nem sempre deve levar a esse tipo de prompt, varia de acordo com o contexto em que uma exceção é lançada (a transação sob a qual foi lançada).
O Lançador Cego
Em geral, muito do design do tratamento de exceções geralmente gira em torno da ideia de um lançador cego. Não sabe como a exceção será capturada ou onde. O mesmo se aplica a formas ainda mais antigas de recuperação de erros usando propagação manual de erros. Os sites que encontram erros não incluem um curso de ação do usuário, eles incorporam apenas as informações mínimas para relatar que tipo de erro foi encontrado.
Responsabilidades invertidas e generalização do apanhador
Ao pensar sobre isso com mais cuidado, eu estava tentando imaginar o tipo de base de código em que isso pode se tornar uma tentação. Minha imaginação (possivelmente errada) é que sua equipe ainda está desempenhando o papel de "consumidor" aqui e implementando a maior parte do código de chamada. Talvez você tenha muitas transações díspares (muitos
try
blocos) que podem ser executadas nos mesmos conjuntos de erros e todas, da perspectiva do design, levam a um curso uniforme de ação de recuperação.Levando em conta os conselhos sábios da
Lightness Races in Orbit's
resposta correta (que eu acho que realmente vem de uma mentalidade avançada orientada a bibliotecas), você ainda pode ser tentado a lançar exceções "o que fazer", apenas mais perto do site de recuperação de transações.Pode ser possível encontrar um site intermediário comum de manipulação de transações a partir daqui, que realmente centralize as preocupações "o que fazer", mas ainda dentro do contexto da captura.
Isso se aplicaria apenas se você pudesse projetar algum tipo de função geral usada por todas essas transações externas (por exemplo: uma função que insere outra função para chamar ou uma classe base de transação abstrata com comportamento substituível que modela esse site intermediário de transações que realiza a captura sofisticada )
No entanto, esse poderia ser responsável por centralizar o curso de ação do usuário em resposta a uma variedade de possíveis erros, e ainda dentro do contexto de pegar e não jogar. Exemplo simples (pseudocódigo Python-ish, e eu não sou nem um desenvolvedor experiente em Python, por isso pode haver uma maneira mais idiomática de fazer isso):
[Espero que com um nome melhor que
general_catcher
]. Neste exemplo, você pode transmitir uma função que contém qual tarefa executar, mas ainda se beneficiar do comportamento de captura generalizada / unificada para todos os tipos de exceções em que você está interessado, e continuar a estender ou modificar a parte "o que fazer" você gosta deste local central e ainda dentro de umcatch
contexto em que isso normalmente é incentivado. O melhor de tudo é que podemos impedir que os locais de lançamento se preocupem com "o que fazer" (preservando a noção de "atirador cego").Se você não encontrar nenhuma dessas sugestões aqui e houver uma forte tentação de lançar exceções "o que fazer" de qualquer maneira, lembre-se principalmente de que isso é muito anti-idiomático, pelo menos, além de potencialmente desencorajar uma mentalidade generalizada.
fonte
Eu acho que na maioria das vezes seria melhor passar argumentos para a função dizendo como lidar com essas situações.
Por exemplo, considere uma função:
Posso passar RetryPolicy.noRetries () ou RetryPolicy.retries (3) ou qualquer outra coisa. No caso de falha repetível, ele consultará a política para decidir se deve ou não tentar novamente.
fonte
new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)
ou algo assim, porqueRateLimitExceeded
talvez seja necessário lidar de maneira diferenteServiceTemporaryUnavailable
. Depois de escrever isso, meu pensamento é: melhor lançar uma exceção, porque ela oferece um controle mais flexível.