Quando lançar uma exceção?


435

Tenho exceções criadas para todas as condições que meu aplicativo não espera. UserNameNotValidException, PasswordNotCorrectExceptionEtc.

No entanto, disseram-me que não deveria criar exceções para essas condições. Na minha UML, essas são exceções ao fluxo principal. Por que não deveria ser uma exceção?

Alguma orientação ou práticas recomendadas para criar exceções?


58
Reabra, esta é uma pergunta muito sensata e válida. Qualquer pergunta envolve uma certa quantidade de opinião, mas, neste caso, suspeito que seja uma questão de "melhores práticas".
Tim Long

19
+1 para reabrir. Como muitos outros tópicos interessantes 'depende', é muito útil analisar as vantagens e desvantagens ao tomar decisões. O fato de as pessoas confundirem opiniões com fatos nas respostas não nega isso. Peneirar a lama é um exercício que deve ser deixado para o leitor.
Aron

12
Também concordo que essa questão deve ser reaberta, pois está relacionada às melhores práticas. A propósito, as melhores práticas são sempre opiniões que podem ajudar os outros.
quer

6
A Microsoft diz: "Não retorne códigos de erro. As exceções são o principal meio de relatar erros nas estruturas". e "... Se um membro não puder fazer com êxito o que foi projetado, isso deve ser considerado uma falha de execução e uma exceção deve ser lançada". msdn.microsoft.com/library/ms229030%28v=vs.100%29.aspx
Matsen75

4
Isso pode ser uma exceção totalmente sensata, depende apenas de quais métodos os lança. Um método chamado IsCredentialsValid(username,password)não deve lançar uma exceção se o nome de usuário ou senha for inválido, mas retornar falso. Mas digamos que um método que leia dados do banco de dados possa legitimamente lançar essa exceção, se a autenticação falhar. Resumindo: você deve lançar uma exceção se um método não puder executar a tarefa que deveria.
JacquesB

Respostas:


629

Minha orientação pessoal é: uma exceção é lançada quando uma suposição fundamental do bloco de código atual é considerada falsa.

Exemplo 1: digamos que eu tenho uma função que deveria examinar uma classe arbitrária e retornar true se essa classe herdar da List <>. Esta função faz a pergunta: "Este objeto é um descendente da lista?" Essa função nunca deve gerar uma exceção, porque não há áreas cinzentas em sua operação - cada classe herda ou não herda da Lista <>, portanto a resposta é sempre "sim" ou "não".

Exemplo 2: digamos que eu tenho outra função que examina uma Lista <> e retorna verdadeiro se seu comprimento for maior que 50 e falso se o comprimento for menor. Esta função faz a pergunta: "Esta lista possui mais de 50 itens?" Mas esta questão faz uma suposição - assume que o objeto que é dado é uma lista. Se eu entregar um NULL, essa suposição é falsa. Nesse caso, se a função retorna seja verdadeira ou falsa, então é quebrar suas próprias regras. A função não pode retornar nada e afirmar que respondeu à pergunta corretamente. Portanto, ele não retorna - lança uma exceção.

Isso é comparável à falácia lógica da "questão carregada" . Toda função faz uma pergunta. Se a entrada fornecida fizer dessa pergunta uma falácia, lance uma exceção. Essa linha é mais difícil de desenhar com funções que retornam nulas, mas a linha inferior é: se as suposições da função sobre suas entradas forem violadas, ela deverá gerar uma exceção em vez de retornar normalmente.

O outro lado dessa equação é: se você encontrar suas funções gerando exceções com frequência, provavelmente precisará refinar suas suposições.


15
Exatamente! Uma exceção é lançada quando e somente quando as pré-condições da função (suposições sobre argumentos) são quebradas !
Lightman

11
Em linguística, isso às vezes é chamado de falha pressuposicional . O exemplo clássico é devido a Bertrand Russell: "O rei da França é careca" não pode ser respondido sim ou não (resp. "O rei da França é careca" não é verdadeiro nem falso), porque contém um pressuposto falso , a saber, que existe um rei da França. A falha do pressuposto é frequentemente vista com descrições definidas, e isso é comum na programação. Por exemplo, "O cabeçalho de uma lista" apresenta uma falha de pressuposto quando uma lista está vazia e é apropriado lançar uma exceção.
Mohan

Esta é possivelmente a melhor explicação!
gaurav

Obrigado. Assim. Muito de. "O rei da França é careca". Já ouvi isso antes quando pesquisava na selva de meinong .... :) Obrigado. @Mohan
ErlichBachman 14/02

285

Porque são coisas que acontecerão normalmente. Exceções não são mecanismos de controle de fluxo. Os usuários costumam errar as senhas, não é um caso excepcional. Exceções devem ser uma coisa verdadeiramente rara, UserHasDiedAtKeyboarddigitar situações.


3
Hmm, não. Exceções podem ser usadas como mecanismos de controle de fluxo se o desempenho máximo não for necessário, o que é verdade para a maioria dos aplicativos da web. O Python usa a exceção 'StopIteration' para finalizar os iteradores e funciona muito bem. O custo não é nada comparado ao IO etc.
Seun Osewa 11/03/10

9
+1 excelente resposta. Estou tão frustrado com os desenvolvedores trabalhando nas APIs que preciso consumir e lançar exceções para cada pequena coisa. MUITO poucos casos realmente exigem exceções. Se você tiver 25 tipos diferentes de exceções definidos, dê uma olhada no seu design novamente, você pode estar fazendo errado.
7wp

1
deve haver uma exceção quando o usuário tenta uma ação ilegal que ele não tem permissão para manipular o código da página da web, por exemplo, para excluir as postagens de outras pessoas aqui no StackOverflow?
Rajat Gupta

30
Exceções SÃO mecanismos de controle de fluxo. Você pode jogá-los. Você pode pegá-los. Você moveu o controle para outro pedaço de código. Isso é controle de fluxo. A única razão pela qual os idiomas têm exceções é que você pode escrever um código direto sem perguntar "isso simplesmente falhou?" depois de tudo que você faz. Haskell, por exemplo, não possui exceções porque as mônadas e a doação podem automatizar a verificação de erros para você.
Jesse

2
Exceções são mais do que mecanismos de controle de fluxo. Eles fornecem ao cliente (método) informações úteis sobre resultados excepcionais dos quais devem estar cientes e manipulados. Isto é, usado corretamente, exceções fazer APIs mais robusto
idelvall

67

Minhas pequenas diretrizes são fortemente influenciadas pelo grande livro "Code complete":

  • Use exceções para notificar sobre coisas que não devem ser ignoradas.
  • Não use exceções se o erro puder ser tratado localmente
  • Verifique se as exceções estão no mesmo nível de abstração que o restante da sua rotina.
  • Exceções devem ser reservadas para o que é verdadeiramente excepcional .

35

NÃO é uma exceção se o nome de usuário não for válido ou a senha não estiver correta. Essas são as coisas que você deve esperar no fluxo normal de operação. Exceções são coisas que não fazem parte da operação normal do programa e são bastante raras.

Edição: Eu não gosto de usar exceções, porque você não pode dizer se um método lança uma exceção apenas olhando para a chamada. É por isso que as exceções só devem ser usadas se você não puder lidar com a situação de maneira decente (pense em "falta de memória" ou "o computador está pegando fogo").


"Não gosto de usar exceções porque você não pode dizer se um método lança uma exceção apenas olhando a chamada." É por isso que há exceções verificadas nos idiomas que os suportam.
Newtopian

1
As exceções verificadas têm seu próprio conjunto de problemas. Eu ainda prefiro usar exceções de "circunstâncias excepcionais", não para coisas que fazem parte do fluxo de trabalho normal.
EricSchaefer

2
Em resposta à sua edição. Eu sempre coloco nos meus documentos xml no final da seção de resumo as exceções que a função lança para que eu possa ver essas informações no intellisense.
Matthew Vines

1
deve haver uma exceção quando o usuário tenta uma ação ilegal que ele não tem permissão para manipular o código da página da web, por exemplo, para excluir as postagens de outras pessoas aqui no StackOverflow?
Rajat Gupta

1
Parece que você está falando sobre como lidar com erros (memória, computador em chamas) x como lidar com exceções de código (registro ausente, tipo de entrada inválido etc.). Eu acho que há uma diferença distinta entre os dois.
Chuck Burgess

28

Uma regra prática é usar exceções no caso de algo que você normalmente não poderia prever. Exemplos são conectividade de banco de dados, arquivo ausente no disco etc. Para cenários que você pode prever, ou seja, usuários que tentam fazer login com uma senha incorreta, você deve usar funções que retornam booleanos e sabem como lidar com a situação normalmente. Você não deseja encerrar abruptamente a execução lançando uma exceção apenas porque alguém digitou incorretamente sua senha.


6
você não precisa interromper a execução do programa em uma exceção ... lance a exceção, o chamador captura a exceção e deve lidar com ela, se possível, registrar e errar e seguir em frente. É 'má forma' apenas continuar jogando exceções na pilha de chamada - pegá-lo onde quer que ocorra, e lidar com ele lá
Krakkos

2
mas por que jogá-los se você pode lidar com isso diretamente? se a senha está errada ou algo está errado Eu apenas deixei um se o retorno um falso e dar um erro
my1

" arquivo ausente no disco " A maioria das estruturas de linguagem, como a estrutura .NET, fornece APIs para verificar também a existência de arquivos. Por que não usá-los antes de acessar o arquivo diretamente!
user1451111

23

Outros propõem que as exceções não devem ser usadas porque o login incorreto é esperado em um fluxo normal se o usuário digitar errado. Eu discordo e não entendo o raciocínio. Compare-o com a abertura de um arquivo. Se o arquivo não existir ou não estiver disponível por algum motivo, uma exceção será lançada pela estrutura. Usar a lógica acima disso foi um erro da Microsoft. Eles deveriam ter retornado um código de erro. O mesmo para análise, solicitações da web etc. etc.

Não considero uma parte de login incorreta de um fluxo normal, é excepcional. Normalmente, o usuário digita a senha correta e o arquivo existe. Os casos excepcionais são excepcionais e é perfeitamente bom usar exceções para eles. A complicação do código propagando valores de retorno através de n níveis acima da pilha é um desperdício de energia e resultará em um código confuso. Faça a coisa mais simples que possa funcionar. Não otimize prematuramente usando códigos de erro, coisas excepcionais por definição raramente acontecem e exceções não custam nada a menos que você as jogue.


Exceto você pode verificar se o arquivo existe antes de abrir a chamada (dependendo da sua estrutura, é claro). Portanto, esse recurso existe e, portanto, se o arquivo desaparecer entre a verificação e você tentar abri-lo, isso será uma exceção.
blowdart 16/09/08

7
O arquivo existente não significa que o usuário tem permissão para gravar no arquivo, por exemplo. Verificar todos os problemas possíveis é realmente tedioso e propenso a erros. + você está duplicando o código (DRY).
Bjorn Reppen

Um ponto da exceção de senha inválida é que qualquer lentidão em comparação com a solução de código de retorno não será perceptível para o usuário humano digitando uma senha.
paperhorse 16/09/08

7
"Complicar seu código propagando valores de retorno através de n níveis acima da pilha é um desperdício de energia e resultará em código confuso". Essa, para mim, é uma razão muito forte para usar exceções. Um bom código geralmente é composto de pequenas funções. Você não deseja passar esse código de erro repetidamente de uma pequena função para outra.
precisa saber é o seguinte

Penso que a confusão resulta de uma suposição, talvez, de que um resultado previsível de um loginmétodo -type seja que uma senha possa estar incorreta, que possa ser de fato usada para determinar isso e que não deseje ter uma exceção nesse caso; enquanto que em um file opencenário de tipo, com um resultado específico desejado - se o sistema não conseguir entregar o resultado devido a parâmetros de entrada incorretos ou a algum fator externo, esse é um uso perfeitamente lógico de uma exceção.
theMayer 11/01

17

As exceções são um efeito um tanto oneroso, se, por exemplo, você tiver um usuário que fornece uma senha inválida, normalmente é uma idéia melhor repassar um sinalizador de falha ou algum outro indicador de que é inválido.

Isso ocorre devido à maneira como as exceções são manipuladas, a entrada incorreta verdadeira e os itens de parada críticos exclusivos devem ser exceções, mas não as informações de login com falha.


14

Eu acho que você só deve lançar uma exceção quando não há nada que você possa fazer para sair do seu estado atual. Por exemplo, se você estiver alocando memória e não houver nenhum para alocar. Nos casos mencionados, você pode recuperar-se claramente desses estados e retornar um código de erro ao seu chamador de acordo.


Você verá muitos conselhos, inclusive nas respostas a esta pergunta, de que você deve lançar exceções somente em circunstâncias "excepcionais". Isso parece superficialmente razoável, mas é um conselho incorreto, porque substitui uma pergunta ("quando devo lançar uma exceção") por outra questão subjetiva ("o que é excepcional"). Em vez disso, siga os conselhos de Herb Sutter (para C ++, disponível no artigo Dr Dobbs, When and How to Use Exceptions , e também em seu livro com Andrei Alexandrescu, C ++ Coding Standards ): lance uma exceção se, e somente se

  • uma pré-condição não for atendida (o que normalmente torna impossível um dos seguintes) ou
  • a alternativa não atenderia a uma pós-condição ou
  • a alternativa falharia em manter um invariável.

Por que isso é melhor? Não substitui a pergunta por várias perguntas sobre pré-condições, pós-condições e invariantes? Isso é melhor por vários motivos conectados.

  • Pré-condições, pós-condições e invariantes são características de design do nosso programa (sua API interna), enquanto a decisão throwé um detalhe da implementação. Isso nos força a ter em mente que devemos considerar o design e sua implementação separadamente, e nosso trabalho ao implementar um método é produzir algo que satisfaça as restrições do design.
  • Isso nos obriga a pensar em termos de pré-condições, pós-condições e invariantes, que são as únicas suposições que os chamadores de nosso método devem fazer, e são expressos com precisão, permitindo um acoplamento fraco entre os componentes de nosso programa.
  • Esse acoplamento solto nos permite refatorar a implementação, se necessário.
  • As pós-condições e os invariantes são testáveis; resulta em código que pode ser facilmente testado em unidade, porque as condições posteriores são predicados que nosso código de teste de unidade pode verificar (afirmar).
  • Pensar em termos de pós-condições produz naturalmente um design que tem sucesso como uma pós-condição , que é o estilo natural para o uso de exceções. O caminho de execução normal ("feliz") do seu programa é apresentado linearmente, com todo o código de tratamento de erros movido para as catchcláusulas.

10

Eu diria que não há regras rígidas e rápidas sobre quando usar exceções. No entanto, existem boas razões para usá-los ou não:

Razões para usar exceções:

  • O fluxo de código para o caso comum é mais claro
  • Pode retornar informações de erro complexas como um objeto (embora isso também possa ser alcançado usando o parâmetro "out" de erro passado por referência)
  • Os idiomas geralmente fornecem algumas facilidades para gerenciar a limpeza organizada no caso da exceção (try / finalmente em Java, usando em C #, RAII em C ++)
  • No caso de nenhuma exceção ser lançada, a execução às vezes pode ser mais rápida do que a verificação dos códigos de retorno
  • Em Java, as exceções verificadas devem ser declaradas ou capturadas (embora isso possa ser um motivo)

Razões para não usar exceções:

  • Às vezes é um exagero se a manipulação de erros é simples
  • Se as exceções não forem documentadas ou declaradas, elas podem ser detectadas pelo código de chamada, o que pode ser pior do que se o código de chamada apenas ignorasse um código de retorno (saída do aplicativo x falha silenciosa - o que é pior pode depender do cenário)
  • No C ++, o código que usa exceções deve ser seguro (mesmo que você não as atire ou ative, mas chame uma função de lançamento indiretamente)
  • No C ++, é difícil saber quando uma função pode ser lançada; portanto, você deve estar paranóico com relação à segurança de exceções, se usá-las
  • Lançar e capturar exceções geralmente é significativamente mais caro comparado à verificação de um sinalizador de retorno

Em geral, eu estaria mais inclinado a usar exceções em Java do que em C ++ ou C #, porque sou da opinião de que uma exceção, declarada ou não, é fundamentalmente parte da interface formal de uma função, pois alterar sua garantia de exceção pode quebrar o código de chamada. A maior vantagem de usá-los no Java IMO é que você sabe que o chamador DEVE lidar com a exceção, e isso aumenta a chance de comportamento correto.

Por isso, em qualquer idioma, eu sempre derivaria todas as exceções em uma camada de código ou API de uma classe comum, para que o código de chamada sempre garanta a captura de todas as exceções. Também consideraria ruim lançar classes de exceção específicas da implementação ao escrever uma API ou biblioteca (ou seja, agrupar exceções de camadas inferiores para que a exceção recebida pelo chamador seja compreensível no contexto da sua interface).

Observe que Java faz a distinção entre exceções gerais e de Tempo de execução, pois as últimas não precisam ser declaradas. Eu usaria apenas classes de exceção de tempo de execução quando você souber que o erro é resultado de um bug no programa.


5

As classes de exceção são como classes "normais". Você cria uma nova classe quando "é" um tipo diferente de objeto, com diferentes campos e operações diferentes.

Como regra geral, você deve tentar equilibrar o número de exceções e a granularidade das exceções. Se o seu método gerar mais de 4-5 exceções diferentes, provavelmente você poderá mesclar algumas delas em exceções mais "gerais" (por exemplo, no seu caso "AuthenticationFailedException") e usar a mensagem de exceção para detalhar o que deu errado. A menos que seu código lide com cada um deles de maneira diferente, você não precisará criar muitas classes de exceção. E se isso acontecer, você deve apenas retornar uma enumeração com o erro que ocorreu. É um pouco mais limpo assim.


5

Se o código estiver sendo executado dentro de um loop e provavelmente causará uma exceção repetidas vezes, lançar exceções não é uma coisa boa, porque elas são muito lentas para N. grande. Mas não há nada errado em lançar exceções personalizadas se o desempenho não for um problema. Apenas verifique se você tem uma exceção básica que todos eles herdam, chamada BaseException ou algo parecido. BaseException herda System.Exception, mas todas as suas exceções herdam BaseException. Você pode até ter uma árvore de tipos de exceção para agrupar tipos semelhantes, mas isso pode ou não ser um exagero.

Portanto, a resposta curta é que, se não causar uma penalidade significativa no desempenho (o que não aconteceria, a menos que você esteja lançando muitas exceções), vá em frente.


Gostei muito do seu comentário sobre as exceções dentro de um loop e pensei em experimentá-lo. Eu escrevi um programa de exemplo executando um loop int.MaxValuetimes e gerando uma exceção 'dividir por zero'. A versão IF / ELSE, na qual eu estava verificando se o dividendo não era zero antes da operação de divisão , foi concluída em 6082 ms e 15407722, enquanto a versão TRY / CATCH, na qual eu estava gerando a exceção e capturando a exceção , foi concluída em 28174385 ms e ​​71371326155 ticks: 4632 vezes mais que a versão if / else.
user1451111

3

Eu concordo com o japollock lá em cima - lance uma aceitação quando você não tiver certeza do resultado de uma operação. Chamadas para APIs, acessando sistemas de arquivos, chamadas de banco de dados, etc. Sempre que você ultrapassar os "limites" de suas linguagens de programação.

Gostaria de acrescentar, sinta-se à vontade para lançar uma exceção padrão. A menos que você faça algo "diferente" (ignore, envie e-mail, faça logon, mostre a imagem da baleia do twitter, etc.), então não se preocupe com exceções personalizadas.


3

a regra de ouro para lançar exceções é bastante simples. você o faz quando o seu código entra em um estado INVÁLIDO INCORREVÍVEL. se os dados estiverem comprometidos ou você não puder retroceder o processamento que ocorreu até o ponto, será necessário encerrá-lo. de fato, o que mais você pode fazer? sua lógica de processamento acabará falhando em outro lugar. se você puder se recuperar de alguma forma, faça isso e não gere uma exceção.

no seu caso particular, se você foi forçado a fazer algo tolo como aceitar retirada de dinheiro e só então verificar usuário / senha você deve encerrar o processo lançando uma exceção para notificar que algo de ruim aconteceu e evitar danos maiores.


2

Em geral, você deseja lançar uma exceção para qualquer coisa que possa acontecer no seu aplicativo "Excepcional"

No seu exemplo, essas duas exceções parecem que você as está chamando por meio de uma validação de senha / nome de usuário. Nesse caso, pode-se argumentar que não é realmente excepcional que alguém digite um nome de usuário / senha incorretamente.

Eles são "exceções" ao fluxo principal da sua UML, mas são mais "ramificações" no processamento.

Se você tentou acessar seu arquivo ou banco de dados de senha e não conseguiu, esse seria um caso excepcional e justificaria lançar uma exceção.


" Se você tentou acessar seu arquivo ou banco de dados de senhas e não conseguiu, seria um caso excepcional e justificaria lançar uma exceção. " A maioria das estruturas de linguagem, como a estrutura .NET, fornece APIs para verificar também a existência de arquivos. Por que não usá-los antes de acessar o arquivo diretamente!
user1451111

2

Primeiro, se os usuários da sua API não estiverem interessados ​​em falhas específicas e refinadas, ter exceções específicas para eles não terá nenhum valor.

Como muitas vezes não é possível saber o que pode ser útil para seus usuários, uma abordagem melhor é ter exceções específicas, mas garantir que elas sejam herdadas de uma classe comum (por exemplo, std :: exception ou seus derivados em C ++). Isso permite que seu cliente capture exceções específicas, se assim o desejar, ou a exceção mais geral, se não se importar.


2

Exceções destinam-se a eventos que são comportamentos anormais, erros, falhas e outros. Comportamento funcional, erro do usuário etc. devem ser tratados pela lógica do programa. Como uma conta ou senha incorreta é uma parte esperada do fluxo lógico em uma rotina de logon, ele deve poder lidar com essas situações sem exceções.


2

Eu tenho problemas filosóficos com o uso de exceções. Basicamente, você espera que um cenário específico ocorra, mas, em vez de lidar com isso explicitamente, você está empurrando o problema para ser tratado "em outro lugar". E onde está esse "outro lugar" pode ser uma incógnita.


2

Eu diria que geralmente todo fundamentalismo leva ao inferno.

Você certamente não gostaria de acabar com um fluxo orientado a exceções, mas evitar completamente as exceções também é uma má idéia. Você precisa encontrar um equilíbrio entre as duas abordagens. O que eu não faria é criar um tipo de exceção para todas as situações excepcionais. Isso não é produtivo.

O que eu geralmente prefiro é criar dois tipos básicos de exceções que são usadas em todo o sistema: LogicalException e TechnicalException . Estes podem ser ainda mais distinguidos por subtipos, se necessário, mas geralmente não é necessário.

A exceção técnica denota uma exceção realmente inesperada, como o servidor de banco de dados estar inoperante, a conexão com o serviço da Web lançou a IOException e assim por diante.

Por outro lado, as exceções lógicas são usadas para propagar a situação incorreta menos grave para as camadas superiores (geralmente algum resultado de validação).

Observe que mesmo a exceção lógica não se destina a ser usada regularmente para controlar o fluxo do programa, mas para destacar a situação em que o fluxo realmente deve terminar. Quando usado em Java, os dois tipos de exceção são subclasses RuntimeException e o tratamento de erros é altamente orientado a aspectos.

Portanto, no exemplo de login, pode ser sensato criar algo como AuthenticationException e distinguir as situações concretas por valores de enumeração como UsernameNotExisting , PasswordMismatch etc. . Você também pode empregar com facilidade algum mecanismo genérico de tratamento de exceções, pois você tem as exceções categorizadas e sabe muito bem o que propagar para o usuário e como.

Nosso uso típico é lançar a LogicalException durante a chamada de serviço da Web quando a entrada do usuário era inválida. A Exceção é organizada para o detalhe SOAPFault e, em seguida, é organizada para a exceção novamente no cliente, o que resulta na exibição do erro de validação em um determinado campo de entrada da página da Web, uma vez que a exceção possui mapeamento adequado para esse campo.

Essa certamente não é a única situação: você não precisa acessar o serviço da web para lançar a exceção. Você é livre para fazê-lo em qualquer situação excepcional (como no caso de você precisar falhar rapidamente) - tudo fica a seu critério.


2

para mim A exceção deve ser lançada quando uma regra técnica ou comercial exigida falhar. por exemplo, se uma entidade automóvel estiver associada a uma matriz de 4 pneus ... se um ou mais pneus forem nulos ... uma exceção deve ser acionada "NotEnoughTiresException", porque pode ser capturada em diferentes níveis do sistema e ter uma significante significado através do registro. além disso, se apenas tentarmos controlar o fluxo, nulo e impedir a instanciação do carro. talvez nunca encontremos a origem do problema, porque o pneu não deve ser nulo em primeiro lugar.


1

a principal razão para evitar lançar uma exceção é que há muita sobrecarga envolvida em lançar uma exceção.

Uma coisa que o artigo abaixo afirma é que uma exceção é para condições e erros excepcionais.

Um nome de usuário errado não é necessariamente um erro de programa, mas um erro de usuário ...

Aqui está um ponto de partida decente para exceções no .NET: http://msdn.microsoft.com/en-us/library/ms229030(VS.80).aspx


1

O lançamento de exceções faz com que a pilha se descontraia, o que tem alguns impactos no desempenho (admitidos, os ambientes gerenciados modernos melhoraram nisso). Ainda repetidamente lançar e capturar exceções em uma situação aninhada seria uma má idéia.

Provavelmente mais importante que isso, as exceções são destinadas a condições excepcionais. Eles não devem ser usados ​​para o fluxo de controle comum, porque isso prejudicará a legibilidade do seu código.


1

Eu tenho três tipos de condições que eu pego.

  1. Entrada incorreta ou ausente não deve ser uma exceção. Use js do lado do cliente e regex do lado do servidor para detectar, definir atributos e encaminhar para a mesma página com mensagens.

  2. A AppException. Isso geralmente é uma exceção que você detecta e lança no seu código. Em outras palavras, essas são as esperadas (o arquivo não existe). Registre-o, defina a mensagem e retorne à página de erro geral. Essa página geralmente contém algumas informações sobre o que aconteceu.

  3. A exceção inesperada. Estes são os que você não conhece. Registre-o com detalhes e encaminhe-os para uma página de erro geral.

Espero que isto ajude


1

A segurança está em conflito com o seu exemplo: você não deve informar ao invasor que existe um nome de usuário, mas a senha está errada. Essa é uma informação extra que você não precisa compartilhar. Basta dizer "o nome de usuário ou a senha está incorreta".


1

A resposta simples é: sempre que uma operação for impossível (por causa de um aplicativo OU por violar a lógica de negócios). Se um método for chamado e for impossível fazer o que o método foi escrito, execute uma exceção. Um bom exemplo é que os construtores sempre lançam ArgumentExceptions se uma instância não puder ser criada usando os parâmetros fornecidos. Outro exemplo é InvalidOperationException, lançado quando uma operação não pode ser executada devido ao estado de outro membro ou membros da classe.

No seu caso, se um método como Login (nome de usuário, senha) for chamado, se o nome de usuário não for válido, é realmente correto lançar uma UserNameNotValidException ou PasswordNotCorrectException se a senha estiver incorreta. O usuário não pode fazer login usando o (s) parâmetro (s) fornecido (s) (ou seja, é impossível porque violaria a autenticação), então lance uma exceção. Embora eu possa ter suas duas exceções herdadas de ArgumentException.

Dito isto, se você NÃO deseja lançar uma exceção porque uma falha de logon pode ser muito comum, uma estratégia é criar um método que retorne tipos que representam falhas diferentes. Aqui está um exemplo:

{ // class
    ...

    public LoginResult Login(string user, string password)
    {
        if (IsInvalidUser(user))
        {
            return new UserInvalidLoginResult(user);
        }
        else if (IsInvalidPassword(user, password))
        {
            return new PasswordInvalidLoginResult(user, password);
        }
        else
        {
            return new SuccessfulLoginResult();
        }
    }

    ...
}

public abstract class LoginResult
{
    public readonly string Message;

    protected LoginResult(string message)
    {
        this.Message = message;
    }
}

public class SuccessfulLoginResult : LoginResult
{
    public SucccessfulLogin(string user)
        : base(string.Format("Login for user '{0}' was successful.", user))
    { }
}

public class UserInvalidLoginResult : LoginResult
{
    public UserInvalidLoginResult(string user)
        : base(string.Format("The username '{0}' is invalid.", user))
    { }
}

public class PasswordInvalidLoginResult : LoginResult
{
    public PasswordInvalidLoginResult(string password, string user)
        : base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
    { }
}

A maioria dos desenvolvedores é ensinada a evitar exceções devido à sobrecarga causada por lançá-las. É ótimo ter consciência de recursos, mas geralmente não às custas do design do aplicativo. Essa é provavelmente a razão pela qual você foi instruído a não lançar suas duas exceções. O uso ou não de exceções geralmente se resume à frequência com que a exceção ocorrerá. Se é um resultado bastante comum ou bastante esperado, é nesse momento que a maioria dos desenvolvedores evita Exceções e cria outro método para indicar falha, devido ao suposto consumo de recursos.

Aqui está um exemplo de como evitar exceções em um cenário como o descrito, usando o padrão Try ():

public class ValidatedLogin
{
    public readonly string User;
    public readonly string Password;

    public ValidatedLogin(string user, string password)
    {
        if (IsInvalidUser(user))
        {
            throw new UserInvalidException(user);
        }
        else if (IsInvalidPassword(user, password))
        {
            throw new PasswordInvalidException(password);
        }

        this.User = user;
        this.Password = password;
    }

    public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
    {
        if (IsInvalidUser(user) || 
            IsInvalidPassword(user, password))
        {
            return false;
        }

        validatedLogin = new ValidatedLogin(user, password);

        return true;
    }
}

1

Na minha opinião, a questão fundamental deveria ser se alguém esperaria que o chamador desejasse continuar o fluxo normal do programa se uma condição ocorrer. Se você não souber, tenha os métodos doSomething e trySomething separados, nos quais o primeiro retorna um erro e o último não, ou possui uma rotina que aceita um parâmetro para indicar se uma exceção deve ser lançada se falhar). Considere uma classe para enviar comandos para um sistema remoto e relatar respostas. Certos comandos (por exemplo, reinicialização) farão com que o sistema remoto envie uma resposta, mas não responda por um determinado período de tempo. Portanto, é útil poder enviar um comando "ping" e descobrir se o sistema remoto responde em um período de tempo razoável, sem ter que lançar uma exceção se isso não acontecer. t (o chamador provavelmente esperaria que as primeiras tentativas de "ping" falhassem, mas uma acabaria por funcionar). Por outro lado, se houver uma sequência de comandos como:

  exchange_command ("arquivo temporário aberto");
  exchange_command ("escreva dados do arquivo temporário {qualquer}}");
  exchange_command ("escreva dados do arquivo temporário {qualquer}}");
  exchange_command ("escreva dados do arquivo temporário {qualquer}}");
  exchange_command ("escreva dados do arquivo temporário {qualquer}}");
  exchange_command ("fechar arquivo temporário");
  exchange_command ("copiar arquivo temporário para arquivo real");

alguém iria querer que a falha de qualquer operação abortasse toda a sequência. Embora se possa verificar cada operação para garantir que seja bem-sucedida, é mais útil que a rotina exchange_command () gere uma exceção se um comando falhar.

Na verdade, no cenário acima, pode ser útil ter um parâmetro para selecionar vários modos de tratamento de falhas: nunca gere exceções, gere exceções apenas para erros de comunicação ou gere exceções nos casos em que um comando não retorna um "êxito" "indicação.


1

"PasswordNotCorrectException" não é um bom exemplo para usar exceções. Espera-se que os usuários digam suas senhas erradas, por isso não é uma exceção IMHO. Você provavelmente até se recupera, mostrando uma boa mensagem de erro, portanto é apenas uma verificação de validade.

Exceções não tratadas interromperão a execução eventualmente - o que é bom. Se você estiver retornando códigos falsos, nulos ou de erro, precisará lidar sozinho com o estado do programa. Se você esquecer de verificar as condições em algum lugar, seu programa poderá continuar em execução com dados incorretos e poderá ser difícil descobrir o que aconteceu e onde .

Obviamente, você pode causar o mesmo problema com instruções de captura vazias, mas pelo menos identificá-las é mais fácil e não exige que você entenda a lógica.

Então, como regra geral:

Use-os onde você não quiser ou simplesmente não possa se recuperar de um erro.


0

Você pode usar um pouco de exceções genéricas para essas condições. Por exemplo, ArgumentException deve ser usado quando algo der errado com os parâmetros de um método (com exceção de ArgumentNullException). Geralmente você não precisaria de exceções como LessThanZeroException, NotPrimeNumberException etc. Pense no usuário do seu método. O número de condições que ela desejará tratar especificamente é igual ao número do tipo de exceção que seu método precisa lançar. Dessa forma, você pode determinar como as exceções detalhadas terão.

A propósito, sempre tente fornecer algumas maneiras para os usuários de suas bibliotecas evitarem exceções. O TryParse é um bom exemplo, ele existe para que você não precise usar o int.Parse e capturar uma exceção. No seu caso, você pode fornecer alguns métodos para verificar se o nome de usuário é válido ou se a senha está correta, para que seus usuários (ou você) não precisem fazer muita manipulação de exceções. Esperamos que isso resulte em código mais legível e melhor desempenho.


0

Por fim, a decisão se resume a saber se é mais útil lidar com erros no nível do aplicativo como esse usando tratamento de exceção ou através de seu próprio mecanismo de rolagem em casa, como retornar códigos de status. Eu não acho que exista uma regra rígida sobre o que é melhor, mas eu consideraria:

  • Quem está chamando seu código? Esta é uma API pública de algum tipo ou uma biblioteca interna?
  • Que linguagem você está usando? Se for Java, por exemplo, lançar uma exceção (marcada) coloca um ônus explícito no responsável pela chamada para lidar com essa condição de erro de alguma maneira, em oposição a um status de retorno que pode ser ignorado. Isso pode ser bom ou ruim.
  • Como são tratadas outras condições de erro no mesmo aplicativo? Os chamadores não querem lidar com um módulo que lida com erros de maneira idiossincrática, diferente de qualquer outra coisa no sistema.
  • Quantas coisas podem dar errado com a rotina em questão e como elas seriam tratadas de maneira diferente? Considere a diferença entre uma série de blocos de captura que manipulam erros diferentes e a ativação de um código de erro.
  • Você possui informações estruturadas sobre o erro que precisa retornar? Lançar uma exceção fornece um lugar melhor para colocar essas informações do que apenas retornar um status.

0

Existem duas classes principais de exceção:

1) Exceção do sistema (por exemplo, conexão com o banco de dados perdida) ou 2) Exceção do usuário. (por exemplo, validação de entrada do usuário, 'senha incorreta')

Achei útil criar minha própria classe de exceção de usuário e, quando quero gerar um erro de usuário, quero ser tratado de maneira diferente (ou seja, erro de recurso exibido ao usuário), então tudo que preciso fazer no meu manipulador de erro principal é verificar o tipo de objeto :

            If TypeName(ex) = "UserException" Then
               Display(ex.message)
            Else
               DisplayError("An unexpected error has occured, contact your help  desk")                   
               LogError(ex)
            End If

0

Algumas coisas úteis para se pensar ao decidir se uma exceção é apropriada:

  1. qual o nível de código que você deseja executar após a ocorrência do candidato à exceção - ou seja, quantas camadas da pilha de chamadas devem ser desenroladas. Você geralmente deseja manipular uma exceção o mais próximo possível de onde ela ocorre. Para validação de nome de usuário / senha, você normalmente lidaria com falhas no mesmo bloco de código, em vez de permitir que uma exceção surgisse. Portanto, uma exceção provavelmente não é apropriada. (OTOH, após três tentativas falhas de login, o fluxo de controle pode mudar para outro local e uma exceção pode ser apropriada aqui.)

  2. Esse evento é algo que você gostaria de ver em um log de erros? Nem todas as exceções são gravadas em um log de erros, mas é útil perguntar se essa entrada em um log de erros seria útil - ou seja, você tentaria fazer algo sobre isso ou seria o lixo que você ignora.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.