A maneira moderna de executar o tratamento de erros…


118

Estou ponderando esse problema há um tempo e me vejo continuamente encontrando advertências e contradições, por isso espero que alguém possa produzir uma conclusão para o seguinte:

Favorecer exceções sobre códigos de erro

Pelo que sei, ao trabalhar na indústria há quatro anos, lendo livros e blogs etc., a melhor prática atual para lidar com erros é lançar exceções, em vez de retornar códigos de erro (não necessariamente um código de erro, mas um tipo que representa um erro).

Mas - para mim isso parece contradizer ...

Codificação para interfaces, não implementações

Codificamos para interfaces ou abstrações para reduzir o acoplamento. Não sabemos, ou queremos saber, o tipo específico e a implementação de uma interface. Então, como podemos saber quais exceções devemos procurar capturar? A implementação pode gerar 10 exceções diferentes ou nenhuma. Quando pegamos uma exceção, certamente estamos fazendo suposições sobre a implementação?

A menos que - a interface tenha ...

Especificações de exceção

Algumas linguagens permitem que os desenvolvedores afirmem que certos métodos lançam certas exceções (Java, por exemplo, usa a throwspalavra - chave.) Do ponto de vista do código de chamada, isso parece bom - sabemos explicitamente quais exceções precisamos capturar.

Mas - isso parece sugerir uma ...

Abstração com vazamento

Por que uma interface deve especificar quais exceções podem ser lançadas? E se a implementação não precisar lançar uma exceção ou precisar lançar outras exceções? No nível da interface, não há como saber quais exceções uma implementação pode querer lançar.

Assim...

Concluir

Por que as exceções são preferidas quando parecem (aos meus olhos) contradizer as melhores práticas de software? E, se os códigos de erro são tão ruins (e eu não preciso ser vendido pelos vícios dos códigos de erro), existe outra alternativa? Qual é o estado da arte atual (ou em breve) para tratamento de erros que atende aos requisitos das melhores práticas descritas acima, mas não depende de código de chamada que verifique o valor de retorno dos códigos de erro?


12
Não entendo seu argumento sobre abstrações com vazamentos. A especificação de qual exceção um método específico pode gerar faz parte da especificação da interface . Assim como o tipo do valor de retorno faz parte da especificação da interface. As exceções simplesmente não "saem do lado esquerdo" da função, mas ainda fazem parte da interface.
Deceze

@ deceze Como uma interface pode indicar o que uma implementação pode lançar? Pode lançar todas as exceções no sistema de tipos! E que muitas línguas não suportam especificações de exceção sugerem que eles são belos desertor
RichK

1
Concordo que pode ser complicado gerenciar todas as diferentes exceções que um método pode lançar, se ele próprio usar outros métodos e não capturar suas exceções internamente. Dito isto, não é uma contradição.
Deceze

1
A suposição vazada aqui é que o código pode manipular uma exceção quando escapa do limite da interface. Isso é muito improvável, já que ele não sabe o suficiente sobre o que realmente deu errado. Tudo o que sabemos é que algo deu errado e a implementação da interface não sabia como lidar com isso. As chances de que ele possa fazer um trabalho melhor são baixas, além de denunciá-lo e encerrar o programa. Ou ignorá-lo se a interface não for crítica para a operação correta do programa. Um detalhe de implementação que você não pode e não deve codificar em um contrato de interface.
Hans Passant

Respostas:


31

Antes de tudo, discordo desta afirmação:

Favorecer exceções sobre códigos de erro

Nem sempre é esse o caso: por exemplo, dê uma olhada no Objective-C (com a estrutura da Foundation). Lá, o NSError é a maneira preferida de lidar com erros, apesar da existência do que um desenvolvedor Java chamaria de verdadeiras exceções: @try, @catch, @throw, classe NSException etc.

No entanto, é verdade que muitas interfaces vazam suas abstrações com as exceções lançadas. É minha convicção que isso não é culpa do estilo "exceção" da propagação / manipulação de erros. Em geral, acredito que o melhor conselho sobre o tratamento de erros é este:

Lide com o erro / exceção no nível mais baixo possível, período

Eu acho que se alguém seguir essa regra, a quantidade de "vazamento" das abstrações pode ser muito limitada e contida.

Se as exceções lançadas por um método devem fazer parte de sua declaração, acredito que deveriam: fazem parte do contrato definido por esta interface: Este método faz A ou falha com B ou C.

Por exemplo, se uma classe é um Analisador de XML, uma parte de seu design deve indicar que o arquivo XML fornecido está completamente errado. Em Java, você normalmente o faz declarando as exceções que espera encontrar e adicionando-as à throwsparte da declaração do método. Por outro lado, se um dos algoritmos de análise falhar, não há razão para passar essa exceção acima sem tratamento.

Tudo se resume a uma coisa: bom design de interface. Se você projetar sua interface bem o suficiente, nenhuma quantidade de exceções deverá assombrá-lo. Caso contrário, não são apenas as exceções que o incomodariam.

Além disso, acho que os criadores de Java tinham razões de segurança muito fortes para incluir exceções a uma declaração / definição de método.

Uma última coisa: algumas linguagens, Eiffel, por exemplo, têm outros mecanismos para tratamento de erros e simplesmente não incluem recursos de lançamento. Lá, uma 'exceção' de classificação é gerada automaticamente quando uma pós-condição para uma rotina não é satisfeita.


12
+1 por negar "Favorecer exceções a códigos de erro". Fui ensinado que as exceções são boas e boas, desde que sejam, de fato, exceções e não a regra. Chamar um método que lança uma exceção para determinar se uma condição é verdadeira ou não é uma prática extremamente ruim.
Neil

4
@ JoshuaDrake: Ele está definitivamente errado. Exceções e gotosão muito diferentes. Por exemplo, as exceções sempre vão na mesma direção - na pilha de chamadas. Em segundo lugar, proteger-se de exceções surpresas é exatamente a mesma prática que o DRY - por exemplo, em C ++, se você usa o RAII para garantir a limpeza, ele garante a limpeza em todos os casos, não apenas na exceção, mas também em todo o fluxo de controle normal. Isso é infinitamente mais confiável. try/finallyrealiza algo um pouco semelhante. Quando você garante a limpeza adequadamente, não precisa considerar as exceções como um caso especial.
DeadMG

4
@JoshuaDrake Desculpe, mas Joel está muito longe. Exceções não são as mesmas que ir, você sempre aumenta pelo menos um nível na pilha de chamadas. E o que você faz se o nível acima não puder lidar imediatamente com o código de erro? Retornar outro código de erro? Parte do problema do erro é que eles PODEM ser ignorados, levando a problemas piores do que o lançamento de uma exceção.
460 Andy

25
-1 porque eu discordo completamente de "Lidar com o erro / exceção no nível mais baixo possível" - isso é errado, ponto final. Lide com erros / exceções no nível apropriado . Muitas vezes, esse é um nível muito mais alto e, nesse caso, os códigos de erro são uma grande dor. O motivo para favorecer exceções em relação aos códigos de erro é que eles permitem que você escolha livremente em que nível lidar com eles sem afetar os níveis intermediários.
Michael Borgwardt

2
@Giorgio: ver artima.com/intv/handcuffs.html - especialmente página 2 e 3.
Michael Borgwardt

27

Gostaria apenas de observar que exceções e códigos de erro não são a única maneira de lidar com erros e caminhos de código alternativos.

Fora do topo da mente, você pode ter uma abordagem como a adotada por Haskell, na qual os erros podem ser sinalizados por meio de tipos de dados abstratos com vários construtores (pense em enumerações discriminadas ou ponteiros nulos, mas tipicamente seguros e com a possibilidade de adicionar sintaxe). funções sugar ou helper para que o fluxo do código pareça bom).

func x = do
    a <- operationThatMightFail 10
    b <- operationThatMightFail 20
    c <- operationThatMightFail 30
    return (a + b + c)

operationThatMightfail é uma função que retorna um valor envolvido em um Maybe. Funciona como um ponteiro anulável, mas a notação não garante que a coisa toda seja avaliada como nula se algum de a, b ou c falhar. (e o compilador protege você contra uma NullPointerException acidental)

Outra possibilidade é passar um objeto manipulador de erros como um argumento extra para todas as funções que você chama. Esse manipulador de erros possui um método para cada "exceção" possível que pode ser sinalizada pela função para a qual você a passa e pode ser usado por essa função para tratar as exceções onde elas ocorrem, sem necessariamente ter que retroceder a pilha por meio de exceções.

O LISP comum faz isso e torna possível ter suporte sintático (argumentos implícitos) e ter as funções internas seguindo este protocolo.


1
Isso é realmente legal. Obrigado por responder a parte sobre alternativas :)
RichK

1
BTW, exceções é uma das partes mais funcionais das linguagens imperativas modernas. Exceções formam uma mônada. Exceções usam correspondência de padrões. Exceções ajudam a imitar o estilo de aplicação sem realmente aprender o que Maybeé.
9000

Eu estava lendo um livro sobre SML recentemente. Mencionou tipos de opções, exceções (e continuações). O conselho era usar tipos de opção quando se espera que o caso indefinido ocorra com bastante frequência e usar exceções quando o caso indefinido ocorre muito raramente.
Giorgio

8

Sim, exceções podem causar abstrações com vazamento. Mas os códigos de erro não são ainda piores a esse respeito?

Uma maneira de lidar com esse problema é fazer com que a interface especifique exatamente quais exceções podem ser lançadas sob quais circunstâncias e declare que as implementações devem mapear seu modelo de exceção interno para essa especificação, capturando, convertendo e lançando exceções, se necessário. Se você deseja uma interface "perfeita", esse é o caminho a seguir.

Na prática, geralmente é suficiente especificar exceções que fazem parte da interface e que um cliente pode querer capturar e fazer alguma coisa. É geralmente entendido que pode haver outras exceções quando erros de baixo nível acontecem ou um erro se manifesta, e com o qual um cliente só pode lidar geralmente mostrando uma mensagem de erro e / ou desligando o aplicativo. Pelo menos a exceção ainda pode conter informações que ajudam a diagnosticar o problema.

De fato, com os códigos de erro, praticamente a mesma coisa acaba acontecendo, apenas de uma maneira mais implícita e com muito mais probabilidade de as informações serem perdidas e o aplicativo acabar em um estado inconsistente.


1
Não entendo por que um código de erro pode causar uma abstração com vazamento. Se um código de erro for retornado em vez de um valor de retorno normal e esse comportamento for descrito na especificação da função / método, o IMO não fará vazamento. Ou estou negligenciando alguma coisa?
Giorgio

Se o código de erro for específico da implementação enquanto a API for independente da implementação, a especificação e o retorno do código de erro vazarão detalhes indesejados da implementação. Exemplo típico: uma API de log com uma implementação baseada em arquivo e baseada em banco de dados, onde a primeira pode ter um erro "disco cheio" e a segunda, um erro "conexão com o banco de dados recusada pelo host".
Michael Borgwardt

Eu entendo o que você quer dizer. Se você deseja relatar erros específicos da implementação, a API não pode ser independente da implementação (nem com códigos de erro nem com exceções). Acho que a única solução seria definir um código de erro independente de implementação como "recurso não disponível" ou decidir que a API não é independente de implementação.
Giorgio

1
@Giorgio: Sim, relatar erros específicos da implementação é complicado com uma API independente da implementação. Ainda assim, você pode fazer isso com exceções, porque (diferentemente dos códigos de erro) eles podem ter vários campos. Portanto, você pode usar o tipo da exceção para fornecer as informações genéricas de erro (ResourceMissingException) e incluir um código / mensagem de erro específico da implementação como um campo. Melhor dos dois mundos :-).
Sleske

E BTW, isso é exatamente o que java.lang.SQLException faz. Possui getSQLState(genérico) e getErrorCode(específico do fornecedor). Agora, se só ele tinha subclasses apropriadas ...
sleske

5

Muitas coisas boas aqui, gostaria de acrescentar que todos devemos ter cuidado com o código que usa exceções como parte do fluxo de controle normal. Às vezes, as pessoas entram nessa armadilha onde qualquer coisa que não seja o caso usual se torna uma exceção. Eu até vi uma exceção usada como uma condição de terminação de loop.

Exceções significam que "algo que eu não posso lidar aqui aconteceu, precisa procurar alguém para descobrir o que fazer". Um usuário digitando entrada inválida não é uma exceção (que deve ser tratada localmente pela entrada, solicitando novamente etc.).

Outro caso degenerado de uso de exceção que já vi são pessoas cuja primeira resposta é "lançar uma exceção". Isso quase sempre é feito sem escrever a captura (regra geral: escreva a captura primeiro e depois a instrução throw). Em grandes aplicações, isso se torna problemático quando uma exceção não capturada surge das regiões inferiores e explode o programa.

Não sou anti-exceção, mas eles parecem singletons de alguns anos atrás: usados ​​com muita frequência e de forma inadequada. Eles são perfeitos para o uso pretendido, mas esse caso não é tão amplo quanto alguns pensam.


4

Abstração com vazamento

Por que uma interface deve especificar quais exceções podem ser lançadas? E se a implementação não precisar lançar uma exceção ou precisar lançar outras exceções? No nível da interface, não há como saber quais exceções uma implementação pode querer lançar.

Não. As especificações de exceção estão no mesmo bloco que os tipos de retorno e argumento - elas fazem parte da interface. Se você não puder estar em conformidade com essa especificação, não implemente a interface. Se você nunca jogar, tudo bem. Não há nada vazando sobre a especificação de exceções em uma interface.

Os códigos de erro estão além do ruim. Eles são terríveis. Você deve se lembrar de verificar e propagar manualmente, todas as vezes, para todas as chamadas. Isso viola o DRY, para começar, e explode massivamente o seu código de tratamento de erros. Essa repetição é um problema muito maior do que qualquer outro enfrentado por exceções. Você nunca pode ignorar silenciosamente uma exceção, mas as pessoas ignoram silenciosamente os códigos de retorno - definitivamente uma coisa ruim.


Os códigos de erro podem ser mais fáceis de usar se você tiver boas formas de açúcar sintático ou métodos auxiliares, e em alguns idiomas você pode garantir ao compilador e ao sistema de tipos que você nunca esquecerá de manipular um código de erro. Quanto à parte da interface de exceção, acho que ele estava pensando na notória confusão das exceções verificadas de Java. Embora pareçam uma ideia perfeitamente razoável à primeira vista, na prática, muitos problemas pequenos e dolorosos são causados.
Hugomg

@missingno: Isso porque, como sempre, o Java tem uma implementação terrível deles, não porque as exceções verificadas são inerentemente ruins.
DeadMG

1
@missingno: Que tipos de problemas podem ser causados ​​por exceções verificadas?
Giorgio

@ Giorgio: É muito difícil prever todo tipo de exceção que um método pode lançar, já que isso precisa levar em consideração outras subclasses e códigos que ainda precisam ser gravados. Na prática, as pessoas acabam com soluções feias que descartam informações, como reutilizar uma classe de exceções do sistema para tudo ou ter que capturar e repetir frequentemente exceções internas. Também ouvi dizer que as exceções verificadas foram um grande obstáculo ao tentar adicionar funções anônimas ao idioma.
Hugomg

@missingno: As funções anônimas AFAIK em Java são apenas açúcar sintático para classes internas anônimas com exatamente um método, por isso não sei se entendi por que as exceções verificadas seriam um problema (mas admito que não sei muito sobre o tópico). Sim, é difícil prever quais exceções um método lançará; é por isso que na IMO pode ser útil verificar exceções, para que você não precise adivinhar. É claro que você pode escrever um código ruim para lidar com eles, mas isso também pode ser feito com exceções não verificadas. No entanto, sei que o debate é bastante complexo e, honestamente, vejo prós e contras de ambos os lados.
Giorgio

2

O tratamento de exceção de poço pode ter sua própria implementação de interface. Dependendo do tipo de exceção lançada, execute as etapas desejadas.

A solução para o seu problema de design é ter duas implementações de interface / abstração. Um para a funcionalidade e outro para manipulação de exceções. E, dependendo do tipo de exceção capturada, chame a classe de tipo de exceção apropriada.

A implementação de códigos de erro é uma maneira ortodoxa de lidar com exceções. É como o uso de string vs. construtor de strings.


4
em outras palavras: implementações lançam subclasses das exceções definidas na API.
Andrew cooke

2

As exceções de IM-muito-HO devem ser julgadas caso a caso, porque, ao interromper o fluxo de controle, elas aumentarão a complexidade real e percebida do seu código, em muitos casos desnecessariamente. Deixando de lado a discussão relacionada ao lançamento de exceções dentro de suas funções - o que pode realmente melhorar seu fluxo de controle, se você quiser examinar exceções através dos limites de chamada, considere o seguinte:

Permitir que um chamado interrompa seu fluxo de controle pode não fornecer nenhum benefício real e não pode haver uma maneira significativa de lidar com a exceção. Para um exemplo direto, se alguém estiver implementando o padrão Observável (em um idioma como C # onde você tem eventos em todos os lugares e não é explícito throwsna definição), não há motivo real para permitir que o Observador interrompa o fluxo de controle se ele travar, e nenhuma maneira significativa de lidar com suas coisas (é claro, um bom vizinho não deve jogar ao observar, mas ninguém é perfeito).

A observação acima pode ser estendida a qualquer interface fracamente acoplada (como você apontou); Eu acho que é realmente uma norma que, após aumentar de 3 a 6 quadros de pilha, uma exceção não detectada provavelmente acabe em uma seção de código que:

  • é abstrato demais para lidar com a exceção de maneira significativa, mesmo que a própria exceção seja aumentada;
  • está executando uma função genérica (não se importava com o motivo da falha, como uma bomba de mensagens ou o observável);
  • é específico, mas com uma responsabilidade diferente, e realmente não deve se preocupar;

Considerando o exposto, decorar interfaces com throwssemântica é apenas um ganho funcional marginal, porque muitos chamadores através de contratos de interface só se importariam se você falhasse, não por quê.

Eu diria que isso se torna uma questão de gosto e conveniência: seu foco principal é recuperar graciosamente seu estado no chamador e no chamado após uma "exceção"; portanto, se você tiver muita experiência em mover códigos de erro (próximos de um plano de fundo C), ou se você estiver trabalhando em um ambiente em que as exceções podem se tornar más (C ++), não acredito que jogar coisas ao redor seja tão importante para uma OOP agradável e limpa que você não possa confiar no seu antigo padrões se você estiver desconfortável com isso. Especialmente se levar à quebra do SoC.

De uma perspectiva teórica, acho que uma maneira SoC-kosher de lidar com exceções pode ser derivada diretamente da observação de que na maioria das vezes o chamador direto só se importa com o que você falhou, e não por quê. O chamado, alguém muito próximo acima (2-3 quadros) captura uma versão otimizada, e a exceção real sempre é afundada em um manipulador de erros especializado (mesmo que apenas seja rastreado) - é aqui que a AOP seria útil, porque esses manipuladores provavelmente serão horizontais.


1

Favorecer exceções sobre códigos de erro

  • Ambos devem coexistir.

  • Retorne o código de erro quando você antecipar determinado comportamento.

  • Retorne a exceção quando você não antecipou algum comportamento.

  • Os códigos de erro são normalmente associados a uma única mensagem, quando o tipo de exceção permanece, mas uma mensagem pode variar

  • A exceção tem um rastreamento de pilha, quando o código de erro não. Não uso códigos de erro para depurar o sistema danificado.

Codificação para interfaces, não implementações

Isso pode ser específico para o JAVA, mas quando declaro minhas interfaces, não especifico quais exceções podem ser lançadas por uma implementação dessa interface, simplesmente não faz sentido.

Quando pegamos uma exceção, certamente estamos fazendo suposições sobre a implementação?

Isso depende inteiramente de você. Você pode tentar capturar um tipo muito específico de exceção e depois capturar um tipo mais geral Exception. Por que não deixar a exceção propagar a pilha e depois lidar com isso? Como alternativa, você pode observar a programação de aspectos em que o tratamento de exceções se torna um aspecto "conectável".

E se a implementação não precisar lançar uma exceção ou precisar lançar outras exceções?

Não entendo por que isso é um problema para você. Sim, você pode ter uma implementação que nunca falha ou gera exceções e uma implementação diferente que falha constantemente e gera exceção. Se for esse o caso, não especifique nenhuma exceção na interface e seu problema será resolvido.

Mudaria alguma coisa se, em vez de exceção, sua implementação retornasse um objeto de resultado? Este objeto conteria o resultado da sua ação, juntamente com quaisquer erros / falhas, se houver. Você pode então interrogar esse objeto.


1

Abstração com vazamento

Por que uma interface deve especificar quais exceções podem ser lançadas? E se a implementação não precisar lançar uma exceção ou precisar lançar outras exceções? No nível da interface, não há como saber quais exceções uma implementação pode querer lançar.

Na minha experiência, o código que recebe o erro (seja por exceção, código de erro ou qualquer outra coisa) normalmente não se importa com a causa exata do erro - reagiria da mesma maneira a qualquer falha, exceto por um possível relatório do erro (seja um diálogo de erro ou algum tipo de log); e esse relatório seria feito ortogonalmente ao código que chamou o procedimento com falha. Por exemplo, esse código pode passar o erro para outro trecho de código que sabe relatar erros específicos (por exemplo, formatar uma sequência de mensagens), possivelmente anexando algumas informações de contexto.

Obviamente, em alguns casos, é necessário anexar semânticas específicas a erros e reagir de maneira diferente com base em qual erro ocorreu. Esses casos devem ser documentados na especificação da interface. No entanto, a interface ainda pode se reservar o direito de lançar outras exceções sem significado específico.


1

Acho que as exceções permitem escrever um código mais estruturado e conciso para relatar e manipular erros: o uso de códigos de erro requer a verificação de valores de retorno após cada chamada e a decisão do que fazer em caso de resultado inesperado.

Por outro lado, concordo que as exceções revelam detalhes de implementação que devem estar ocultos no código que invoca uma interface. Como não é possível saber a priori qual parte do código pode lançar quais exceções (a menos que sejam declaradas na assinatura do método como em Java), usando exceções, estamos introduzindo dependências implícitas muito complexas entre diferentes partes do código, o que é contra o princípio de minimizar dependências.

Resumindo:

  • Acho que as exceções permitem um código mais limpo e uma abordagem mais agressiva para testar e depurar, porque as exceções não capturadas são muito mais visíveis e difíceis de ignorar do que os códigos de erro (falha em breve).
  • Por outro lado, erros de exceção não detectados que não são descobertos durante o teste podem aparecer em um ambiente de produção na forma de uma falha. Em certas circunstâncias, esse comportamento não é aceitável e, nesse caso, acho que usar códigos de erro é uma abordagem mais robusta.

1
Discordo. Sim, exceções não capturadas podem travar um aplicativo, mas códigos de erro não verificados também podem - por isso é uma lavagem. Se você usar as exceções corretamente, as únicas não capturadas serão as fatais (como OutOfMemory) e, para elas, travar imediatamente é o melhor que você pode fazer.
Sleske

O código de erro faz parte do contrato entre o chamador m1 e o chamado m2: os possíveis códigos de erro são definidos apenas na interface de m2. Com exceções (a menos que você esteja usando Java e declarando todas as exceções lançadas nas assinaturas de métodos), você tem um contrato implícito entre o chamador m1 e todos os métodos que podem ser chamados por m2, recursivamente. Portanto, é claro que é um erro não verificar um código de erro retornado, mas sempre é possível fazê-lo. Por outro lado, nem sempre é possível verificar todas as exceções geradas por um método, a menos que você saiba como ele é implementado.
Giorgio

Primeiro: Você pode checar todas as exceções - basta fazer o "manuseio de exceções de Pokemon" (precisa capturá-las todas - por exemplo, catch Exceptionou mesmo Throwableou equivalente).
Sleske

1
Na prática, se a API for projetada adequadamente, ela especificará todas as exceções com as quais o cliente pode lidar de maneira significativa - elas precisam ser capturadas especificamente. Estes são os equivalentes dos códigos de erro). Qualquer outra exceção significa "erro interno" e o aplicativo precisará desligar ou, pelo menos, desligar o subsistema correspondente. Você pode pegá-los se quiser (veja acima), mas geralmente deve deixá-los borbulhar. Que "borbulhar" é a principal vantagem do uso de exceções. Você ainda pode pegá-los mais longe, ou não, dependendo das necessidades.
Sleske

-1

Tendência à direita.

Não pode ser ignorado, deve ser tratado, é completamente transparente. E se você usar o tipo de erro canhoto correto, ele transmitirá todas as mesmas informações que uma exceção java.

Desvantagem? Código com manipulação de erro adequada parece nojento (verdadeiro para todos os mecanismos).


isso não parece oferecer nada substancial sobre os pontos apresentados e explicados nas 10 respostas anteriores. Por esbarrar questão de dois anos de idade com coisas assim
mosquito

Exceto que ninguém aqui mencionou o viés certo também. hugomg chegou perto de falar sobre haskell, no entanto, talvez seja um manipulador de erros de merda, pois não deixa explicações sobre por que o erro ocorreu, nem qualquer método direto com o qual se recuperar, e os retornos de chamada são um dos maiores pecados no design de fluxo de controle. E essa pergunta surgiu no google.
Keynan
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.