Considere uma situação em que tenho três (ou mais) maneiras de realizar um cálculo, cada uma das quais pode falhar com uma exceção. A fim de tentar cada cálculo até encontrarmos um que seja bem-sucedido, tenho feito o seguinte:
double val;
try { val = calc1(); }
catch (Calc1Exception e1)
{
try { val = calc2(); }
catch (Calc2Exception e2)
{
try { val = calc3(); }
catch (Calc3Exception e3)
{
throw new NoCalcsWorkedException();
}
}
}
Existe algum padrão aceito que alcance isso de uma maneira mais agradável? Claro que eu poderia envolver cada cálculo em um método auxiliar que retorna nulo em caso de falha e, em seguida, apenas usar o ??
operador, mas há uma maneira de fazer isso de forma mais geral (ou seja, sem ter que escrever um método auxiliar para cada método que desejo usar )? Já pensei em escrever um método estático usando genéricos que envolva qualquer método em um try / catch e retorne null em caso de falha, mas não tenho certeza de como faria isso. Alguma ideia?
fonte
String
para aDate
usandoSimpleDateFormat.parse
e preciso tentar vários formatos diferentes em ordem, passando para o próximo quando uma exceção lançar.Respostas:
Na medida do possível, não use exceções para o fluxo de controle ou circunstâncias normais.
Mas, para responder à sua pergunta diretamente (assumindo que todos os tipos de exceção sejam os mesmos):
fonte
Calc1Exception
,Calc2Exception
eCalc3Exception
compartilham uma classe base comum.continue
no bloco catch ebreak
depois do bloco catch para que o loop termine quando um cálculo funcionar (graças a Lirik por este bit)break
declaração seguintecalc();
dentro dotry
(e nãocontinue
declaração em tudo) pode ser um pouco mais idiomática.Apenas para oferecer uma alternativa "fora da caixa", que tal uma função recursiva ...
NOTA: de forma alguma estou dizendo que esta é a melhor maneira de fazer o trabalho, apenas uma maneira diferente
fonte
return DoCalc(c++)
é equivalente areturn DoCalc(c)
- o valor pós-incrementado não será passado mais profundamente. Para fazer funcionar (e introduzir mais obscuridade), poderia ser mais parecidoreturn DoCalc((c++,c))
.Você poderia nivelar o aninhamento colocando-o em um método como este:
Mas suspeito que o verdadeiro problema de design seja a existência de três métodos diferentes que fazem essencialmente a mesma coisa (da perspectiva do chamador), mas lançam exceções diferentes e não relacionadas.
Isso pressupõe que as três exceções não estão relacionadas. Se todos eles tiverem uma classe base comum, seria melhor usar um loop com um único bloco catch, como Ani sugeriu.
fonte
Tente não controlar a lógica com base em exceções; observe também que exceções devem ser lançadas apenas em casos excepcionais. Os cálculos, na maioria dos casos, não devem lançar exceções, a menos que acessem recursos externos ou analisem strings ou algo assim. De qualquer forma, no pior caso, siga o estilo TryMethod (como TryParse ()) para encapsular a lógica de exceção e tornar seu fluxo de controle sustentável e limpo:
Outra variação utilizando o padrão Try e combinando a lista de métodos em vez de aninhados se:
e se o foreach for um pouco complicado, você pode deixar claro:
fonte
Crie uma lista de delegados para suas funções de cálculo e, em seguida, faça um loop while para percorrê-los:
Isso deve lhe dar uma ideia geral de como fazer isso (ou seja, não é uma solução exata).
fonte
Exception
. ;)Exception
.Isso parece um trabalho para ... MONADS! Especificamente, a mônada Maybe. Comece com a mônada Maybe conforme descrito aqui . Em seguida, adicione alguns métodos de extensão. Escrevi esses métodos de extensão especificamente para o problema conforme você o descreveu. O bom das mônadas é que você pode escrever os métodos de extensão exatos necessários para sua situação.
Use-o assim:
Se você se pega fazendo esse tipo de cálculo com frequência, talvez a mônada deva reduzir a quantidade de código clichê que você precisa escrever e, ao mesmo tempo, aumentar a legibilidade do código.
fonte
Maybe
isso não torna a solução monádica; ele usa zero das propriedades monádicas deMaybe
e, portanto, pode muito bem apenas usarnull
. Além disso, usado "monadicamente" seria o inverso doMaybe
. Uma solução monádica real teria que usar uma mônada de estado que mantém o primeiro valor não excepcional como seu estado, mas isso seria um exagero quando a avaliação normal encadeada funciona.Outra versão da abordagem do método try . Este permite exceções digitadas, uma vez que existe um tipo de exceção para cada cálculo:
fonte
&&
entre cada condição.Em Perl você pode fazer
foo() or bar()
, que será executadobar()
sefoo()
falhar. Em C #, não vemos essa construção "se falhar, então", mas há um operador que podemos usar para essa finalidade: o operador de coalescência nula??
, que continua somente se a primeira parte for nula.Se você puder alterar a assinatura de seus cálculos e se envolver suas exceções (como mostrado nos posts anteriores) ou reescrevê-los para retornar
null
, sua cadeia de código se torna cada vez mais breve e ainda fácil de ler:Usei as seguintes substituições para suas funções, o que resulta no valor
40.40
emval
.Sei que essa solução nem sempre será aplicável, mas você fez uma pergunta muito interessante e acredito, mesmo que o tópico seja relativamente antigo, que vale a pena considerar esse padrão quando você puder fazer as pazes.
fonte
Dado que os métodos de cálculo têm a mesma assinatura sem parâmetros, você pode registrá-los em uma lista e iterar por essa lista e executar os métodos. Provavelmente, seria ainda melhor para você usar o
Func<double>
significado de "uma função que retorna um resultado do tipodouble
".fonte
Você pode usar Task / ContinueWith e verificar a exceção. Aqui está um bom método de extensão para ajudar a torná-lo bonito:
fonte
Se o tipo real de exceção lançada não importa, você pode apenas usar um bloco catch sem tipo:
fonte
if(succeeded) { break; }
pós-captura.E use-o assim:
fonte
Parece que a intenção do OP era encontrar um bom padrão para resolver seu problema e resolver o problema atual com o qual ele estava lutando naquele momento.
Eu vi muitos bons padrões que evitam blocos aninhados de try catch , postados neste feed, mas não encontrei uma solução para o problema citado acima. Então, aqui está a solução:
Como OP mencionou acima, ele queria fazer um objeto wrapper que retornasse
null
em caso de falha . Eu o chamaria de pod ( pod seguro para exceções ).Mas e se você quiser criar um pod seguro para um resultado de Tipo de Referência retornado por funções / métodos CalcN ().
Portanto, você pode notar que não há necessidade de "escrever um método auxiliar para cada método que deseja usar" .
Os dois tipos de pods (para
ValueTypeResult
s eReferenceTypeResult
s) são suficientes .Aqui está o código de
SafePod
. Porém, não é um contêiner. Em vez disso, ele cria um wrapper de delegado seguro para exceções paraValueTypeResult
s eReferenceTypeResult
s.É assim que você pode alavancar o operador de coalescência nula
??
combinado com o poder das entidades de cidadãos de primeira classedelegate
.fonte
Você está certo sobre embrulhar cada cálculo, mas deve embrulhar de acordo com o princípio diga-não-pergunte.
As operações são simples e podem mudar seu comportamento de forma independente. E não importa por que eles ficam inadimplentes. Como prova, você pode implementar calc1DefaultingToCalc2 como:
fonte
Parece que seus cálculos têm mais informações válidas para retornar do que apenas o cálculo em si. Talvez faça mais sentido para eles fazerem seu próprio tratamento de exceções e retornar uma classe de "resultados" que contém informações de erro, informações de valor etc. Pense como a classe AsyncResult faz seguindo o padrão assíncrono. Você pode então avaliar o resultado real do cálculo. Você pode racionalizar isso pensando em termos que, se um cálculo falhar, isso será tão informativo quanto se ele fosse aprovado. Portanto, uma exceção é uma informação, não um "erro".
Claro, estou me perguntando mais sobre a arquitetura geral que você está usando para fazer esses cálculos.
fonte
while (!calcResult.HasValue) nextCalcResult()
, em vez de uma lista de Calc1, Calc2, Calc3 etc.Que tal rastrear as ações que você está fazendo ...
fonte
track
neste caso), e sei exatamente qual segmento em meu código causou a falha do bloco. Talvez você devesse ter elaborado para dizer aos membros como o DeCaf que atrack
mensagem é enviada para sua rotina de tratamento de erros personalizada que permite depurar seu código. Parece que ele não entendeu sua lógica.