Usando uma validação try-finally (sem captura) vs enum-state

9

Eu tenho lido os conselhos sobre esta questão sobre como uma exceção deve ser tratada o mais próximo possível de onde é levantada.

Meu dilema sobre a melhor prática é se deve-se usar um try / catch / finalmente para retornar uma enumeração (ou um int que represente um valor, 0 para erro, 1 para ok, 2 para aviso etc., dependendo do caso), para que uma resposta está sempre em ordem ou deve-se deixar passar a exceção para que a parte que está chamando lide com ela?

Pelo que pude entender, isso pode ser diferente dependendo do caso, então o conselho original parece estranho.

Por exemplo, em um serviço da web, você sempre desejaria retornar um estado, é claro, portanto, qualquer exceção deve ser tratada no local, mas digamos que dentro de uma função que publique / obtenha alguns dados via http, você deseje o exceção (por exemplo, no caso de 404), apenas passar para o que a acionou. Caso contrário, seria necessário criar uma maneira de informar a parte que chama da qualidade do resultado (erro: 404), bem como o próprio resultado.

Embora seja possível tentar capturar a exceção 404 dentro da função auxiliar que obtém / publica os dados, você deveria? É apenas eu que uso um smallint para denotar estados no programa (e documentá-los adequadamente, é claro) e depois utilizo essas informações para fins de validação de sanidade (tudo ok / tratamento de erros) fora?

Atualização: Eu esperava uma exceção fatal / não fatal para a classificação principal, mas não quis incluí-la para não prejudicar as respostas. Deixe-me esclarecer sobre o que é a pergunta: Manipulando as exceções lançadas, não lançando exceções. Qual é o efeito desejado: detecte um erro e tente se recuperar dele. Se a recuperação não for possível, forneça o feedback mais significativo.

Novamente, com o exemplo http get / post, a pergunta é: você deve fornecer um novo objeto que descreva o que aconteceu com o chamador original? Se esse auxiliar estivesse em uma biblioteca que você está usando, esperaria que ele fornecesse um código de status para a operação ou o incluiria em um bloco try-catch? Se você o estiver projetando , você forneceria um código de status ou lançaria uma exceção e deixaria o nível superior convertê-lo em um código / mensagem de status?

Sinopse: Como você escolhe se um pedaço de código, em vez de produzir uma exceção, retorna um código de status junto com os resultados que ele pode produzir?

Mihalis Bagos
fonte
11
Isso não está relacionado ao erro que está alterando a forma de manipulação de erros que está sendo usada. No caso 404, você deixaria passar porque não consegue lidar com isso.
Stonemetal
"um int que representa um valor, 0 para erro, 1 para ok, 2 para aviso etc" Por favor, diga que este foi um exemplo simples! Usando 0 a média OK é muito definitivamente o padrão ...
Anaximandro

Respostas:

15

Exceções devem ser usadas para condições excepcionais. Lançar uma exceção é basicamente fazer a declaração: "Não posso lidar com essa condição aqui; alguém mais acima na pilha de chamadas pode capturar isso para mim e lidar com isso?"

O retorno de um valor pode ser preferível, se estiver claro que o chamador aceitará esse valor e fará algo significativo com ele. Isso é especialmente verdadeiro se o lançamento de uma exceção tem implicações no desempenho, ou seja, pode ocorrer em um loop restrito. Lançar uma exceção leva muito mais tempo do que retornar um valor (em pelo menos duas ordens de magnitude).

Exceções nunca devem ser usadas para implementar a lógica do programa. Em outras palavras, não lance uma exceção para fazer algo; lançar uma exceção para declarar que não poderia ser feito.

Robert Harvey
fonte
Obrigado pela resposta, é a mais informativa, mas meu foco está no tratamento de exceções e não no lançamento de exceções. Você deve capturar a exceção 404 assim que a receber ou deve deixá-la subir mais na pilha?
Mihalis Bagos
Eu vejo sua edição, mas isso não muda minha resposta. Converta a exceção em um código de erro, se isso for significativo para o chamador. Caso contrário, lide com a exceção; deixe subir a pilha; ou pegue-o, faça algo com ele (como gravar em um log ou qualquer outra coisa) e repita o processo.
Robert Harvey
11
+1: para uma explicação razoável e equilibrada. Uma exceção deve ser usada para lidar com casos excepcionais. Caso contrário, uma função ou método deve retornar um valor significativo (tipo de enumeração ou opção) e o chamador deve tratá-lo adequadamente. Talvez se possa mencionar uma terceira alternativa que é popular na programação funcional, ou seja, continuações.
Giorgio #
4

Alguns bons conselhos que li uma vez foram: lançar exceções quando você não puder progredir, devido ao estado dos dados com os quais está lidando; no entanto, se você tiver um método que possa gerar uma exceção, forneça sempre que possível um método para afirmar se os dados são realmente válido antes que o método seja chamado.

Por exemplo, System.IO.File.OpenRead () lançará um FileNotFoundException se o arquivo fornecido não existir, mas também fornece um método .Exists () que retorna um valor booleano indicando se o arquivo está presente, que você deve chamar antes chamando OpenRead () para evitar exceções inesperadas.

Para responder à parte "quando devo lidar com uma exceção" da pergunta, eu diria onde quer que você possa realmente fazer algo a respeito. Se o seu método não puder lidar com uma exceção lançada por um método chamado, não a pegue. Deixe que ele suba mais alto na cadeia de chamadas para algo que possa lidar com isso. Em alguns casos, pode ser apenas um logger ouvindo Application.UnhandledException.

Trevor Pilley
fonte
Muitas pessoas, especialmente python programadores, preferem EAFP ou seja, "É mais fácil pedir perdão do que está a obter permissão"
Mark Booth
11
+1 para comentar sobre como evitar exceções, como em .Exists ().
22412 codeboutloud
+1 Este ainda é um bom conselho. No código que eu escrevo / gerencio, uma Exceção é "Excepcional", 9/10 vezes que uma Exceção é destinada a um desenvolvedor, diz hey, você deve estar na defensiva programação! Na outra vez, é algo com o qual não podemos lidar, registramos e saímos da melhor maneira possível. A consistência é importante, por exemplo, por convenção, normalmente teríamos uma verdadeira resposta falsa e mensagens internas para tarifa / processamento padrão. As APIs de terceiros que parecem gerar exceções para tudo podem ser tratadas durante a chamada e retornadas usando o processo padrão acordado.
Gavin Howden
3

use try / catch / finalmente para retornar uma enumeração (ou um int que represente um valor, 0 para erro, 1 para aprovação, 2 para aviso etc., dependendo do caso), para que uma resposta esteja sempre em ordem,

Esse é um design terrível. Não "mascare" uma exceção traduzindo para um código numérico. Deixe isso como uma exceção apropriada e inequívoca.

ou deve-se deixar passar a exceção para que a parte que está chamando lide com isso?

É para isso que servem as exceções.

tratado o mais próximo possível de onde é levantado

Não é uma verdade universal . É uma boa ideia algumas vezes. Outras vezes, não é tão útil.

S.Lott
fonte
Não é um design terrível. A Microsoft o implementa em muitos lugares, nomeadamente no provedor de associação asp.NET padrão. Fora isso, não consigo ver como essa resposta contribui com algo para a conversa
Mihalis Bagos 17/02/2012
@ MihalisBagos: Tudo o que posso fazer é sugerir que a abordagem da Microsoft não seja adotada por todas as linguagens de programação. Em idiomas sem exceções, retornar um valor é essencial. C é o exemplo mais notável. Em idiomas com exceções, retornar "valores de código" para indicar erros é um design terrível. Isso leva a (algumas vezes) ifinstruções complicadas em vez de (às vezes) um tratamento de exceção mais simples. A resposta ("Como você escolhe ...") é simples. Não . Eu acho que dizer isso contribui para a conversa. Você é livre para ignorar esta resposta, no entanto.
21412 S.Lott
Não estou dizendo que sua opinião não conta, mas estou dizendo que sua opinião não foi desenvolvida. Com esse comentário, entendo que o raciocínio é que onde podemos usar exceções, deveríamos, apenas porque podemos? Eu não vejo o código de status como mascarar, em vez de uma categorização / organização dos casos de fluxo de código
Mihalis Bagos
11
A exceção já é uma categorização / organização. Não vejo o valor em substituir uma exceção inequívoca por um valor de retorno que pode ser facilmente confundido com valores de retorno "normais" ou "não excepcionais". Os valores de retorno sempre devem ser não excepcionais. Minha opinião não é muito desenvolvida: é muito simples. Não transforme uma exceção em um código numérico. Já era um objeto de dados perfeitamente bom que serve todos os casos de uso perfeitamente bons que podemos imaginar.
S.Lott 17/02/2012
3

Eu concordo com S.Lott . Capturar a exceção o mais próximo possível da fonte pode ser uma boa ou má idéia, dependendo da situação. A chave para lidar com exceções é capturá-las apenas quando você pode fazer algo sobre isso. Pegá-los e retornar um valor numérico para a função de chamada geralmente é um design ruim. Você só quer deixá-los flutuar até que você possa se recuperar.

Eu sempre considero o tratamento de exceções um passo à frente da minha lógica de aplicativo. Pergunto-me: se essa exceção for lançada, quanto tempo de backup da pilha de chamadas devo rastrear antes que meu aplicativo esteja em um estado recuperável? Em muitos casos, se não há nada que eu possa fazer no aplicativo para recuperar, isso pode significar que eu não o pego até o nível superior e apenas registro a exceção, falho no trabalho e tento desligar de maneira limpa.

Realmente não existe uma regra rígida e rápida sobre quando e como configurar o tratamento de exceções, exceto deixá-los em paz até que você saiba o que fazer com eles.

DwayneAllen
fonte
1

Exceções são coisas bonitas. Eles permitem que você produza uma descrição clara de um problema de tempo de execução sem recorrer a ambigüidades desnecessárias. Exceções podem ser digitadas, subtipadas e podem ser tratadas por tipo. Eles podem ser repassados ​​para manipulação em outros lugares e, se não puderem ser manipulados, podem ser re-levantados para serem tratados em uma camada mais alta no seu aplicativo. Eles também retornarão automaticamente do seu método sem a necessidade de invocar muita lógica maluca para lidar com códigos de erro ofuscados.

Você deve lançar uma exceção imediatamente após encontrar dados inválidos no seu código. Você deve agrupar as chamadas para outros métodos em uma tentativa .. captura .. finalmente para lidar com quaisquer exceções que possam ser lançadas e, se você não souber como responder a uma exceção, jogue-a novamente para indicar as camadas mais altas que há algo errado que deve ser tratado em outro lugar.

Gerenciar códigos de erro pode ser muito difícil. Você geralmente acaba com muitas duplicações desnecessárias no seu código e / ou muita lógica bagunçada para lidar com estados de erro. Ser um usuário e encontrar um código de erro é ainda pior, pois o código em si não será significativo e não fornecerá ao usuário um contexto para o erro. Uma exceção, por outro lado, pode dizer ao usuário algo útil, como "Você esqueceu de inserir um valor" ou "você inseriu um valor inválido, eis o intervalo válido que você pode usar ..." ou "Eu não Para saber o que aconteceu, entre em contato com o suporte técnico e diga que eu acabei de travar e forneça o seguinte rastreamento de pilha ... ".

Portanto, minha pergunta ao OP é por que diabos você NÃO deseja usar exceções ao retornar códigos de erro?

S.Robins
fonte
0

Todas boas respostas. Gostaria também de acrescentar que o retorno de um código de erro em vez de lançar uma exceção pode tornar o código do chamador mais complicado. Diga o método A chama o método B chama o método C e C encontra um erro. Se C retornar um código de erro, agora B precisará ter lógica para determinar se pode lidar com esse código de erro. Se não puder, será necessário retorná-lo para A. Se A não puder lidar com o erro, o que você fará? Lançar uma exceção? Neste exemplo, o código é muito mais limpo se C simplesmente lança uma exceção, B não captura a exceção e, portanto, aborta automaticamente sem nenhum código extra necessário para fazê-lo, e A pode capturar certos tipos de exceção e permitir que outros continuem a chamada pilha.

Isso traz à mente uma boa regra para codificar:

Linhas de código são como balas de ouro. Você deseja usar o mínimo possível para realizar o trabalho.

Raymond Saltrelli
fonte
0

Usei uma combinação das duas soluções: para cada função de validação, passo um registro que preenchei com o status de validação (um código de erro). No final da função, se houver um erro de validação, lancei uma exceção, desta forma, não ligo uma exceção para cada campo, mas apenas uma vez.

Eu também aproveitei que o lançamento de uma exceção interromperia a execução porque não quero que a execução continue quando os dados forem inválidos.

Por exemplo

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

Se depender apenas de booleano, o desenvolvedor que usa minha função deve levar isso em consideração ao escrever:

if not Validate() then
  DoNotContinue();

mas ele pode se esquecer e apenas ligar Validate()(eu sei que ele não deveria, mas talvez ele possa).

Então, no código acima, ganhei as duas vantagens:

  1. Apenas uma exceção na função de validação.
  2. A exceção, mesmo não detectada, interromperá a execução e aparecerá no tempo do teste
Bahaa
fonte
0

Não há uma resposta aqui - como se não houvesse um tipo de HttpException.

Faz muito sentido que as bibliotecas HTTP subjacentes gerem uma exceção quando obtêm uma resposta 4xx ou 5xx; da última vez que olhei para as especificações HTTP, esses eram erros.

Quanto a lançar essa exceção - ou envolvê-la e rever novamente - acho que realmente é uma questão de caso de uso. Por exemplo, se você estiver escrevendo um wrapper para capturar alguns dados da API e expô-los a aplicativos, poderá decidir que semanticamente uma solicitação de um recurso inexistente que retorne um HTTP 404 faria mais sentido capturar isso e retornar nulo. Por outro lado, um erro 406 (não aceitável) pode valer a pena lançar um erro, pois isso significa que algo mudou e o aplicativo deve estar travando, queimando e gritando por ajuda.

Wyatt Barnett
fonte