Tenho exceções criadas para todas as condições que meu aplicativo não espera. UserNameNotValidException
, PasswordNotCorrectException
Etc.
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?
exception
language-agnostic
Kwan Cheng
fonte
fonte
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.Respostas:
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.
fonte
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,
UserHasDiedAtKeyboard
digitar situações.fonte
Minhas pequenas diretrizes são fortemente influenciadas pelo grande livro "Code complete":
fonte
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").
fonte
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.
fonte
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.
fonte
login
mé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 umfile open
cená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.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.
fonte
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
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.
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.catch
cláusulas.fonte
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:
Razões para não usar exceções:
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.
fonte
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.
fonte
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.
fonte
int.MaxValue
times 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.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.
fonte
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.
fonte
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.
fonte
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.
fonte
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.
fonte
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.
fonte
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.
fonte
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.
fonte
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
fonte
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.
fonte
Eu tenho três tipos de condições que eu pego.
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.
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.
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
fonte
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".
fonte
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:
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 ():
fonte
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:
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.
fonte
"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.
fonte
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.
fonte
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:
fonte
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 :
fonte
Algumas coisas úteis para se pensar ao decidir se uma exceção é apropriada:
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.)
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.
fonte