Existem muitos requisitos necessários para um sistema transmitir e tratar adequadamente exceções. Também existem muitas opções para escolher um idioma para implementar o conceito.
Requisitos para exceções (em nenhuma ordem específica):
Documentação : um idioma deve ter o significado de documentar exceções que uma API pode lançar. Idealmente, este meio de documentação deve ser utilizável em máquina para permitir que compiladores e IDEs forneçam suporte ao programador.
Transmitir situações excepcionais : Essa é óbvia, para permitir que uma função transmita situações que impedem a funcionalidade chamada de executar a ação esperada. Na minha opinião, existem três grandes categorias de tais situações:
2.1 Erros no código que fazem com que alguns dados sejam inválidos.
2.2 Problemas na configuração ou outros recursos externos.
2.3 Recursos inerentemente não confiáveis (rede, sistemas de arquivos, bancos de dados, usuários finais, etc.). Esse é um caso esquecido, uma vez que sua natureza não confiável deve nos fazer esperar suas falhas esporádicas. Nesse caso, essas situações devem ser consideradas excepcionais?
Forneça informações suficientes para o código lidar com isso : As exceções devem fornecer informações suficientes para o receptor, para que ele possa reagir e possivelmente lidar com a situação. as informações também devem ser suficientes para que, quando registradas, essas exceções forneçam contexto suficiente para um programador identificar e isolar as instruções incorretas e fornecer uma solução.
Forneça confiança ao programador sobre o status atual do estado de execução de seu código : Os recursos de tratamento de exceções de um sistema de software devem estar presentes o suficiente para fornecer as salvaguardas necessárias, mantendo-se fora do caminho do programador, para que ele possa se concentrar na tarefa em mão.
Para cobrir estes, os seguintes métodos foram implementados em vários idiomas:
Exceções verificadas Fornecem uma ótima maneira de documentar exceções e, teoricamente, quando implementadas corretamente, devem fornecer ampla garantia de que tudo está bom. No entanto, o custo é tal que muitos sentem mais produtivo simplesmente ignorar ou engolir exceções ou repeti-las como exceções não verificadas. Quando usadas exceções verificadas de maneira inadequada, perdem praticamente toda a sua utilidade. Além disso, as exceções verificadas dificultam a criação de uma API estável no tempo. As implementações de um sistema genérico em um domínio específico trarão uma carga de situação excepcional que seria difícil de manter usando apenas exceções verificadas.
Exceções não verificadas - muito mais versáteis que as exceções verificadas, elas não documentam adequadamente as possíveis situações excepcionais de uma determinada implementação. Eles contam com documentação ad-hoc, se houver. Isso cria situações em que a natureza não confiável de uma mídia é mascarada por uma API que dá a aparência de confiabilidade. Além disso, quando lançadas, essas exceções perdem o significado à medida que retornam pelas camadas de abstração. Como eles são mal documentados, um programador não pode direcioná-los especificamente e muitas vezes precisa lançar uma rede muito maior do que o necessário para garantir que os sistemas secundários, caso falhem, não derrubem todo o sistema. O que nos leva de volta ao problema da deglutição, com exceção das exceções fornecidas.
Tipos de retorno com várias etapas Aqui, você deve confiar em um conjunto separado, em uma tupla ou em outro conceito semelhante para retornar o resultado esperado ou um objeto que representa a exceção. Aqui, não há desenrolamento de pilha, nenhum código de corte, tudo é executado normalmente, mas o valor de retorno deve ser validado por erro antes de continuar. Eu realmente não trabalhei com isso ainda, portanto não posso comentar por experiência própria. Reconheço que resolve algumas exceções de problemas ignorando o fluxo normal, mas ainda assim sofrerá os mesmos problemas das exceções verificadas como cansativo e constantemente "na sua cara".
Então a questão é:
Qual é a sua experiência nesse assunto e qual, segundo você, é o melhor candidato para criar um bom sistema de tratamento de exceções para um idioma?
Edição: Poucos minutos depois de escrever esta pergunta me deparei com este post , assustador!
fonte
noexcept
história em C ++ pode render muito bons insights para EH em C # e Java também.)Respostas:
Nos primeiros dias do C ++, descobrimos que, sem algum tipo de programação genérica, linguagens fortemente tipadas eram extremamente difíceis de manejar. Também descobrimos que as exceções verificadas e a programação genérica não funcionavam bem juntas, e as exceções verificadas foram essencialmente abandonadas.
Os tipos de retorno multiset são ótimos, mas não substituem as exceções. Sem exceções, o código está cheio de ruído na verificação de erros.
O outro problema com exceções verificadas é que uma alteração nas exceções geradas por uma função de baixo nível força uma cascata de alterações em todos os chamadores e seus chamadores, e assim por diante. A única maneira de evitar isso é que cada nível de código capture quaisquer exceções geradas por níveis inferiores e envolva-as em uma nova exceção. Novamente, você acaba com um código muito barulhento.
fonte
Por um longo tempo nas linguagens OO, o uso de exceções tem sido o padrão de fato para erros de comunicação. Mas linguagens de programação funcionais oferecem a possibilidade de uma abordagem diferente, por exemplo, usando mônadas (que eu não uso) ou a "Programação Orientada a Ferrovias", mais leve, como descrito por Scott Wlaschin.
Em este post
Em esta apresentação no NDC 2014
É realmente uma variante do tipo de resultado com várias etapas.
O tipo de resultado pode ser declarado assim
Portanto, o resultado de uma função que retorna esse tipo seria um
Success
ou umFail
tipo. Não pode ser os dois.Em linguagens de programação orientadas mais imperativas, esse tipo de estilo pode exigir uma grande quantidade de código no site do chamador. Mas a programação funcional permite que você construa funções de ligação ou operadores para unir várias funções, para que a verificação de erros não ocupe metade do código. Como um exemplo:
A
updateUser
função chama cada uma dessas funções em sucessão e cada uma delas pode falhar. Se todos tiverem êxito, o resultado da última função chamada será retornado. Se uma das funções falhar, o resultado dessa função será o resultado daupdateUser
função geral . Tudo isso é tratado pelo operador >> = personalizado.No exemplo acima, os tipos de erro podem ser
Se o chamador de
updateUser
não manipular explicitamente todos os erros possíveis da função, o compilador emitirá um aviso. Então você tem tudo documentado.No Haskell, existe uma
do
notação que pode tornar o código ainda mais limpo.fonte
do
notação de Haskell , que torna o código resultante ainda mais limpo.Railway Oriented Programming
comportamento é exatamente monádico.Acho a resposta de Pete muito boa e gostaria de acrescentar alguma consideração e um exemplo. Uma discussão muito interessante sobre o uso de exceções versus o retorno de valores de erro especiais pode ser encontrada em Programação no padrão ML, por Robert Harper , no final da Seção 29.3, página 243, 244.
O problema é implementar uma função parcial
f
retornando um valor de algum tipot
. Uma solução é ter a função tipoe lance uma exceção quando não houver resultado possível. A segunda solução é implementar uma função com o tipo
e retorno
SOME v
sobre o sucesso eNONE
sobre o fracasso.Aqui está o texto do livro, com pequena adaptação feita por mim para tornar o texto mais geral (o livro se refere a um exemplo específico). O texto modificado é escrito em itálico .
Isso diz respeito à escolha entre exceções e tipos de retorno de opção.
Com relação à ideia de que representar um erro no tipo de retorno leva a verificações de erro espalhadas por todo o código: esse não precisa ser o caso. Aqui está um pequeno exemplo em Haskell que ilustra isso.
Suponha que desejemos analisar dois números e depois dividir o primeiro pelo segundo. Portanto, pode haver um erro ao analisar cada número ou ao dividir (divisão por zero). Portanto, temos que verificar se há um erro após cada etapa.
A análise e a divisão são realizadas no
let ...
bloco. Observe que, usando aMaybe
mônada e ado
notação, apenas o caminho de sucesso é especificado: a semântica daMaybe
mônada propaga implicitamente o valor do erro (Nothing
). Nenhuma sobrecarga para o programador.fonte
Either
tipo seria mais adequado. O que você faz se receberNothing
aqui? Você acabou de receber a mensagem "erro". Não é muito útil para depuração.Tornei-me um grande fã das exceções verificadas e gostaria de compartilhar minha regra geral sobre quando usá-las.
Cheguei à conclusão de que existem basicamente dois tipos de erros com os quais meu código precisa lidar. Há erros testáveis antes da execução do código e erros não testáveis antes da execução do código. Um exemplo simples para um erro que pode ser testado antes da execução do código em uma NullPointerException.
Um teste simples poderia ter evitado o erro, como ...
Há momentos na computação em que você pode executar 1 ou mais testes antes de executar o código para garantir sua segurança e você ainda terá uma exceção. Por exemplo, você pode testar um sistema de arquivos para garantir que haja espaço em disco suficiente no disco rígido antes de gravar seus dados na unidade. Em um sistema operacional de multiprocessamento, como os usados hoje, seu processo pode testar o espaço em disco e o sistema de arquivos retornará um valor dizendo que há espaço suficiente; uma alternância de contexto para outro processo poderá gravar os bytes restantes disponíveis para o sistema operacional. sistema. Quando o contexto do sistema operacional voltar ao processo em que você grava o conteúdo no disco, ocorrerá uma exceção simplesmente porque não há espaço em disco suficiente no sistema de arquivos.
Considero o cenário acima como um caso perfeito para uma exceção verificada. É uma exceção no código que força você a lidar com algo ruim, mesmo que seu código possa ser perfeitamente escrito. Se você optar por fazer coisas ruins como 'engolir a exceção', você é o mau programador. A propósito, eu encontrei casos em que é razoável engolir a exceção, mas por favor deixe um comentário no código sobre o motivo pelo qual a exceção foi engolida. O mecanismo de manipulação de exceção não é o culpado. Costumo brincar que prefiro que meu marcapasso cardíaco seja escrito com uma linguagem que tenha exceções verificadas.
Há momentos em que fica difícil decidir se o código é testável ou não. Por exemplo, se você estiver escrevendo um intérprete e uma SyntaxException for lançada quando o código falhar na execução por algum motivo sintático, a SyntaxException deve ser uma exceção verificada ou (em Java) uma RuntimeException? Eu responderia se o intérprete verificar a sintaxe do código antes que o código seja executado, a exceção deve ser uma RuntimeException. Se o intérprete simplesmente executar o código 'hot' e simplesmente encontrar um erro de sintaxe, eu diria que a exceção deve ser uma exceção verificada.
Admito que nem sempre estou feliz em ter que capturar ou lançar uma exceção verificada, porque há momentos em que não tenho certeza do que fazer. Exceções verificadas são uma maneira de forçar um programador a estar atento ao possível problema que pode ocorrer. Uma das razões pelas quais eu programa em Java é porque ele tem exceções verificadas.
fonte
Atualmente, estou no meio de um grande projeto / API baseado em OOP e usei esse layout das exceções. Mas tudo realmente depende de quão profundo você deseja ir com o tratamento de exceções e coisas do gênero.
ExpectedException
- AuthorisedException
- EmptySetException
- NoRemainingException
- NoRowsException
- NotFoundException
- ValidationException
UnexpectedException
- ConnectivityException
- EnvironmentException
- ProgrammerException
- SQLException
EXEMPLO
fonte
NAN
ouNULL
.