Para lidar com vários erros possíveis que não devem interromper a execução, tenho uma error
variável que os clientes podem verificar e usar para lançar exceções. Isso é um anti-padrão? Existe uma maneira melhor de lidar com isso? Para um exemplo disso em ação, você pode ver a API mysqli do PHP . Suponha que os problemas de visibilidade (acessadores, escopo público e privado, seja a variável em uma classe ou global?) Sejam tratados corretamente.
44
try
/catch
existe para. Além disso, você pode colocar sua pilhatry
/catch
muito mais longe em um local mais apropriado para manipulá-la (permitindo uma maior separação de preocupações).Respostas:
Se um idioma suportar inerentemente exceções, é preferível lançar exceções e os clientes poderão capturar a exceção se não quiserem que ela resulte em falha. De fato, os clientes do seu código esperam exceções e se deparam com muitos bugs porque não verificarão os valores de retorno.
Existem algumas vantagens em usar exceções, se você tiver uma escolha.
Mensagens
Exceções contêm mensagens de erro legíveis pelo usuário que podem ser usadas pelos desenvolvedores para depuração ou mesmo exibidas aos usuários, se desejado. Se o código de consumo não puder manipular a exceção, ele poderá sempre registrá-lo para que os desenvolvedores possam percorrer os logs sem precisar parar em todos os outros rastreamentos para descobrir qual era o valor de retorno e mapeá-lo em uma tabela para descobrir qual era o valor. exceção real.
Com valores de retorno, não há informações adicionais que possam ser fornecidas com facilidade. Alguns idiomas suportam a realização de chamadas de método para obter a última mensagem de erro. Portanto, essa preocupação é um pouco menor, mas isso exige que o chamador faça chamadas extras e às vezes exigirá acesso a um 'objeto especial' que carrega essas informações.
No caso de mensagens de exceção, forneço o máximo de contexto possível, como:
Compare isso com um código de retorno -85. Qual você prefere?
Pilhas de chamadas
As exceções também costumam ter pilhas de chamadas detalhadas que ajudam a depurar o código cada vez mais rápido e também podem ser registradas pelo código de chamada, se desejado. Isso permite que os desenvolvedores identifiquem o problema normalmente na linha exata e, portanto, são muito poderosos. Mais uma vez, compare isso com um arquivo de log com valores de retorno (como -85, 101, 0 etc.), qual você prefere?
Falha na abordagem tendenciosa rápida
Se um método for chamado em algum lugar que falhar, lançará uma exceção. O código de chamada deve suprimir a exceção explicitamente ou ela falhará. Eu achei isso realmente incrível, porque durante o desenvolvimento e teste (e até na produção) o código falha rapidamente, forçando os desenvolvedores a corrigi-lo. No caso de valores de retorno, se uma verificação de um valor de retorno for perdida, o erro será ignorado silenciosamente e o erro aparecerá em algum lugar inesperado, geralmente com um custo muito maior para depuração e correção.
Quebrando e desembrulhando exceções
Exceções podem ser agrupadas dentro de outras exceções e depois desembrulhadas, se necessário. Por exemplo, seu código pode lançar o
ArgumentNullException
que o código de chamada pode quebrar dentro de umUnableToRetrievePolicyException
porque essa operação falhou no código de chamada. Enquanto o usuário pode receber uma mensagem semelhante ao exemplo que forneci acima, algum código de diagnóstico pode desembrulhar a exceção e descobrir queArgumentNullException
ele causou o problema, o que significa que é um erro de codificação no código do consumidor. Isso pode disparar um alerta para que o desenvolvedor possa corrigir o código. Esses cenários avançados não são fáceis de implementar com os valores de retorno.Simplicidade do código
Este é um pouco mais difícil de explicar, mas aprendi com essa codificação tanto com valores de retorno quanto com exceções. O código que foi escrito usando valores de retorno normalmente fazia uma chamada e, em seguida, fazia uma série de verificações sobre qual era o valor de retorno. Em alguns casos, ele chamaria outro método e agora terá outra série de verificações para os valores de retorno desse método. Com exceções, o tratamento de exceções é muito mais simples na maioria, se não em todos os casos. Você tem um bloco try / catch / finalmente, com o tempo de execução tentando o melhor para executar o código nos blocos finalmente para limpeza. Até blocos try / catch / finalmente aninhados são relativamente mais fáceis de acompanhar e manter do que aninhados if / else e valores de retorno associados de vários métodos.
Conclusão
Se a plataforma que você está usando suporta exceções (especialmente Java ou .NET), você deve definitivamente assumir que não há outra maneira, exceto lançar exceções, porque essas plataformas têm diretrizes para lançar exceções e seus clientes esperam tão. Se eu estivesse usando sua biblioteca, não me preocuparei em verificar os valores de retorno, porque espero que exceções sejam lançadas, é assim que o mundo nessas plataformas é.
No entanto, se fosse C ++, seria um pouco mais desafiador determinar porque já existe uma grande base de código com códigos de retorno e um grande número de desenvolvedores é ajustado para retornar valores em vez de exceções (por exemplo, o Windows é repleto de HRESULTs) . Além disso, em muitos aplicativos, também pode ser um problema de desempenho (ou pelo menos percebido).
fonte
ErrorStateReturnVariable
superclasse, e uma de suas propriedades éInnerErrorState
(que é uma instância deErrorStateReturnVariable
), que as subclasses de implementação podem definir para mostrar uma cadeia de erros ... oh, espere. : pAs variáveis de erro são uma relíquia de idiomas como C, onde as exceções não estavam disponíveis. Hoje, você deve evitá-los, exceto quando estiver escrevendo uma biblioteca que é potencialmente usada a partir de um programa C (ou um idioma semelhante sem manipulação de exceção).
Obviamente, se você tiver um tipo de erro que possa ser melhor classificado como "aviso" (= sua biblioteca pode fornecer um resultado válido e o chamador pode ignorar o aviso se achar que isso não é importante), um indicador de status no formato de uma variável pode fazer sentido, mesmo em idiomas, com exceções. Mas cuidado. Os chamadores da biblioteca tendem a ignorar esses avisos, mesmo que não devam. Então pense duas vezes antes de introduzir essa construção em sua biblioteca.
fonte
Existem várias maneiras de sinalizar um erro:
O problema da variável de erro é que é fácil esquecer de verificar.
O problema das exceções é que isso cria caminhos ocultos de execuções e, embora seja fácil escrever try / catch, é realmente difícil obter uma recuperação adequada na cláusula catch (sem suporte dos sistemas / compiladores de tipos).
O problema dos manipuladores de condições é que eles não se compõem bem: se você possui execução dinâmica de código (funções virtuais), é impossível prever quais condições devem ser tratadas. Além disso, se a mesma condição puder ser levantada em vários pontos, não há como dizer que uma solução uniforme pode ser aplicada a cada vez e ela rapidamente se torna confusa.
Os retornos polimórficos (
Either a b
em Haskell) são minha solução favorita até agora:O único problema é que eles podem levar a verificações excessivas; os idiomas que os utilizam têm expressões idiomáticas para encadear as chamadas de funções que os utilizam, mas ainda pode exigir um pouco mais de digitação / confusão. Em Haskell, isso seria mônadas ; no entanto, isso é muito mais assustador do que parece, consulte Programação Orientada a Ferrovias .
fonte
Eu acho horrível. No momento, estou refatorando um aplicativo Java que usa valores de retorno em vez de exceções. Embora você possa não estar trabalhando com Java, acho que isso se aplica.
Você acaba com um código como este:
Ou isto:
Eu prefiro que as ações lançem exceções, então você acaba com algo como:
Você pode agrupar isso em uma tentativa de captura e obter a mensagem da exceção ou pode optar por ignorar a exceção, por exemplo, ao excluir algo que já pode ter desaparecido. Ele também preserva seu rastreamento de pilha, se você tiver um. Os próprios métodos também se tornam mais fáceis. Em vez de lidar com as exceções, eles apenas lançam o que deu errado.
Código atual (horrível):
Novo e melhorado:
O rastreamento de faixas é preservado e a mensagem está disponível na exceção, em vez do inútil "Algo deu errado!".
Obviamente, você pode fornecer melhores mensagens de erro e deve. Mas este post está aqui porque o código atual no qual estou trabalhando é uma dor e você não deve fazer o mesmo.
fonte
throw new Exception("Something went wrong with " + instanceVar, ex);
"Para lidar com vários erros possíveis, isso não deve interromper a execução"
Se você quer dizer que os erros não devem interromper a execução da função atual, mas devem ser relatados ao chamador de alguma maneira - você tem algumas opções que realmente não foram mencionadas. Este caso é realmente mais um aviso do que um erro. Arremessar / Retornar não é uma opção, pois encerra a função atual. Uma única mensagem de erro parametrizada ou retornada permite apenas a ocorrência de um desses erros.
Dois padrões que eu usei são:
Uma coleção de erro / aviso, passada ou mantida como uma variável de membro. Ao qual você anexa itens e continua processando. Eu, pessoalmente, não gosto muito dessa abordagem, pois sinto que ela diminui o poder do chamador.
Passe um objeto manipulador de erro / aviso (ou defina-o como uma variável de membro). E cada erro chama uma função de membro do manipulador. Dessa forma, o chamador pode decidir o que fazer com esses erros sem fim.
O que você passa para essas coleções / manipuladores deve conter contexto suficiente para que o erro seja manipulado "corretamente" - uma string geralmente é muito pequena, pois algumas instâncias de Exception costumam ser sensatas - mas algumas vezes desaprovadas (como abuso de exceções) .
O código oficial usando um manipulador de erros pode ter esta aparência
fonte
warnings
pacote do Python , que fornece outro padrão para esse problema.Geralmente, não há nada de errado em usar esse padrão ou esse padrão, desde que você use o padrão usado por todos os outros. No desenvolvimento de Objective-C , o padrão mais preferido é passar um ponteiro em que o método chamado pode depositar um objeto NSError. As exceções são reservadas para erros de programação e levam a uma falha (a menos que você tenha programadores Java ou .NET criando seu primeiro aplicativo para iPhone). E isso funciona muito bem.
fonte
A pergunta já está respondida, mas não consigo evitar.
Você realmente não pode esperar que o Exception forneça uma solução para todos os casos de uso. Martelar alguém?
Há casos em que Exceções não são o fim de tudo e tudo, por exemplo, se um método recebe uma solicitação e é responsável por validar todos os campos passados, e não apenas o primeiro, é necessário pensar que deve ser possível indique a causa do erro para mais de um campo. Também deve ser possível indicar se a natureza da validação impede o usuário de ir mais longe ou não. Um exemplo disso seria uma senha não forte. Você pode mostrar uma mensagem ao usuário indicando que a senha digitada não é muito forte, mas é forte o suficiente.
Você poderia argumentar que todas essas validações poderiam ser lançadas como uma exceção no final do módulo de validação, mas seriam códigos de erro em qualquer coisa, exceto no nome.
Portanto, a lição aqui é: Exceções têm seus lugares, assim como códigos de erro. Escolheu sabiamente.
fonte
Validator
(interface) injetada no método em questão (ou no objeto por trás dele). Dependendo da injeção,Validator
o método continuará com senhas ruins - ou não. O código ao redor poderia então tentar aWeakValidator
se o usuário pedisse depois, por exemplo, aWeakPasswordException
foi lançado pelo inicialmente tentadoStrongValidator
.MiddlyStrongValidator
ou algo assim. E se isso realmente não interrompeu seu fluxo, eleValidator
deve ter sido chamado com antecedência, ou seja, antes de prosseguir com o fluxo enquanto o usuário ainda está digitando sua senha (ou similar). Mas a validação não fazia parte do método em questão em primeiro lugar. :) Provavelmente uma questão de gosto, afinal de contas ...AggregateException
(ou similarValidationException
) e coloco exceções específicas para cada problema de validação no InnerExceptions. Por exemplo, pode serBadPasswordException
: "A senha do usuário é menor que o tamanho mínimo de 6" ouMandatoryFieldMissingException
: "O primeiro nome deve ser fornecido ao usuário" etc. Isso não é equivalente a códigos de erro. Todas essas mensagens podem ser exibidas para o usuário de uma maneira que eles entendam, e se umaNullReferenceException
foi lançada, obtivemos um bug.Existem casos de uso nos quais os códigos de erro são preferíveis às exceções.
Se o seu código puder continuar apesar do erro, mas precisar de relatórios, uma exceção será uma má escolha, pois as exceções encerram o fluxo. Por exemplo, se você estiver lendo um arquivo de dados e descobrir que ele contém alguns dados incorretos não terminais, talvez seja melhor ler o restante do arquivo e relatar o erro, em vez de falhar completamente.
As outras respostas abordaram por que as exceções devem ser preferidas aos códigos de erro em geral.
fonte
AcknowledgePossibleCorruption
método. #Definitivamente, não há nada errado em não usar exceções quando as exceções não são uma boa opção.
Quando a execução do código não deve ser interrompida (por exemplo, atuando na entrada do usuário que pode conter vários erros, como um programa para compilar ou um formulário para processar), acho que coletar erros em variáveis de erro como
has_errors
eerror_messages
é realmente um design muito mais elegante do que lançar uma exceção no primeiro erro. Ele permite encontrar todos os erros na entrada do usuário sem forçar o usuário a reenviar desnecessariamente.fonte
Em algumas linguagens de programação dinâmicas, você pode usar valores de erro e manipulação de exceções . Isso é feito retornando o objeto de exceção não lançada no lugar do valor de retorno comum, que pode ser verificado como um valor de erro, mas gera uma exceção se não estiver marcado.
No Perl 6 , é feito via
fail
, que, se dentro dono fatal;
escopo, retorna umFailure
objeto de exceção não-lançado especial .No Perl 5 você pode usar Contextual :: Return com o qual você pode fazer
return FAIL
.fonte
A menos que haja algo muito específico, acho que ter uma variável de erro para validação é uma má ideia. O objetivo parece ser economizar o tempo gasto na validação (você pode simplesmente retornar o valor da variável)
Mas, se você alterar alguma coisa, precisará recalcular esse valor de qualquer maneira. Não posso dizer mais sobre parar e lançar exceções.
Edição: Eu não sabia que isso é uma questão de paradigma de software, em vez de um caso específico.
Deixe-me esclarecer mais meus pontos em um caso particular em que minha resposta faria sentido
Existem dois tipos de erros:
Na camada de serviço, não há escolha a não ser usar o objeto Result como wrapper, que é a equivalência da variável de erro. É possível simular uma exceção por meio de chamada de serviço em protocolo como http, mas definitivamente não é uma boa coisa a fazer. Não estou falando sobre esse tipo de erro e não acho que esse seja o tipo de erro solicitado nesta pergunta.
Eu estava pensando no segundo tipo de erro. E minha resposta é sobre esse segundo tipo de erro. Nos objetos de entidade, existem opções para nós, algumas delas são
Usar variável de validação é o mesmo que ter um único método de validação para cada objeto de entidade. Em particular, o usuário pode definir o valor de maneira a manter o levantador puramente, sem efeito colateral (geralmente é uma boa prática) ou pode-se incorporar a validação em cada levantador e salvar o resultado em uma variável de validação. A vantagem disso seria economizar tempo, o resultado da validação é armazenado em cache na variável de validação, de modo que quando o usuário chama validation () várias vezes, não há necessidade de validação múltipla.
A melhor coisa a fazer nesse caso é usar um único método de validação sem usar nenhuma validação para armazenar em cache o erro de validação. Isso ajuda a manter o levantador apenas como levantador.
fonte