Como evitar lançar exceções irritantes?

21

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.
Mike
fonte
1
Seu exemplo de salvamento que pode falhar não é realmente uma exceção terrivelmente irritante. É bastante comum e provavelmente deveria ser simplesmente uma exceção.
31512 S.Lott
@ S.Lott: O que você quer dizer com isso é bastante comum? A situação em si, ou lançando uma exceção nessa situação? De qualquer forma, concordo com você que não é evidente se esta é de fato uma situação irritante. Vou atualizar a pergunta.
6762 Mike
"A situação em si, ou lançando uma exceção nesta situação" Ambos.
315 S.Lott

Respostas:

24

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?

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.

Eric Lippert
fonte
Todos muito bons pontos, obrigado. Agora, aqui está uma pergunta retórica que provavelmente resume minha postagem: se você redesenhasse o File.Open (), criaria um File.TryOpen ()? Como você comunicaria ao consumidor o motivo da falha? Ou lançar uma exceção exógena é realmente o melhor compromisso aqui?
6762 Mike
10
@ Mike: Os sistemas de arquivos são um bom exemplo do uso de exceções exógenas. Eles falham raramente, então o fracasso é excepcional. Eles falham de forma imprevisível e por razões totalmente fora do controle do chamador (não há um "bloqueio" que possa ser usado para manter o cabo Ethernet conectado), e as falhas são diversas e acionáveis ​​(ou seja, uma falha porque o arquivo é não encontrado vs o arquivo foi encontrado, mas você não tem acesso de gravação, ambos podem ser acionados de maneiras diferentes.) Todos esses são motivos para representar uma falha como uma exceção.
Eric Lippert
Considero a pergunta a ser respondida agora, mas estou curiosa;) Se o método pode falhar por 2 ou mais razões, as falhas são acionáveis, as falhas são excepcionais e as falhas não podem ser detectadas com antecedência nem evitadas, o que você faria?
6602 Mike
@ Mike Não posso falar por Eric, mas isso parece um bom lugar para códigos de erro. Retornando um membro de um enum, talvez.
Matthew Leia
1

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.

FrustratedWithFormsDesigner
fonte
0

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.

Tulains Córdova
fonte