As variáveis ​​de erro são antipadrão ou bom design?


44

Para lidar com vários erros possíveis que não devem interromper a execução, tenho uma errorvariável que os clientes podem verificar e usar para lançar exceções. Isso é um anti-padrão? Existe uma maneira melhor de lidar com isso? Para um exemplo disso em ação, você pode ver a API mysqli do PHP . Suponha que os problemas de visibilidade (acessadores, escopo público e privado, seja a variável em uma classe ou global?) Sejam tratados corretamente.


6
Este o try/ catchexiste para. Além disso, você pode colocar sua pilha try/ catchmuito mais longe em um local mais apropriado para manipulá-la (permitindo uma maior separação de preocupações).
precisa

Algo a ter em mente: se você usar o tratamento baseado em exceções e tiver uma exceção, não deseja mostrar muitas informações ao usuário. Use um manipulador de erros como Elmah ou Raygun.io para interceptá-lo e mostrar uma mensagem de erro genérica ao usuário. NUNCA mostre um rastreamento de pilha ou uma mensagem de erro específica ao usuário, porque eles divulgam informações sobre o funcionamento interno do aplicativo, que pode ser abusado.
Nzall

4
@Nate Seu conselho é aplicável apenas a aplicativos críticos de segurança nos quais o usuário não é totalmente confiável. Mensagens de erro vagas são elas próprias um anti-padrão. O mesmo acontece com o envio de relatórios de erros pela rede sem o consentimento expresso do usuário.
Piedar

3
@piedar eu criei uma questão separada onde isso pode ser discutido mais livremente: programmers.stackexchange.com/questions/245255/...
Nzall

26
Um princípio geral no design da API que o leva muito longe é sempre olhar o que o PHP está fazendo e, em seguida, fazer exatamente o oposto.
Philipp

Respostas:


65

Se um idioma suportar inerentemente exceções, é preferível lançar exceções e os clientes poderão capturar a exceção se não quiserem que ela resulte em falha. De fato, os clientes do seu código esperam exceções e se deparam com muitos bugs porque não verificarão os valores de retorno.

Existem algumas vantagens em usar exceções, se você tiver uma escolha.

Mensagens

Exceções contêm mensagens de erro legíveis pelo usuário que podem ser usadas pelos desenvolvedores para depuração ou mesmo exibidas aos usuários, se desejado. Se o código de consumo não puder manipular a exceção, ele poderá sempre registrá-lo para que os desenvolvedores possam percorrer os logs sem precisar parar em todos os outros rastreamentos para descobrir qual era o valor de retorno e mapeá-lo em uma tabela para descobrir qual era o valor. exceção real.

Com valores de retorno, não há informações adicionais que possam ser fornecidas com facilidade. Alguns idiomas suportam a realização de chamadas de método para obter a última mensagem de erro. Portanto, essa preocupação é um pouco menor, mas isso exige que o chamador faça chamadas extras e às vezes exigirá acesso a um 'objeto especial' que carrega essas informações.

No caso de mensagens de exceção, forneço o máximo de contexto possível, como:

Não foi possível recuperar uma política do nome "foo" para o usuário "bar", que foi referenciado no perfil do usuário.

Compare isso com um código de retorno -85. Qual você prefere?

Pilhas de chamadas

As exceções também costumam ter pilhas de chamadas detalhadas que ajudam a depurar o código cada vez mais rápido e também podem ser registradas pelo código de chamada, se desejado. Isso permite que os desenvolvedores identifiquem o problema normalmente na linha exata e, portanto, são muito poderosos. Mais uma vez, compare isso com um arquivo de log com valores de retorno (como -85, 101, 0 etc.), qual você prefere?

Falha na abordagem tendenciosa rápida

Se um método for chamado em algum lugar que falhar, lançará uma exceção. O código de chamada deve suprimir a exceção explicitamente ou ela falhará. Eu achei isso realmente incrível, porque durante o desenvolvimento e teste (e até na produção) o código falha rapidamente, forçando os desenvolvedores a corrigi-lo. No caso de valores de retorno, se uma verificação de um valor de retorno for perdida, o erro será ignorado silenciosamente e o erro aparecerá em algum lugar inesperado, geralmente com um custo muito maior para depuração e correção.

Quebrando e desembrulhando exceções

Exceções podem ser agrupadas dentro de outras exceções e depois desembrulhadas, se necessário. Por exemplo, seu código pode lançar o ArgumentNullExceptionque o código de chamada pode quebrar dentro de um UnableToRetrievePolicyExceptionporque essa operação falhou no código de chamada. Enquanto o usuário pode receber uma mensagem semelhante ao exemplo que forneci acima, algum código de diagnóstico pode desembrulhar a exceção e descobrir que ArgumentNullExceptionele causou o problema, o que significa que é um erro de codificação no código do consumidor. Isso pode disparar um alerta para que o desenvolvedor possa corrigir o código. Esses cenários avançados não são fáceis de implementar com os valores de retorno.

Simplicidade do código

Este é um pouco mais difícil de explicar, mas aprendi com essa codificação tanto com valores de retorno quanto com exceções. O código que foi escrito usando valores de retorno normalmente fazia uma chamada e, em seguida, fazia uma série de verificações sobre qual era o valor de retorno. Em alguns casos, ele chamaria outro método e agora terá outra série de verificações para os valores de retorno desse método. Com exceções, o tratamento de exceções é muito mais simples na maioria, se não em todos os casos. Você tem um bloco try / catch / finalmente, com o tempo de execução tentando o melhor para executar o código nos blocos finalmente para limpeza. Até blocos try / catch / finalmente aninhados são relativamente mais fáceis de acompanhar e manter do que aninhados if / else e valores de retorno associados de vários métodos.

Conclusão

Se a plataforma que você está usando suporta exceções (especialmente Java ou .NET), você deve definitivamente assumir que não há outra maneira, exceto lançar exceções, porque essas plataformas têm diretrizes para lançar exceções e seus clientes esperam tão. Se eu estivesse usando sua biblioteca, não me preocuparei em verificar os valores de retorno, porque espero que exceções sejam lançadas, é assim que o mundo nessas plataformas é.

No entanto, se fosse C ++, seria um pouco mais desafiador determinar porque já existe uma grande base de código com códigos de retorno e um grande número de desenvolvedores é ajustado para retornar valores em vez de exceções (por exemplo, o Windows é repleto de HRESULTs) . Além disso, em muitos aplicativos, também pode ser um problema de desempenho (ou pelo menos percebido).


5
O Windows retorna valores HRESULT das funções C ++ para manter a compatibilidade C em suas APIs públicas (e porque você começa a entrar em um mundo de mágoas tentando organizar exceções através dos limites). Não siga cegamente o modelo do sistema operacional se estiver escrevendo um aplicativo.
Cody Grey

2
A única coisa que eu acrescentaria a isso é a menção de um acoplamento mais flexível. As exceções permitem lidar com muitas situações inesperadas no local mais apropriado. Por exemplo, em um aplicativo Web, você deseja retornar 500 em qualquer exceção para a qual seu código não estava preparado, em vez de travar o aplicativo Web. Portanto, você precisa de algum tipo de captura na parte superior do seu código (ou na sua estrutura). Uma situação semelhante existe nas GUIs da área de trabalho. Mas você também pode colocar manipuladores menos gerais em vários locais do código para lidar com uma variedade de situações de falha de maneira apropriada para o processo atual que está sendo tentado.
precisa

2
@TrentonMaki Se você está falando sobre erros de construtores C ++, a melhor resposta está aqui: parashift.com/c++-faq-lite/ctors-can-throw.html . Em resumo, lance uma exceção, mas lembre-se de limpar os possíveis vazamentos primeiro. Não conheço outras línguas em que apenas jogar de um construtor seja ruim. Acho que a maioria dos usuários de uma API prefere capturar exceções em vez de verificar códigos de erro.
Ogre Psalm33

3
"Tais cenários avançados não são fáceis de implementar com os valores de retorno". Certamente você pode! Tudo o que você precisa fazer é criar uma ErrorStateReturnVariablesuperclasse, e uma de suas propriedades é InnerErrorState(que é uma instância de ErrorStateReturnVariable), que as subclasses de implementação podem definir para mostrar uma cadeia de erros ... oh, espere. : p
Brian S

5
Só porque um idioma suporta exceções não os torna uma panacéia. Exceções introduzem caminhos de execução ocultos e, portanto, seus efeitos precisam ser adequadamente controlados; try / catch são fáceis de adicionar, mas recebendo o direito a recuperação é difícil ...
Matthieu M.

21

As variáveis ​​de erro são uma relíquia de idiomas como C, onde as exceções não estavam disponíveis. Hoje, você deve evitá-los, exceto quando estiver escrevendo uma biblioteca que é potencialmente usada a partir de um programa C (ou um idioma semelhante sem manipulação de exceção).

Obviamente, se você tiver um tipo de erro que possa ser melhor classificado como "aviso" (= sua biblioteca pode fornecer um resultado válido e o chamador pode ignorar o aviso se achar que isso não é importante), um indicador de status no formato de uma variável pode fazer sentido, mesmo em idiomas, com exceções. Mas cuidado. Os chamadores da biblioteca tendem a ignorar esses avisos, mesmo que não devam. Então pense duas vezes antes de introduzir essa construção em sua biblioteca.


1
Obrigada pelo esclarecimento! Sua resposta + Omer Iqbal respondeu à minha pergunta.
Mikayla Maki

Outra maneira de lidar com "avisos" é lançar uma exceção por padrão e ter algum tipo de sinalizador opcional para impedir que a exceção seja lançada.
Cyanfish

1
@ Cyanfish: sim, mas é preciso ter cuidado para não projetar demais essas coisas, especialmente ao criar bibliotecas. É melhor fornecer um mecanismo de aviso simples e funcional que 2, 3 ou mais.
Doc Brown

Uma exceção só deve ser lançada quando ocorrer um cenário de falha, desconhecido ou irrecuperável - circunstâncias excepcionais . Você deve esperar impacto no desempenho ao construir exceções
Gusdor

@ Gusdor: absolutamente, é por isso que um "aviso" típico IMHO não deve lançar uma exceção por padrão. Mas isso também depende um pouco do nível de abstração. Às vezes, um serviço ou biblioteca simplesmente não pode decidir se um evento incomum deve ser tratado como uma exceção do ponto de vista dos chamadores. Pessoalmente, nessas situações, eu prefiro a lib apenas para definir um indicador de aviso (sem exceção), deixe o chamador testar essa sinalização e lançar uma exceção se achar apropriado. Isso é o que eu tinha em mente quando escrevi acima "é melhor fornecer um mecanismo de aviso em uma biblioteca".
Doc Brown

20

Existem várias maneiras de sinalizar um erro:

  • uma variável de erro para verificar: C , Go , ...
  • uma exceção: Java , C # , ...
  • um manipulador de "condição": Lisp (apenas?), ...
  • um retorno polimórfico: Haskell , ML , Rust , ...

O problema da variável de erro é que é fácil esquecer de verificar.

O problema das exceções é que isso cria caminhos ocultos de execuções e, embora seja fácil escrever try / catch, é realmente difícil obter uma recuperação adequada na cláusula catch (sem suporte dos sistemas / compiladores de tipos).

O problema dos manipuladores de condições é que eles não se compõem bem: se você possui execução dinâmica de código (funções virtuais), é impossível prever quais condições devem ser tratadas. Além disso, se a mesma condição puder ser levantada em vários pontos, não há como dizer que uma solução uniforme pode ser aplicada a cada vez e ela rapidamente se torna confusa.

Os retornos polimórficos ( Either a bem Haskell) são minha solução favorita até agora:

  • explícito: nenhum caminho oculto de execução
  • explícito: totalmente documentado na assinatura do tipo de função (sem surpresas)
  • difícil de ignorar: você precisa padronizar a correspondência para obter o resultado desejado e lidar com o caso de erro

O único problema é que eles podem levar a verificações excessivas; os idiomas que os utilizam têm expressões idiomáticas para encadear as chamadas de funções que os utilizam, mas ainda pode exigir um pouco mais de digitação / confusão. Em Haskell, isso seria mônadas ; no entanto, isso é muito mais assustador do que parece, consulte Programação Orientada a Ferrovias .


1
Boa resposta! Eu esperava conseguir uma lista de maneiras de manipular erros.
precisa

Arrghhhh! Quantas vezes eu vi isso "O problema da variável de erro é que é fácil esquecer de verificar". O problema com exceções é que é fácil esquecer de capturá-lo. Em seguida, seu aplicativo falha. Seu chefe não quer pagar para corrigi-lo, mas seus clientes param de usar seu aplicativo porque ficam frustrados com as falhas. Tudo por causa de algo que não afetaria a execução do programa se os códigos de erro fossem retornados e ignorados. A única vez que vi pessoas ignorar códigos de erro é quando isso não importava muito. O código mais alto da cadeia sabe que algo deu errado.
Dunk

1
@ Dunker: Eu não acho que a minha resposta faz o pedido de desculpas também; no entanto, pode depender do tipo de código que você escreve. Minha experiência pessoal de trabalho tende a favorecer sistemas que falham na presença de erros porque a corrupção silenciosa de dados é pior (e não detectada) e os dados em que trabalho são valiosos para o cliente (é claro, também significam correções urgentes).
Matthieu M.

Não se trata de corrupção silenciosa de dados. É uma questão de entender seu aplicativo e saber quando e onde você precisa verificar se uma operação foi bem-sucedida ou não. Em muitos casos, a determinação e o manuseio podem ser adiados. Outra pessoa não deveria me dizer quando eu tenho que lidar com uma operação com falha, o que uma exceção exige. É o meu aplicativo, sei quando e onde quero lidar com problemas relevantes. Se as pessoas escrevem aplicativos que podem corromper dados, isso é apenas um péssimo trabalho. Escrever aplicativos que travam (que eu vejo muito) também está fazendo um trabalho ruim.
Dunk

12

Eu acho horrível. No momento, estou refatorando um aplicativo Java que usa valores de retorno em vez de exceções. Embora você possa não estar trabalhando com Java, acho que isso se aplica.

Você acaba com um código como este:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

Ou isto:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

Eu prefiro que as ações lançem exceções, então você acaba com algo como:

x.doActionA();
x.doActionB();

Você pode agrupar isso em uma tentativa de captura e obter a mensagem da exceção ou pode optar por ignorar a exceção, por exemplo, ao excluir algo que já pode ter desaparecido. Ele também preserva seu rastreamento de pilha, se você tiver um. Os próprios métodos também se tornam mais fáceis. Em vez de lidar com as exceções, eles apenas lançam o que deu errado.

Código atual (horrível):

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

Novo e melhorado:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

O rastreamento de faixas é preservado e a mensagem está disponível na exceção, em vez do inútil "Algo deu errado!".

Obviamente, você pode fornecer melhores mensagens de erro e deve. Mas este post está aqui porque o código atual no qual estou trabalhando é uma dor e você não deve fazer o mesmo.


1
Um problema, porém, há situações em que o seu 'novo e aprimorado' perderia o contexto de onde a exceção ocorre inicialmente. Por exemplo, na "versão atual (horrível)" de doActionA (), a cláusula catch teria acesso a variáveis ​​de instância e outras informações do objeto anexo para fornecer uma mensagem mais útil.
InformedA

1
Isso é verdade, mas atualmente não acontece aqui. E você sempre pode capturar a exceção em doActionA e envolvê-la em outra exceção com uma mensagem de status. Então você ainda terá o rastreamento de pilha e a mensagem útil. throw new Exception("Something went wrong with " + instanceVar, ex);
mrjink

Eu concordo, isso pode não acontecer na sua situação. Mas você não pode "sempre" colocar as informações em doActionA (). Por quê? O chamador de doActionA () pode ser o único que contém as informações que você precisa incluir.
InformedA

2
Portanto, peça ao chamador que o inclua quando lida com a exceção. O mesmo se aplica à pergunta original, no entanto. Não há nada que você possa fazer agora que não possa fazer exceções, e isso leva a um código mais limpo. Prefiro exceções do que booleanos retornados ou mensagens de erro.
precisa

Você assume geralmente uma falsa suposição de que uma exceção que ocorre em qualquer uma das "someOperation" s pode ser tratada e limpa da mesma maneira. O que acontece na vida real é que você precisa capturar e manipular exceções para cada operação. Portanto, você não apenas lança uma exceção como no seu exemplo. Além disso, o uso de exceções acaba criando um monte de blocos try-catch aninhados ou uma série de blocos try-catch. Freqüentemente, torna o código muito menos legível. Não sou contra exceções, mas uso a ferramenta apropriada para a situação específica. Exceções são apenas uma ferramenta.
Dunk

5

"Para lidar com vários erros possíveis, isso não deve interromper a execução"

Se você quer dizer que os erros não devem interromper a execução da função atual, mas devem ser relatados ao chamador de alguma maneira - você tem algumas opções que realmente não foram mencionadas. Este caso é realmente mais um aviso do que um erro. Arremessar / Retornar não é uma opção, pois encerra a função atual. Uma única mensagem de erro parametrizada ou retornada permite apenas a ocorrência de um desses erros.

Dois padrões que eu usei são:

  • Uma coleção de erro / aviso, passada ou mantida como uma variável de membro. Ao qual você anexa itens e continua processando. Eu, pessoalmente, não gosto muito dessa abordagem, pois sinto que ela diminui o poder do chamador.

  • Passe um objeto manipulador de erro / aviso (ou defina-o como uma variável de membro). E cada erro chama uma função de membro do manipulador. Dessa forma, o chamador pode decidir o que fazer com esses erros sem fim.

O que você passa para essas coleções / manipuladores deve conter contexto suficiente para que o erro seja manipulado "corretamente" - uma string geralmente é muito pequena, pois algumas instâncias de Exception costumam ser sensatas - mas algumas vezes desaprovadas (como abuso de exceções) .

O código oficial usando um manipulador de erros pode ter esta aparência

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}

3
+1 por perceber que o usuário deseja um aviso, e não um erro. Vale a pena mencionar o warningspacote do Python , que fornece outro padrão para esse problema.
James_pic

Obrigado pela ótima resposta! Isso é mais do que eu queria ver, padrões adicionais para lidar com erros em que a tentativa / captura tradicional pode não ser suficiente.
precisa

Pode ser útil mencionar que um objeto de retorno de chamada de erro transmitido pode gerar uma exceção quando ocorrem certos erros ou avisos - na verdade, esse pode ser o comportamento padrão - mas também pode ser útil para os meios pelos quais ele pode solicitar a função de chamada para fazer alguma coisa. Por exemplo, um método "manipular erro de análise" pode fornecer ao chamador um valor que a análise deve ser considerada como retornada.
Supercat

5

Geralmente, não há nada de errado em usar esse padrão ou esse padrão, desde que você use o padrão usado por todos os outros. No desenvolvimento de Objective-C , o padrão mais preferido é passar um ponteiro em que o método chamado pode depositar um objeto NSError. As exceções são reservadas para erros de programação e levam a uma falha (a menos que você tenha programadores Java ou .NET criando seu primeiro aplicativo para iPhone). E isso funciona muito bem.


4

A pergunta já está respondida, mas não consigo evitar.

Você realmente não pode esperar que o Exception forneça uma solução para todos os casos de uso. Martelar alguém?

Há casos em que Exceções não são o fim de tudo e tudo, por exemplo, se um método recebe uma solicitação e é responsável por validar todos os campos passados, e não apenas o primeiro, é necessário pensar que deve ser possível indique a causa do erro para mais de um campo. Também deve ser possível indicar se a natureza da validação impede o usuário de ir mais longe ou não. Um exemplo disso seria uma senha não forte. Você pode mostrar uma mensagem ao usuário indicando que a senha digitada não é muito forte, mas é forte o suficiente.

Você poderia argumentar que todas essas validações poderiam ser lançadas como uma exceção no final do módulo de validação, mas seriam códigos de erro em qualquer coisa, exceto no nome.

Portanto, a lição aqui é: Exceções têm seus lugares, assim como códigos de erro. Escolheu sabiamente.


Eu acho que isso implica um design ruim. Um método que recebe argumentos deve ser capaz de lidar com eles ou não - nada no meio. Seu exemplo deve ter uma Validator(interface) injetada no método em questão (ou no objeto por trás dele). Dependendo da injeção, Validatoro método continuará com senhas ruins - ou não. O código ao redor poderia então tentar a WeakValidatorse o usuário pedisse depois, por exemplo, a WeakPasswordExceptionfoi lançado pelo inicialmente tentado StrongValidator.
JHR

Ah, mas eu não disse que isso não poderia ser uma interface ou um validador. Eu nem mencionei JSR303. E, se você ler atentamente, certifiquei-me de não dizer uma senha fraca, mas não muito forte. Uma senha fraca seria um motivo para interromper o fluxo e solicitar ao usuário uma senha mais forte.
Alexandre Santos

E o que você faria com uma senha meio forte, mas não muito fraca? Você interrompe o fluxo e mostra ao usuário uma mensagem indicando que a senha digitada não é muito forte. Então, tem um MiddlyStrongValidatorou algo assim. E se isso realmente não interrompeu seu fluxo, ele Validatordeve ter sido chamado com antecedência, ou seja, antes de prosseguir com o fluxo enquanto o usuário ainda está digitando sua senha (ou similar). Mas a validação não fazia parte do método em questão em primeiro lugar. :) Provavelmente uma questão de gosto, afinal de contas ...
JHR

@jhr Nos validadores que escrevi, normalmente crio um AggregateException(ou similar ValidationException) e coloco exceções específicas para cada problema de validação no InnerExceptions. Por exemplo, pode ser BadPasswordException: "A senha do usuário é menor que o tamanho mínimo de 6" ou MandatoryFieldMissingException: "O primeiro nome deve ser fornecido ao usuário" etc. Isso não é equivalente a códigos de erro. Todas essas mensagens podem ser exibidas para o usuário de uma maneira que eles entendam, e se uma NullReferenceExceptionfoi lançada, obtivemos um bug.
Omer Iqbal

4

Existem casos de uso nos quais os códigos de erro são preferíveis às exceções.

Se o seu código puder continuar apesar do erro, mas precisar de relatórios, uma exceção será uma má escolha, pois as exceções encerram o fluxo. Por exemplo, se você estiver lendo um arquivo de dados e descobrir que ele contém alguns dados incorretos não terminais, talvez seja melhor ler o restante do arquivo e relatar o erro, em vez de falhar completamente.

As outras respostas abordaram por que as exceções devem ser preferidas aos códigos de erro em geral.


Se um aviso precisar ser registrado ou algo assim, que seja. Mas se um erro "precisar ser reportado", pelo qual eu suponho que você quer dizer reportar ao usuário, não há como garantir que o código circundante leia seu código de retorno e o relate.
JHR

Não, quero dizer, denunciá-lo ao chamador. É responsabilidade do chamador decidir se o usuário precisa saber sobre o erro ou não, assim como uma exceção.
Jack Aidley

1
@jhr: Quando há alguma garantia de alguma coisa? O contrato para uma classe pode especificar que os clientes tenham certas responsabilidades; se os clientes cumprirem o contrato, farão o que o contrato exigir. Caso contrário, qualquer conseqüência será culpa do código do cliente. Se alguém deseja se proteger contra erros acidentais do cliente e tem controle sobre o tipo retornado por um método de serialização, pode-se incluir um sinalizador de "possível corrupção não reconhecida" e não permitir que os clientes leiam dados sem chamar um AcknowledgePossibleCorruptionmétodo. #
18714

1
... mas ter uma classe de objeto para armazenar informações sobre problemas pode ser mais útil do que lançar exceções ou retornar um código de erro de aprovação / reprovação. Caberia ao aplicativo usar essas informações de maneira adequada (por exemplo, ao carregar o arquivo "Foo", informe ao usuário que os dados podem não ser confiáveis ​​e solicite ao usuário que escolha um novo nome ao salvar).
Supercat

1
Há uma garantia ao usar exceções: se você não as capturar, elas aumentam - na pior das hipóteses, até a interface do usuário. Não existe essa garantia se você usar códigos de retorno que ninguém lê. Claro, siga a API se você quiser usá-la. Concordo! Mas há espaço para erro, infelizmente ...
jhr

2

Definitivamente, não há nada errado em não usar exceções quando as exceções não são uma boa opção.

Quando a execução do código não deve ser interrompida (por exemplo, atuando na entrada do usuário que pode conter vários erros, como um programa para compilar ou um formulário para processar), acho que coletar erros em variáveis ​​de erro como has_errorse error_messagesé realmente um design muito mais elegante do que lançar uma exceção no primeiro erro. Ele permite encontrar todos os erros na entrada do usuário sem forçar o usuário a reenviar desnecessariamente.


Opinião interessante sobre a questão. Penso que o problema com a minha pergunta e a minha compreensão não é uma terminologia clara. O que você descreve não é excepcional, mas é um erro. Como devemos chamar isso?
21814 Mikayla Maki

1

Em algumas linguagens de programação dinâmicas, você pode usar valores de erro e manipulação de exceções . Isso é feito retornando o objeto de exceção não lançada no lugar do valor de retorno comum, que pode ser verificado como um valor de erro, mas gera uma exceção se não estiver marcado.

No Perl 6 , é feito via fail, que, se dentro do no fatal;escopo, retorna um Failureobjeto de exceção não-lançado especial .

No Perl 5 você pode usar Contextual :: Return com o qual você pode fazer return FAIL.


-1

A menos que haja algo muito específico, acho que ter uma variável de erro para validação é uma má ideia. O objetivo parece ser economizar o tempo gasto na validação (você pode simplesmente retornar o valor da variável)

Mas, se você alterar alguma coisa, precisará recalcular esse valor de qualquer maneira. Não posso dizer mais sobre parar e lançar exceções.

Edição: Eu não sabia que isso é uma questão de paradigma de software, em vez de um caso específico.

Deixe-me esclarecer mais meus pontos em um caso particular em que minha resposta faria sentido

  1. Eu tenho uma coleção de objetos de entidade
  2. Tenho serviços da web de estilo processual que funcionam com esses objetos de entidade

Existem dois tipos de erros:

  1. Os erros durante o processamento ocorrem na camada de serviço
  2. Os erros porque existem inconsistências nos objetos da entidade

Na camada de serviço, não há escolha a não ser usar o objeto Result como wrapper, que é a equivalência da variável de erro. É possível simular uma exceção por meio de chamada de serviço em protocolo como http, mas definitivamente não é uma boa coisa a fazer. Não estou falando sobre esse tipo de erro e não acho que esse seja o tipo de erro solicitado nesta pergunta.

Eu estava pensando no segundo tipo de erro. E minha resposta é sobre esse segundo tipo de erro. Nos objetos de entidade, existem opções para nós, algumas delas são

  • usando uma variável de validação
  • lança uma exceção imediatamente quando um campo é definido incorretamente do setter

Usar variável de validação é o mesmo que ter um único método de validação para cada objeto de entidade. Em particular, o usuário pode definir o valor de maneira a manter o levantador puramente, sem efeito colateral (geralmente é uma boa prática) ou pode-se incorporar a validação em cada levantador e salvar o resultado em uma variável de validação. A vantagem disso seria economizar tempo, o resultado da validação é armazenado em cache na variável de validação, de modo que quando o usuário chama validation () várias vezes, não há necessidade de validação múltipla.

A melhor coisa a fazer nesse caso é usar um único método de validação sem usar nenhuma validação para armazenar em cache o erro de validação. Isso ajuda a manter o levantador apenas como levantador.


Entendo do que você está falando. Abordagem interessante. Fico feliz que faz parte do conjunto de respostas.
23814 Mikayla Maki
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.