Foi-me dito que as exceções devem ser usadas apenas em casos excepcionais. Como sei se meu caso é excepcional?

99

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?

Daniel Kaplan
fonte
3
possível duplicado? Quando lançar uma exceção . Embora estivesse fechado lá, mas acho que se encaixa aqui. Ainda é um pouco de filosofia, algumas pessoas e comunidades tendem a ver as exceções como uma espécie de controle de fluxo.
22613 Thorsten Müller
8
Quando os usuários são burros, eles fornecem entrada inválida. Quando os usuários são inteligentes, eles jogam fornecendo entrada inválida. Portanto, entrada de usuário inválida não é uma exceção.
Mouviciel
7
Além disso, não confunda uma exceção , que é um mecanismo muito específico em Java e .NET, com um erro que é um termo muito mais genérico. O tratamento de erros é mais do que gerar exceções. Esta discussão aborda as nuances entre exceções e erros .
Eric King
4
"Excepcional"! = "Raramente acontece"
ConditionRacer
3
Acho as exceções de Vexing de Eric Lippert um conselho decente.
19713 Brian

Respostas:

87

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 Integerclasse de Java não tem como verificar se uma string é um número inteiro válido sem potencialmente gerar a NumberFormatException.

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.

Karl Bielefeldt
fonte
10
Bom golpe, lá. Na verdade, no aplicativo do mundo real que eu projetei, o impacto no desempenho fez diferença e eu tive que alterá-lo para não gerar exceções para determinadas operações de análise.
Robert Harvey
17
Não estou dizendo que ainda não há casos em que o impacto no desempenho é um motivo válido, mas esses casos são a exceção (trocadilho intencional) e não a regra.
Karl Bielefeldt
11
@RobertHarvey O truque em Java é lançar objetos de exceção pré-fabricados, em vez de 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 .
Ingo
3
+1: exatamente o que eu ia responder. Use-o quando simplificar o código. As exceções podem fornecer um código muito mais claro, onde você não precisa se preocupar em verificar os valores de retorno em todos os níveis da pilha de chamadas. (Como tudo mais, porém, se usadas de forma errada pode fazer seu código de uma confusão horrível.)
Leo
4
@Brendan Suponha que ocorra alguma exceção e o código de tratamento de erros esteja 4 níveis abaixo na pilha de chamadas. Se você usar um código de erro, todas as 4 funções acima do manipulador precisarão ter o tipo do código de erro como seu valor de retorno, e você deverá fazer uma cadeia 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.
Doval
72

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:

public void Save(PersonData personData) {  }

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:

public ValidationResult Validate(PersonData personData) {  }

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:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

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.

Theo Lenndorff
fonte
Ontem fiz um structchamado "ValidationResult" e estruturei meu código da maneira que você descreve.
paul
4
Não ajuda a responder à sua pergunta, mas gostaria de salientar que você seguiu implícita ou intencionalmente o princípio de separação de consultas por comando ( en.wikipedia.org/wiki/Command-query_separation ). ;-)
Theo Lenndorff
Boa ideia! Uma desvantagem: no seu exemplo, a validação é realmente realizada duas vezes: uma vez Validate(retornando False se inválido) e uma vez durante Save(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.
Heinzi 21/10
@Heinzi eu concordo. Pode ser refatorado para que Validate()seja chamado dentro do Save()método, e detalhes específicos do ValidationResultpodem ser usados ​​para construir uma mensagem apropriada para a exceção.
Phil
3
Isso é melhor do que a resposta aceita, eu acho. Lance quando a chamada não puder fazer o que deveria.
213 Andy
31

As exceções devem ser excepcionais: espera-se que o usuário insira dados inválidos, portanto, este não é um caso excepcional

Sobre esse argumento:

  • Espera-se que um arquivo não exista, portanto esse não é um caso excepcional.
  • Espera-se que a conexão com o servidor seja perdida, portanto esse não é um caso excepcional
  • Espera-se que o arquivo de configuração seja distorcido, para que não seja um caso excepcional
  • Espera-se que sua solicitação às vezes caia, portanto esse não é um caso excepcional

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.

Winston Ewert
fonte
1
@gnat, eu sei. Meu argumento era que você deveria seguir as convenções da linguagem (neste caso, Java), mesmo que elas não sejam as suas favoritas.
Winston Ewert
6
+1 "exceptions should be exceptional" is a terrible rule of thumb.Bem dito! Essa é uma daquelas coisas que as pessoas repetem sem pensar nelas.
Andrés F.
2
"Esperado" é definido não pelo argumento ou pela convenção subjetiva, mas pelo contrato da API / função (isso pode ser explícito, mas geralmente é apenas implícito). Diferentes funções / APIs / subsistemas podem ter expectativas diferentes, por exemplo, para algumas funcionalidades de nível superior, um arquivo inexistente é um caso esperado para ele manipular (isso pode ser relatado a um usuário por meio de uma GUI); para outras funções de nível inferior, é provavelmente não (e, portanto, deve lançar uma exceção). Esta resposta parece perder aquele ponto importante ....
mikera
1
@mikera, sim, uma função deve (somente) lançar as exceções definidas em seu contrato. Mas essa não é a questão. A questão é como você decide qual deve ser esse contrato. Eu mantenho que a regra de ouro "exceções devem ser excepcionais" não é útil para tomar essa decisão.
Winston Ewert #
1
@ Supercat, eu não acho que realmente importe o que acaba sendo mais comum. Eu acho que a pergunta crítica é ter um padrão são. Se eu não lidar explicitamente com a condição de erro, meu código finge que nada aconteceu ou recebo uma mensagem de erro útil?
Winston Ewert
30

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.

Ivan Pintar
fonte
8
Mas por que? Por que as exceções são menos úteis no tratamento de problemas mais esperados?
Winston Ewert
6
@Pinetree, verificar a existência do arquivo antes de abrir um arquivo é uma má ideia. O arquivo pode deixar de existir entre a verificação e a abertura, o arquivo não pode ter permissão que permita a abertura e a verificação da existência e a abertura do arquivo exigirão duas chamadas de sistema caras. É melhor tentar abrir o arquivo e depois lidar com o fracasso.
Winston Ewert
10
Tanto quanto eu posso ver, praticamente todas as falhas possíveis são melhor tratadas como se recuperar da falha do que tentar verificar o sucesso com antecedência. Se você usa ou não exceções ou outra coisa indica que a falha é um problema separado. Prefiro exceções porque não posso ignorá-las acidentalmente.
Winston Ewert
11
Não concordo com sua premissa de que, como se espera dados inválidos do usuário, eles não podem ser considerados excepcionais. Se eu escrever um analisador e alguém alimentá-lo com dados não analisáveis, isso é uma exceção. Não posso continuar analisando. Como a exceção é tratada é outra questão inteiramente.
CondiçãoRacer
4
File.ReadAllBytes lançará um FileNotFoundExceptionquando 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?
oɔɯǝɹ
16

Referência

Do programador pragmático:

Acreditamos que as exceções raramente devem ser usadas como parte do fluxo normal de um programa; exceções devem ser reservadas para eventos inesperados. Suponha que uma exceção não capturada encerre seu programa e se pergunte: "Esse código ainda será executado se eu remover todos os manipuladores de exceção?" Se a resposta for "não", talvez as exceções estejam sendo usadas em circunstâncias não excepcionais.

Eles examinam o exemplo de abertura de um arquivo para leitura e o arquivo não existe - isso deve gerar uma exceção?

Se o arquivo deveria estar lá, uma exceção é garantida. [...] Por outro lado, se você não tem idéia se o arquivo deve ou não existir, não parece excepcional se você não consegue encontrá-lo, e um retorno de erro é apropriado.

Mais tarde, eles discutem por que escolheram essa abordagem:

A exceção [n] representa uma transferência imediata e não local de controle - é uma espécie de cascata goto. Os programas que usam exceções como parte de seu processamento normal sofrem com todos os problemas de legibilidade e manutenção do código espaguete clássico. Esses programas quebram o encapsulamento: as rotinas e seus chamadores são mais fortemente acoplados através do tratamento de exceções.

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.

Mike Partridge
fonte
11

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.

Robert Harvey
fonte
1
+1, muito perto do que eu ia dizer. Eu diria que é mais sobre escopo e realmente não tem nada a ver com o usuário. Um bom exemplo disso é a diferença entre as duas funções .Net int.Parse e int.TryParse, o primeiro não tem escolha a não ser lançar uma exceção em entradas ruins, o posterior nunca deve lançar uma exceção
jmoreno
1
@jmoreno: Ergo, você usaria o TryParse quando o código pudesse fazer algo sobre a condição imperceptível e o Parse quando não pudesse.
Robert Harvey
7

Há duas preocupações que você deve considerar:

  1. você discute uma única preocupação - vamos chamá-la, Assignerjá que essa preocupação é atribuir entradas a objetos estruturados - e você expressa a restrição de que suas entradas sejam válidas

  2. uma 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 Assignercomponente, 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 Assignerem primeiro lugar. Eles deveriam estar conversando com ele através do Validator.

Agora, na Validatorentrada 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 Assignere seu colega está falando sobre um combinado Validator+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

... como sei se meu caso é excepcional?

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.

Sem utilidade
fonte
-1 Sim, existem duas preocupações, mas isso não responde à pergunta "Como sei se meu caso é excepcional?"
RMalke
A questão é que o mesmo caso pode ser excepcional em um contexto, e não em outro. A identificação de qual contexto você está falando (em vez de confundir os dois) responde à pergunta aqui.
Inútil
... na verdade, talvez isso não aconteça - em vez disso, resolvi seu ponto de vista.
Inútil
4

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.

Manoj R
fonte
3

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 está livre de erros e
  • os serviços dos quais depende estão disponíveis e
  • seu usuário está usando seu programa da maneira que ele deveria ser usado (mesmo que parte da entrada fornecida seja inválida)

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.

jammycakes
fonte
2

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:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

etc. gastando três linhas de código para cada trabalho útil. Por outro lado, se readIntegerlanç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á:

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

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:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

versus

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

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.

supercat
fonte
2

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.

Geo
fonte
2

Pode haver um requisito para relatar todos os erros que podemos encontrar na entrada, não apenas o primeiro.

É 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 Dogclasse usando exceções:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Como chamá-lo:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

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:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Método de validação:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

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 dogincorretamente?

Se , durante o commit no segundo exemplo, ainda ocorrer um erro , mesmo que seu validador tenha validado o dogproblema 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.

Matthias Ronge
fonte