Meu caso específico aqui é que o usuário pode passar uma string para o aplicativo, o aplicativo analisa e atribui a objetos estruturados. Às vezes, o usuário pode digitar algo inválido. Por exemplo, suas informações podem descrever uma pessoa, mas elas podem dizer que sua idade é "maçã". O comportamento correto nesse caso é reverter a transação e informar ao usuário que ocorreu um erro e eles terão que tentar novamente. Pode haver um requisito para relatar todos os erros que podemos encontrar na entrada, não apenas o primeiro.
Nesse caso, argumentei que deveríamos lançar uma exceção. Ele discordou, dizendo: "Exceções devem ser excepcionais: espera-se que o usuário insira dados inválidos, portanto esse não é um caso excepcional". Eu realmente não sabia como argumentar sobre esse ponto, porque, por definição da palavra, ele parece estar certo.
Mas, entendo que é por isso que as exceções foram inventadas em primeiro lugar. Antes, você tinha que inspecionar o resultado para ver se havia um erro. Se você não verificasse, coisas ruins poderiam acontecer sem você perceber.
Sem exceções, todos os níveis da pilha precisam verificar o resultado dos métodos que chamam e, se um programador esquecer de fazer check-in em um desses níveis, o código poderá prosseguir acidentalmente e salvar dados inválidos (por exemplo). Parece mais propenso a erros dessa maneira.
De qualquer forma, fique à vontade para corrigir qualquer coisa que eu tenha dito aqui. Minha principal pergunta é se alguém diz que as exceções devem ser excepcionais. Como saber se meu caso é excepcional?
fonte
Respostas:
Foram criadas exceções para ajudar a facilitar o tratamento de erros com menos confusão de códigos. Você deve usá-los nos casos em que eles facilitam o tratamento de erros com menos confusão de código. Esse negócio de "exceções somente para circunstâncias excepcionais" decorre de um momento em que o tratamento de exceções foi considerado um golpe de desempenho inaceitável. Esse não é mais o caso na grande maioria do código, mas as pessoas ainda expressam a regra sem se lembrar do motivo.
Especialmente em Java, que talvez seja a linguagem mais apaixonada por exceções já concebida, você não deve se sentir mal ao usar exceções quando isso simplifica seu código. De fato, a própria
Integer
classe de Java não tem como verificar se uma string é um número inteiro válido sem potencialmente gerar aNumberFormatException
.Além disso, embora você não possa confiar apenas na validação da interface do usuário, lembre-se de que a interface do usuário foi projetada corretamente, como o uso de um girador para inserir valores numéricos curtos, um valor não numérico que faça parte do back-end seria realmente um condição excepcional.
fonte
throw new ...
. Ou lance exceções personalizadas, em que fillInStackTrace () é substituído. Então você não deve notar nenhuma degradação no desempenho, para não falar em hits .if (foo() == ERROR) { return ERROR; } else { // continue }
em todos os níveis. Se você lançar uma exceção desmarcada, não haverá ruído e redundância "se erro retornar erro". Além disso, se você estiver passando funções como argumentos, o uso de um código de erro poderá alterar a assinatura da função para um tipo incompatível, mesmo que o erro possa não ocorrer.Quando uma exceção deve ser lançada? Quando se trata de código, acho que a seguinte explicação é muito útil:
Uma exceção é quando um membro falha ao concluir a tarefa que deve executar conforme indicado pelo nome . (Jeffry Richter, CLR via C #)
Por que isso é útil? Isso sugere que depende do contexto em que algo deve ser tratado como uma exceção ou não. No nível das chamadas de método, o contexto é dado por (a) nome, (b) assinatura do método e (b) código do cliente, que utiliza ou se espera que utilize o método.
Para responder sua pergunta, você deve dar uma olhada no código, onde a entrada do usuário é processada. Pode ser algo como isto:
O nome do método sugere que alguma validação é feita? Não. Nesse caso, um PersonData inválido deve gerar uma exceção.
Suponha que a classe tenha outro método parecido com este:
O nome do método sugere que alguma validação é feita? Sim. Nesse caso, um PersonData inválido não deve lançar uma exceção.
Para juntar as coisas, os dois métodos sugerem que o código do cliente fique assim:
Quando não está claro se um método deve lançar uma exceção, talvez seja devido a um nome ou assinatura de método mal escolhido. Talvez o design da classe não esteja claro. Às vezes, você precisa modificar o design do código para obter uma resposta clara à pergunta se uma exceção deve ser lançada ou não.
fonte
struct
chamado "ValidationResult" e estruturei meu código da maneira que você descreve.Validate
(retornando False se inválido) e uma vez duranteSave
(lançando uma exceção específica e bem documentada, se inválido). Obviamente, o resultado da validação pode ser armazenado em cache dentro do objeto, mas isso adicionaria complexidade adicional, pois o resultado da validação precisaria ser invalidado nas alterações.Validate()
seja chamado dentro doSave()
método, e detalhes específicos doValidationResult
podem ser usados para construir uma mensagem apropriada para a exceção.Sobre esse argumento:
Qualquer exceção que você pegar, você deve esperar porque, bem, você decidiu pegá-lo. E assim, por essa lógica, você nunca deve lançar nenhuma exceção que realmente planeja capturar.
Por isso, acho que "exceções devem ser excepcionais" é uma terrível regra de ouro.
O que você deve fazer depende do idioma. Idiomas diferentes têm convenções diferentes sobre quando exceções devem ser lançadas. Python, por exemplo, lança exceções para tudo e, quando em Python, eu sigo o exemplo. C ++, por outro lado, lança relativamente poucas exceções, e aí eu sigo o exemplo. Você pode tratar C ++ ou Java como Python e lançar exceções para tudo, mas você está trabalhando em desacordo com o modo como a linguagem espera que seja usada.
Eu prefiro a abordagem do Python, mas acho uma má idéia colocar outras linguagens nela.
fonte
"exceptions should be exceptional" is a terrible rule of thumb.
Bem dito! Essa é uma daquelas coisas que as pessoas repetem sem pensar nelas.Eu sempre penso em coisas como acessar o servidor de banco de dados ou uma API da web quando penso em exceções. Você espera que a API do servidor / web funcione, mas em um caso excepcional, pode não funcionar (o servidor está inoperante). Geralmente, uma solicitação da Web pode ser rápida, mas em circunstâncias excepcionais (carga alta) ela pode expirar. Isso é algo fora de seu controle.
Os dados de entrada dos usuários estão sob seu controle, pois você pode verificar o que eles enviam e fazer com o que quiser. No seu caso, eu validaria a entrada do usuário antes mesmo de tentar salvá-la. E eu costumo concordar que usuários que fornecem dados inválidos devem ser esperados, e seu aplicativo deve ser responsável por validar a entrada e fornecer a mensagem de erro amigável.
Dito isso, eu uso exceções na maioria dos meus criadores de modelos de domínio, onde não há absolutamente nenhuma chance de entrada de dados inválidos. No entanto, esta é a última linha de defesa, e costumo criar meus formulários de entrada com regras de validação avançadas , para que praticamente não haja chance de acionar a exceção do modelo de domínio. Portanto, quando um levantador está esperando uma coisa e recebe outra, é uma situação excepcional, que não deveria ter acontecido em circunstâncias comuns.
EDIT (outra coisa a considerar):
Ao enviar dados fornecidos pelo usuário ao banco de dados, você sabe de antemão o que deve e não deve entrar em suas tabelas. Isso significa que os dados podem ser validados com relação a algum formato esperado. Isso é algo que você pode controlar. O que você não pode controlar é o seu servidor falhar no meio da sua consulta. Para que você saiba que a consulta está correta e os dados são filtrados / validados, tente a consulta e ela ainda falhará, é uma situação excepcional.
Da mesma forma com as solicitações da Web, você não pode saber se a solicitação expirará ou falhará na conexão antes de tentar enviá-la. Portanto, isso também garante uma abordagem try / catch, pois você não pode perguntar ao servidor se ele funcionará alguns milissegundos mais tarde quando você enviar a solicitação.
fonte
FileNotFoundException
quando receber a entrada incorreta (por exemplo, um nome de arquivo inexistente). Essa é a única maneira válida de ameaçar esse erro. O que mais você pode fazer sem resultar no retorno de códigos de erro?Referência
Do programador pragmático:
Eles examinam o exemplo de abertura de um arquivo para leitura e o arquivo não existe - isso deve gerar uma exceção?
Mais tarde, eles discutem por que escolheram essa abordagem:
Em relação à sua situação
Sua pergunta se resume a "Os erros de validação devem gerar exceções?" A resposta é que depende de onde a validação está acontecendo.
Se o método em questão estiver em uma seção do código em que se supõe que os dados de entrada já foram validados, dados de entrada inválidos devem gerar uma exceção; se o código for projetado para que esse método receba a entrada exata digitada por um usuário, dados inválidos serão esperados e uma exceção não deverá ser gerada.
fonte
Há muita pontificação filosófica aqui, mas de um modo geral, condições excepcionais são simplesmente aquelas que você não pode ou não deseja lidar (exceto limpeza, relatório de erros e similares) sem intervenção do usuário. Em outras palavras, são condições irrecuperáveis.
Se você entregar um programa a um caminho de arquivo, com a intenção de processar esse arquivo de alguma maneira, e o arquivo especificado por esse caminho não existir, essa é uma condição excepcional. Você não pode fazer nada sobre isso no seu código, além de denunciá-lo ao usuário e permitir que ele especifique um caminho de arquivo diferente.
fonte
Há duas preocupações que você deve considerar:
você discute uma única preocupação - vamos chamá-la,
Assigner
já que essa preocupação é atribuir entradas a objetos estruturados - e você expressa a restrição de que suas entradas sejam válidasuma interface de usuário bem implementada tem uma preocupação adicional: validação da entrada do usuário e feedback construtivo sobre erros (vamos chamar de parte
Validator
)Do ponto de vista do
Assigner
componente, lançar uma exceção é totalmente razoável, pois você expressou uma restrição que foi violada.Do ponto de vista da experiência do usuário , o usuário não deve estar falando diretamente com isso
Assigner
em primeiro lugar. Eles deveriam estar conversando com ele através doValidator
.Agora, na
Validator
entrada inválida do usuário não é um caso excepcional, é realmente o caso em que você está mais interessado. Portanto, aqui uma exceção não seria apropriada, e é também nesse ponto que você deseja identificar todos erros em vez de resgatando o primeiro.Você notará que não mencionei como essas preocupações são implementadas. Parece que você está falando sobre o
Assigner
e seu colega está falando sobre um combinadoValidator+Assigner
. Depois de perceber que existem duas preocupações separadas (ou separáveis), pelo menos você pode discuti-lo com sensatez.Para abordar o comentário de Renan, estou apenas assumindo que, depois de identificar suas duas preocupações separadas, é óbvio que casos devem ser considerados excepcionais em cada contexto.
De fato, se não for óbvio se algo deve ser considerado excepcional, eu diria que você provavelmente não terminou de identificar as preocupações independentes em sua solução.
Eu acho que isso faz a resposta direta para
continue simplificando até que seja óbvio . Quando você tem uma pilha de conceitos simples que entende bem, pode argumentar claramente sobre como redigitá-los em código, classes, bibliotecas ou qualquer outra coisa.
fonte
Outros responderam bem, mas ainda aqui está a minha resposta curta. Exceção é uma situação em que algo no ambiente está errado, que você não pode controlar e seu código não pode seguir adiante. Nesse caso, você também precisará informar ao usuário o que deu errado, por que você não pode ir além e qual é a resolução.
fonte
Nunca fui um grande fã do conselho de que você só deve lançar exceções em casos excepcionais, em parte porque não diz nada (é como dizer que você deve comer apenas alimentos comestíveis), mas também porque é muito subjetivo e muitas vezes não está claro o que constitui um caso excepcional e o que não.
No entanto, existem boas razões para este conselho: lançar e capturar exceções é lento e, se você estiver executando seu código no depurador no Visual Studio, com ele definido para notificá-lo sempre que uma exceção for lançada, você poderá acabar sendo spam por dezenas se não centenas de mensagens muito antes de você chegar ao problema.
Então, como regra geral, se:
seu código nunca deve gerar uma exceção, mesmo que seja capturada mais tarde. Para interceptar dados inválidos, você pode usar validadores no nível ou código da interface do usuário, como
Int32.TryParse()
na camada de apresentação.Para qualquer outra coisa, você deve seguir o princípio de que uma exceção significa que seu método não pode fazer o que o nome diz. Em geral, não é uma boa ideia usar códigos de retorno para indicar falha (a menos que o nome do método indique claramente que isso ocorre, por exemplo
TryParse()
) por dois motivos. Primeiro, a resposta padrão para um código de erro é ignorar a condição de erro e continuar independentemente; segundo, você pode facilmente acabar com alguns métodos usando códigos de retorno e outros métodos usando exceções e esquecendo qual é qual. Eu já vi bases de código em que duas implementações intercambiáveis diferentes da mesma interface adotam abordagens diferentes aqui.fonte
As exceções devem representar condições nas quais é provável que o código de chamada imediata não esteja preparado para lidar, mesmo que o método de chamada possa. Considere, por exemplo, o código que está lendo alguns dados de um arquivo, pode legitimamente supor que qualquer arquivo válido termine com um registro válido e não seja necessário extrair qualquer informação de um registro parcial.
Se a rotina de leitura de dados não usasse exceções, mas simplesmente informasse se a leitura foi ou não bem-sucedida, o código de chamada teria que se parecer com:
etc. gastando três linhas de código para cada trabalho útil. Por outro lado, se
readInteger
lançará uma exceção ao encontrar o final de um arquivo e se o chamador puder simplesmente transmitir a exceção, o código se tornará:Muito mais simples e com aparência mais limpa, com muito mais ênfase no caso em que as coisas funcionam normalmente. Note-se que nos casos em que o chamador imediato iria estar esperando para lidar com uma condição, um método que retorna um código de erro, muitas vezes, ser mais útil do que aquele que lança uma exceção. Por exemplo, para totalizar todos os números inteiros em um arquivo:
versus
O código que pede os números inteiros espera que uma dessas chamadas falhe. Ter o código usando um loop sem fim, que será executado até que isso aconteça, é muito menos elegante do que usar um método que indique falhas por meio de seu valor de retorno.
Como as classes geralmente não sabem quais condições seus clientes esperam ou não, geralmente é útil oferecer duas versões de métodos que podem falhar da maneira que alguns chamam e outros não. Isso permitirá que esses métodos sejam usados de maneira limpa com os dois tipos de chamadas. Observe também que mesmo os métodos "try" devem gerar exceções se surgirem situações que o chamador provavelmente não está esperando. Por exemplo,
tryReadInteger
não deve ser lançada uma exceção se encontrar uma condição de fim de arquivo limpa (se o chamador não estivesse esperando isso, o chamador teria usadoreadInteger
) Por outro lado, provavelmente deveria lançar uma exceção se os dados não pudessem ser lidos porque, por exemplo, o cartão de memória que os continha estava desconectado. Embora esses eventos sempre devam ser reconhecidos como uma possibilidade, é improvável que o código de chamada imediata esteja preparado para fazer algo útil em resposta; certamente não deve ser relatado da mesma maneira que seria uma condição de fim de arquivo.fonte
A coisa mais importante na escrita de um software é torná-lo legível. Todas as outras considerações são secundárias, incluindo a eficiência e a correção. Se for legível, o resto pode ser resolvido na manutenção e, se não for legível, é melhor jogar fora. Portanto, você deve lançar exceções quando melhorar a legibilidade.
Quando você estiver escrevendo algum algoritmo, pense na pessoa que irá lê-lo no futuro. Quando você chegar a um lugar onde possa haver um problema em potencial, pergunte a si mesmo se o leitor deseja ver como você lida com esse problema agora ou se prefere continuar com o algoritmo?
Eu gosto de pensar em uma receita para bolo de chocolate. Quando ele diz para você adicionar os ovos, ele tem uma opção: pode assumir que você tem ovos e seguir com a receita, ou pode começar uma explicação de como você pode obter ovos se não tiver ovos. Poderia encher um livro inteiro com técnicas para caçar galinhas selvagens, tudo para ajudá-lo a assar um bolo. Isso é bom, mas a maioria das pessoas não vai querer ler essa receita. A maioria das pessoas prefere apenas assumir que os ovos estão disponíveis e seguir com a receita. Essa é uma decisão que os autores precisam fazer ao escrever receitas.
Não pode haver regras garantidas sobre o que faz uma boa exceção e quais problemas devem ser tratados imediatamente, porque exige que você leia a mente do seu leitor. O melhor que você fará é regras de ouro e "exceções são apenas para circunstâncias excepcionais" são boas. Geralmente, quando um leitor está lendo seu método, ele está procurando o que o método fará 99% das vezes, e prefere não ter isso repleto de casos bizarros de canto, como lidar com usuários inserindo informações ilegais e outras coisas que quase nunca acontecem. Eles querem ver diretamente o fluxo normal do seu software, uma instrução após a outra, como se os problemas nunca acontecessem.
fonte
É por isso que você não pode lançar uma exceção aqui. Uma exceção interrompe imediatamente o processo de validação. Portanto, haveria muita solução para isso.
Um mau exemplo:
Método de validação para
Dog
classe usando exceções:Como chamá-lo:
O problema aqui é que o processo de validação, para obter todos os erros, exigiria ignorar as exceções já encontradas. O exemplo acima pode funcionar, mas esse é um uso indevido claro de exceções . O tipo de validação solicitada deve ocorrer antes do toque no banco de dados. Portanto, não há necessidade de reverter nada. E, os resultados da validação provavelmente serão erros de validação (espero que zero, no entanto).
A melhor abordagem é:
Chamada de método:
Método de validação:
Por quê? Existem inúmeras razões, e a maioria das razões foi apontada nas outras respostas. Para simplificar: é muito mais simples ler e entender por outras pessoas. Segundo, você deseja mostrar os rastreamentos da pilha do usuário para explicar que ele configurou o seu
dog
incorretamente?Se , durante o commit no segundo exemplo, ainda ocorrer um erro , mesmo que seu validador tenha validado o
dog
problema com zero, lançar uma exceção é a coisa certa . Como: sem conexão com o banco de dados, a entrada do banco de dados foi modificada por outra pessoa enquanto isso.fonte