A leitura do artigo de Eric Lippert sobre exceções foi definitivamente uma revelação sobre como eu deveria abordar as exceções, tanto como produtor quanto como consumidor. No entanto, ainda estou lutando para definir uma diretriz sobre como evitar exceções irritantes.
Especificamente:
- Suponha que você tenha um método Save que pode falhar porque a) alguém modificou o registro antes de você ou b) o valor que você está tentando criar já existe . Essas condições são esperadas e não excepcionais; portanto, em vez de lançar uma exceção, você decide criar uma versão Try do seu método, TrySave, que retorna um booleano indicando se a gravação foi bem-sucedida. Mas, se falhar, como o consumidor saberá qual foi o problema? Ou seria melhor retornar uma enumeração indicando o resultado, tipo Ok / RecordAlreadyModified / ValueAlreadyExists? Com integer.TryParse, esse problema não existe, pois há apenas um motivo pelo qual o método pode falhar.
- O exemplo anterior é realmente uma situação irritante? Ou lançar uma exceção nesse caso seria a maneira preferida? Eu sei que é assim que é feito na maioria das bibliotecas e estruturas, incluindo a estrutura de entidades.
- Como você decide quando criar uma versão Try do seu método versus fornecer uma maneira de testar antecipadamente se o método funcionará ou não? Atualmente, estou seguindo estas diretrizes:
- Se houver a chance de uma condição de corrida, crie uma versão Try. Isso evita a necessidade de o consumidor capturar uma exceção exógena. Por exemplo, no método Save descrito anteriormente.
- Se o método para testar a condição praticamente fizer tudo o que o método original, crie uma versão Try. Por exemplo, integer.TryParse ().
- Em qualquer outro caso, crie um método para testar a condição.
exceptions
Mike
fonte
fonte
Respostas:
Boa pergunta.
A primeira pergunta que me vem à cabeça é: se os dados já estão lá, em que sentido o salvamento falhou ? Com certeza parece que me conseguiu. Mas vamos supor, por uma questão de argumento, que você realmente tem muitas razões diferentes pelas quais uma operação pode falhar.
A segunda pergunta que me vem à mente é: as informações que você deseja retornar ao usuário são acionáveis ? Ou seja, eles vão tomar alguma decisão com base nessas informações?
Quando a luz "verificar motor" acende, abro o capô, verifico se há um motor no meu carro que não está pegando fogo e o levo para a garagem. É claro que na garagem eles têm todos os tipos de equipamentos de diagnóstico para fins especiais que lhes dizem por que a luz do mecanismo de verificação está acesa, mas, na minha perspectiva, o sistema de aviso é bem projetado. Não me importo se o problema é porque o sensor de oxigênio está registrando um nível anormal de oxigênio na câmara de combustão ou porque o detector de velocidade ociosa está desconectado ou o que seja. Eu vou tomar a mesma ação, ou seja, deixar alguém descobrir isso .
O chamador se importa por que o salvamento falhou? Eles farão algo a respeito, além de desistir ou tentar novamente?
Vamos supor, por uma questão de argumento, que o chamador realmente executará ações diferentes, dependendo do motivo pelo qual a operação falhou.
A terceira pergunta que vem à mente é: o modo de falha é excepcional ? Eu acho que você pode estar confundindo possível com excepcional . Eu pensaria em dois usuários tentando modificar o mesmo registro ao mesmo tempo que uma situação excepcional, mas possível, e não comum.
Vamos supor, por uma questão de argumento, que isso é excepcional.
A quarta pergunta que vem à mente é: existe uma maneira de detectar com segurança a má situação antes do tempo?
Se a situação ruim está no meu balde "exógeno", então não. Não há como dizer com segurança "outro usuário modificou esse registro?" porque eles podem modificá-lo depois que você faz a pergunta . A resposta é obsoleta assim que é produzida.
A quinta pergunta que vem à mente é: existe uma maneira de projetar a API para que a situação ruim possa ser evitada?
Por exemplo, você pode fazer com que a operação "salvar" exija duas etapas. Etapa 1: adquira um bloqueio no registro que está sendo modificado. Essa operação é bem-sucedida ou falha e, portanto, pode retornar um booleano. O chamador pode então ter uma política sobre como lidar com a falha: espere um pouco e tente novamente, desista, o que for. Etapa 2: depois que a trava for adquirida, salve e libere a trava. Agora, o salvamento sempre é bem - sucedido e, portanto, não há necessidade de se preocupar com nenhum tipo de tratamento de erros. Se o salvamento falhar, isso é realmente excepcional.
fonte
No seu exemplo, se a situação ValueAlreadyExists puder ser facilmente verificada, ela deve ser verificada e uma exceção pode ser levantada antes de tentar o Save, não acho que uma tentativa seja necessária nessa situação. A condição de corrida é mais difícil de verificar antes do tempo, portanto, agrupar o Save in a Try nesse caso é provavelmente uma boa ideia.
Em geral, se há uma condição que eu acho muito provável (como NoDataReturned, DivideByZero, etc ...) OU é muito fácil de procurar (como uma coleção vazia ou um valor NULL), tento verificar se há com antecedência e lidar com isso antes que eu chegue ao ponto em que eu teria que pegar uma exceção. Admito que nem sempre é fácil conhecer essas condições com antecedência, às vezes elas só aparecem quando o código está sendo testado com rigor.
fonte
O
save()
método deve gerar uma exceção.A camada superior deve capturar e informar o usuário , sem finalizar o programa, a menos que seja um programa de linha de comando semelhante ao Unix, caso em que não há problema em finalizar.
Os valores de retorno não são uma boa maneira de gerenciar exceções.
fonte