Há alguns anos, não consigo obter uma resposta decente para a seguinte pergunta: por que alguns desenvolvedores são tão contra as exceções verificadas? Tive várias conversas, li coisas em blogs, li o que Bruce Eckel tinha a dizer (a primeira pessoa que vi falar contra eles).
Atualmente, estou escrevendo um novo código e prestando muita atenção em como lido com exceções. Estou tentando ver o ponto de vista da multidão "não gostamos de exceções verificadas" e ainda não consigo vê-lo.
Toda conversa que tenho termina com a mesma pergunta sem resposta ... deixe-me configurá-la:
Em geral (de como o Java foi projetado),
Error
é para coisas que nunca devem ser capturadas (a VM tem alergia a amendoim e alguém deixou cair um pote de amendoim)RuntimeException
é para coisas que o programador fez de errado (o programador saiu do final de uma matriz)Exception
(excetoRuntimeException
) é para itens fora do controle do programador (o disco é preenchido durante a gravação no sistema de arquivos, o limite de manipulação de arquivos para o processo foi atingido e você não pode abrir mais arquivos)Throwable
é simplesmente o pai de todos os tipos de exceção.
Um argumento comum que ouço é que, se uma exceção acontecer, tudo o que o desenvolvedor fará será sair do programa.
Outro argumento comum que ouço é que as exceções verificadas dificultam a refatoração do código.
Para o argumento "tudo o que vou fazer é sair", digo que, mesmo se você estiver saindo, precisará exibir uma mensagem de erro razoável. Se você está apenas tentando manipular erros, seus usuários não ficarão muito felizes quando o programa terminar sem uma indicação clara do motivo.
Para a multidão "dificulta a refatoração", isso indica que o nível adequado de abstração não foi escolhido. Em vez de declarar que um método lança um IOException
, ele IOException
deve ser transformado em uma exceção mais adequada ao que está acontecendo.
Não tenho problema em agrupar Main com catch(Exception)
(ou, em alguns casos, catch(Throwable)
para garantir que o programa possa sair normalmente - mas eu sempre pego as exceções específicas que eu preciso.) Isso me permite, no mínimo, exibir um mensagem de erro.
A pergunta que as pessoas nunca respondem é a seguinte:
Se você joga
RuntimeException
subclasses em vez deException
subclasses, como sabe o que deve capturar?
Se a resposta for verdadeira Exception
, você também estará lidando com erros de programador da mesma maneira que as exceções do sistema. Isso parece errado para mim.
Se você capturar Throwable
, estará tratando exceções do sistema e erros de VM (e similares) da mesma maneira. Isso parece errado para mim.
Se a resposta é que você captura apenas as exceções que você sabe que são lançadas, como você sabe quais são lançadas? O que acontece quando o programador X lança uma nova exceção e esquece de capturá-la? Isso me parece muito perigoso.
Eu diria que um programa que exibe um rastreamento de pilha está errado. As pessoas que não gostam de exceções verificadas não se sentem assim?
Portanto, se você não gosta de exceções verificadas, pode explicar por que não AND e responder à pergunta que não foi respondida, por favor?
Edit: Eu não estou procurando conselhos sobre quando usar qualquer um dos modelos, o que eu estou procurando é por que as pessoas se estendem RuntimeException
porque não gostam de se estender Exception
e / ou por que capturam uma exceção e, em seguida, repetem uma RuntimeException
vez em vez de adicionar lances a o método deles. Quero entender a motivação para não gostar de exceções verificadas.
fonte
Respostas:
Acho que li a mesma entrevista de Bruce Eckel que você fez - e sempre me incomodou. De fato, o argumento foi feito pelo entrevistado (se esse é realmente o post de que você está falando) Anders Hejlsberg, o gênio da MS por trás do .NET e C #.
Embora eu seja fã de Hejlsberg e de seu trabalho, esse argumento sempre me pareceu falso. Basicamente, tudo se resume a:
De "apresentado de outra forma ao usuário", quero dizer, se você usar uma exceção de tempo de execução, o programador preguiçoso a ignorará (em vez de capturá-lo com um bloco de captura vazio) e o usuário o verá.
O resumo do resumo do argumento é que "os programadores não os usarão corretamente e não usá-los adequadamente é pior do que não tê-los" .
Há alguma verdade nesse argumento e, de fato, suspeito que a motivação de Goslings por não colocar substituições de operadores em Java venha de um argumento semelhante - eles confundem o programador porque costumam ser abusados.
Mas, no final, acho que é um argumento falso de Hejlsberg e, possivelmente, um post-hoc criado para explicar a falta e não uma decisão bem pensada.
Eu diria que, embora o uso excessivo de exceções verificadas seja uma coisa ruim e tende a levar ao manuseio desleixado dos usuários, mas o uso adequado delas permite que o programador de API ofereça grandes benefícios ao programador de cliente da API.
Agora o programador da API deve ter cuidado para não lançar exceções verificadas em todo o lugar, ou elas simplesmente irritarão o programador cliente. O programador cliente muito preguiçoso recorrerá
(Exception) {}
como Hejlsberg avisa e todos os benefícios serão perdidos e o inferno acontecerá. Mas, em algumas circunstâncias, simplesmente não há substituto para uma boa exceção verificada.Para mim, o exemplo clássico é a API de abertura de arquivo. Toda linguagem de programação no histórico de linguagens (pelo menos nos sistemas de arquivos) possui uma API em algum lugar que permite abrir um arquivo. E todo programador cliente que usa essa API sabe que precisa lidar com o caso de o arquivo que eles estão tentando abrir não existir. Permita-me reformular: Todo programador cliente que usa essa API deve saber que precisa lidar com esse caso. E há o problema: o programador de API pode ajudá-lo a saber que deve lidar com isso apenas comentando ou pode realmente insistir que o cliente lide com ele.
Em C, o idioma é algo como
onde
fopen
indica falha retornando 0 e C (tolamente) permite tratar 0 como um booleano e ... Basicamente, você aprende esse idioma e está bem. Mas e se você é um noob e não aprendeu o idioma. Então, é claro, você começa come aprenda da maneira mais difícil.
Observe que estamos falando apenas de linguagens fortemente tipadas aqui: Há uma idéia clara do que é uma API em uma linguagem fortemente tipada: É um monte de funcionalidades (métodos) para você usar com um protocolo claramente definido para cada uma.
Esse protocolo claramente definido é normalmente definido por uma assinatura de método. Aqui fopen requer que você passe uma string (ou um caractere * no caso de C). Se você fornecer outra coisa, receberá um erro em tempo de compilação. Você não seguiu o protocolo - não está usando a API corretamente.
Em alguns idiomas (obscuros), o tipo de retorno também faz parte do protocolo. Se você tentar chamar o equivalente
fopen()
em alguns idiomas sem atribuí-lo a uma variável, também receberá um erro em tempo de compilação (você só pode fazer isso com funções void).O ponto que estou tentando enfatizar é o seguinte: em uma linguagem de tipo estatístico, o programador da API incentiva o cliente a usar a API corretamente, impedindo a compilação do código do cliente, se cometer algum erro óbvio.
(Em uma linguagem de tipo dinâmico, como Ruby, você pode passar qualquer coisa, digamos, um float, como o nome do arquivo - e ele será compilado. Por que incomodar o usuário com exceções verificadas, se você não quer mesmo controlar os argumentos do método. argumentos apresentados aqui se aplicam somente a idiomas de tipo estaticamente.)
Então, e as exceções verificadas?
Bem, aqui está uma das APIs Java que você pode usar para abrir um arquivo.
Vê aquela pegadinha? Aqui está a assinatura para esse método de API:
Observe que
FileNotFoundException
é uma exceção verificada .O programador da API está dizendo isso para você: "Você pode usar esse construtor para criar um novo FileInputStream, mas você
a) deve passar o nome do arquivo como uma String
b) deve aceitar a possibilidade de o arquivo não ser encontrado em tempo de execução "
E esse é o ponto todo, no que me diz respeito.
A chave é basicamente o que a pergunta afirma como "Coisas que estão fora do controle do programador". Meu primeiro pensamento foi que ele / ela significa coisas que estão fora da API controle de programadores de . Mas, de fato, as exceções verificadas quando usadas corretamente devem realmente estar fora do controle do programador do cliente e do programador da API. Eu acho que essa é a chave para não abusar das exceções verificadas.
Eu acho que o arquivo aberto ilustra bem o ponto. O programador da API sabe que você pode atribuir a eles um nome de arquivo que não existe no momento em que a API é chamada e que eles não poderão retornar o que você queria, mas terão de lançar uma exceção. Eles também sabem que isso acontecerá com bastante regularidade e que o programador cliente pode esperar que o nome do arquivo esteja correto no momento em que gravou a chamada, mas pode estar errado no tempo de execução por razões além do controle deles.
Portanto, a API torna explícito: haverá casos em que esse arquivo não existe no momento em que você me liga e você teve muito melhor que lidar com isso.
Isso seria mais claro com um contra-caso. Imagine que estou escrevendo uma API de tabela. Eu tenho o modelo de tabela em algum lugar com uma API, incluindo este método:
Agora, como programador de API, sei que haverá casos em que algum cliente passa com um valor negativo para a linha ou um valor de linha fora da tabela. Portanto, posso ficar tentado a lançar uma exceção verificada e forçar o cliente a lidar com isso:
(Eu realmente não chamaria isso de "Marcado", é claro).)
Esse é um mau uso das exceções verificadas. O código do cliente estará cheio de chamadas para buscar dados de linha, cada um dos quais precisará usar uma tentativa / captura, e para quê? Eles vão relatar ao usuário que a linha errada foi procurada? Provavelmente não - porque, seja qual for a interface do usuário em torno da exibição da minha tabela, não deve permitir que o usuário entre em um estado em que uma linha ilegal esteja sendo solicitada. Portanto, é um erro da parte do programador cliente.
O programador da API ainda pode prever que o cliente codificará esses erros e deve lidar com isso com uma exceção de tempo de execução como uma
IllegalArgumentException
.Com uma exceção marcada
getRowData
, este é claramente um caso que levará o programador preguiçoso de Hejlsberg a simplesmente adicionar capturas vazias. Quando isso acontece, os valores ilegais da linha não serão óbvios nem mesmo para o testador ou para a depuração do desenvolvedor do cliente, mas levarão a erros indiretos difíceis de identificar a origem. Os foguetes Arianne explodirão após o lançamento.Ok, então aqui está o problema: estou dizendo que a exceção verificada
FileNotFoundException
não é apenas uma coisa boa, mas uma ferramenta essencial na caixa de ferramentas dos programadores de API para definir a API da maneira mais útil para o programador cliente. Mas issoCheckedInvalidRowNumberException
é um grande inconveniente, levando a uma programação ruim e deve ser evitado. Mas como saber a diferença.Eu acho que não é uma ciência exata e acho que isso está por trás e talvez justifique até certo ponto o argumento de Hejlsberg. Mas não estou feliz em jogar o bebê fora com a água do banho aqui, então permita-me extrair algumas regras aqui para distinguir boas exceções verificadas de ruins:
Fora do controle do cliente ou Fechado x Aberto:
As exceções verificadas devem ser usadas apenas quando o caso de erro estiver fora de controle da API e do programador do cliente. Isso tem a ver com o quão aberto ou fechado o sistema é. Em uma interface de usuário restrita em que o programador cliente tem controle, digamos, de todos os botões, comandos de teclado etc. que adicionam e excluem linhas da exibição de tabela (um sistema fechado), é um erro de programação do cliente se tentar buscar dados de uma linha inexistente. Em um sistema operacional baseado em arquivo no qual qualquer número de usuários / aplicativos pode adicionar e excluir arquivos (um sistema aberto), é concebível que o arquivo que o cliente está solicitando tenha sido excluído sem o conhecimento deles, portanto, espera-se que eles lidem com ele .
Ubiquidade:
As exceções marcadas não devem ser usadas em uma chamada de API feita frequentemente pelo cliente. Com frequência, quero dizer de muitos lugares no código do cliente - não com frequência no tempo. Portanto, um código de cliente não tende a abrir muito o mesmo arquivo, mas minha exibição de tabela se
RowData
espalha por diferentes métodos. Em particular, eu vou escrever um monte de código comoe será doloroso ter que tentar sempre tentar / capturar.
Informando o Usuário:
As exceções marcadas devem ser usadas nos casos em que você pode imaginar uma mensagem de erro útil sendo apresentada ao usuário final. Este é o "e o que você fará quando isso acontecer?" questão que levantei acima. Ele também se refere ao item 1. Como você pode prever que algo fora do sistema da API do cliente pode fazer com que o arquivo não esteja lá, você pode razoavelmente informar o usuário sobre ele:
Como o número da sua linha ilegal foi causado por um bug interno e não foi culpa do usuário, não há realmente nenhuma informação útil que você possa fornecer. Se seu aplicativo não permitir que exceções de tempo de execução cheguem ao console, provavelmente acabará dando a eles uma mensagem feia como:
Em resumo, se você acha que seu programador cliente pode explicar sua exceção de uma maneira que ajude o usuário, provavelmente não deve estar usando uma exceção marcada.
Então essas são minhas regras. Um pouco artificial, e sem dúvida haverá exceções (por favor, ajude-me a refiná-las, se quiser). Mas meu argumento principal é que há casos em
FileNotFoundException
que a exceção verificada é uma parte tão importante e útil do contrato da API quanto os tipos de parâmetro. Portanto, não devemos dispensá-lo apenas porque é mal utilizado.Desculpe, não pretendia fazer isso tão longo e waffly. Deixe-me terminar com duas sugestões:
R: Programadores de API: use as exceções verificadas com moderação para preservar sua utilidade. Em caso de dúvida, use uma exceção desmarcada.
B: Programadores clientes: adquira o hábito de criar uma exceção agrupada (pesquise no google) logo no início do seu desenvolvimento. O JDK 1.4 e posterior fornecem um construtor
RuntimeException
para isso, mas você também pode criar o seu próprio facilmente. Aqui está o construtor:Em seguida, adquira o hábito de sempre que precisar lidar com uma exceção verificada e se sentir preguiçoso (ou você acha que o programador da API estava muito zeloso ao usar a exceção verificada em primeiro lugar), não apenas engula a exceção, envolva-a e repita o processo.
Coloque isso em um dos pequenos modelos de código do seu IDE e use-o quando estiver com preguiça. Dessa forma, se você realmente precisar lidar com a exceção verificada, será forçado a voltar e lidar com ela depois de ver o problema em tempo de execução. Porque, acredite em mim (e em Anders Hejlsberg), você nunca mais voltará a esse TODO em seu
fonte
O problema das exceções verificadas é que elas não são realmente exceções pelo entendimento usual do conceito. Em vez disso, eles são valores de retorno alternativos da API.
A idéia geral das exceções é que um erro lançado em algum lugar da cadeia de chamadas pode surgir e ser tratado pelo código em algum lugar mais adiante, sem que o código intermediário precise se preocupar com isso. As exceções verificadas, por outro lado, exigem que todos os níveis de código entre o lançador e o coletor declarem que conhecem todas as formas de exceção que podem passar por eles. Na prática, isso é realmente pouco diferente do que se as exceções verificadas fossem simplesmente valores de retorno especiais que o chamador tinha que verificar. por exemplo, [pseudocódigo]:
Como o Java não pode executar valores de retorno alternativos ou tuplas simples em linha como valores de retorno, as exceções verificadas são uma resposta razoável.
O problema é que muitos códigos, incluindo grandes faixas da biblioteca padrão, usam mal as exceções verificadas para condições excepcionais reais que você pode querer recuperar vários níveis. Por que o IOException não é um RuntimeException? Em qualquer outro idioma, posso permitir que uma exceção de E / S aconteça e, se eu não fizer nada para lidar com isso, meu aplicativo será interrompido e receberei um rastreamento útil da pilha. Esta é a melhor coisa que pode acontecer.
Talvez dois métodos acima do exemplo que você deseja capturar todas as IOExceptions de todo o processo de gravação em fluxo, abortem o processo e saltem para o código de relatório de erros; em Java, você não pode fazer isso sem adicionar 'throws IOException' em todos os níveis de chamada, mesmo níveis que não fazem IO. Esses métodos não precisam saber sobre o tratamento de exceções; tendo que adicionar exceções às suas assinaturas:
E há muitas exceções ridículas da biblioteca, como:
Quando você precisa desordenar seu código com coisas ridículas como essa, não é de admirar que as exceções verificadas recebam um monte de ódio, mesmo que, na verdade, esse seja apenas um projeto pobre de API.
Outro efeito ruim é Inversão de Controle, onde o componente A fornece um retorno de chamada para o componente genérico B. O componente A deseja permitir que uma exceção seja lançada do retorno de chamada para o local onde chamou o componente B, mas não pode porque isso alteraria a interface de retorno de chamada que é corrigida por B. A pode fazê-lo apenas envolvendo a exceção real em um RuntimeException, que é ainda mais um clichê de manipulação de exceção para gravar.
As exceções verificadas conforme implementadas em Java e sua biblioteca padrão significam clichê, clichê, clichê. Em uma linguagem já detalhada, isso não é uma vitória.
fonte
Em vez de refazer todas as (muitas) razões contra exceções verificadas, selecionarei apenas uma. Perdi a conta do número de vezes que escrevi esse bloco de código:
99% do tempo eu não posso fazer nada sobre isso. Finalmente, os blocos fazem qualquer limpeza necessária (ou pelo menos deveriam).
Também perdi a conta do número de vezes que vi isso:
Por quê? Porque alguém tinha que lidar com isso e era preguiçoso. Isso estava errado? Certo. Isso acontece? Absolutamente. E se essa fosse uma exceção desmarcada? O aplicativo teria acabado de morrer (o que é preferível a engolir uma exceção).
E então temos um código irritante que usa exceções como uma forma de controle de fluxo, como o java.text.Format . Bzzzt. Errado. Um usuário que coloca "abc" em um campo numérico em um formulário não é uma exceção.
Ok, acho que foram três razões.
fonte
Sei que essa é uma pergunta antiga, mas passei um tempo lutando com exceções verificadas e tenho algo a acrescentar. Por favor, perdoe-me pelo comprimento!
Minha carne principal com exceções verificadas é que elas arruinam o polimorfismo. É impossível fazê-los jogar bem com interfaces polimórficas.
Pegue a boa e velha
List
interface Java . Temos implementações comuns na memória comoArrayList
eLinkedList
. Também temos a classe esquelética,AbstractList
que facilita o design de novos tipos de lista. Para uma lista somente leitura, precisamos implementar apenas dois métodos:size()
eget(int index)
.Esta
WidgetList
classe de exemplo lê alguns objetos de tamanho fixo do tipoWidget
(não mostrados) de um arquivo:Ao expor os Widgets usando a
List
interface familiar , você pode recuperar itens (list.get(123)
) ou iterar uma lista (for (Widget w : list) ...
) sem precisar saber sobreWidgetList
si mesmo. Pode-se passar essa lista para qualquer método padrão que use listas genéricas ou envolvê-la em umCollections.synchronizedList
. O código que o utiliza não precisa saber nem se importar se os "Widgets" são feitos no local, provêm de uma matriz ou são lidos de um arquivo ou banco de dados, ou de toda a rede ou de um futuro relé de subespaço. Ainda funcionará corretamente porque aList
interface foi implementada corretamente.Exceto que não é. A classe acima não é compilada porque os métodos de acesso a arquivos podem
IOException
gerar uma exceção verificada que você precisa "capturar ou especificar". Você não pode especificá-lo como lançado - o compilador não permitirá, porque isso violaria o contrato daList
interface. E não há nenhuma maneira útil deWidgetList
lidar com a exceção (como explicarei mais adiante).Aparentemente, a única coisa a fazer é capturar e repetir exceções verificadas como uma exceção não verificada:
((Edit: Java 8 adicionou uma
UncheckedIOException
classe exatamente para este caso: para capturar e refazerIOException
s através dos limites do método polimórfico. Meio que prova meu argumento!))Portanto, as exceções verificadas simplesmente não funcionam em casos como este. Você não pode jogá-los. O mesmo vale para um inteligente
Map
apoiado por um banco de dados ou uma implementação dejava.util.Random
conectado a uma fonte de entropia quântica por meio de uma porta COM. Assim que você tenta fazer algo novo com a implementação de uma interface polimórfica, o conceito de exceções verificadas falha. Mas as exceções verificadas são tão traiçoeiras que ainda não o deixam em paz, porque você ainda precisa capturar e refazer qualquer método de nível inferior, bagunçando o código e bagunçando o rastreamento da pilha.Acho que a
Runnable
interface onipresente é frequentemente apoiada nesse canto, se chamar algo que gera exceções verificadas. Ele não pode lançar a exceção como está, então tudo o que pode fazer é desordenar o código capturando e repetindo novamente como aRuntimeException
.Na verdade, você pode lançar exceções verificadas não declaradas se recorrer a hacks. A JVM, em tempo de execução, não se importa com regras de exceção verificadas, portanto, precisamos enganar apenas o compilador. A maneira mais fácil de fazer isso é abusar dos genéricos. Este é o meu método para ele (nome da classe mostrado porque (antes do Java 8) é necessário na sintaxe de chamada do método genérico):
Viva! Usando isso, podemos lançar uma exceção marcada em qualquer profundidade da pilha sem declará-la, sem agrupá-la em
RuntimeException
e sem bagunçar o rastreamento da pilha! Usando o exemplo "WidgetList" novamente:Infelizmente, o insulto final das exceções verificadas é que o compilador se recusa a permitir que você capture uma exceção verificada se, na sua opinião falsa, não puder ter sido lançada. (Exceções não verificadas não possuem essa regra.) Para capturar a exceção lançada sorrateiramente, precisamos fazer o seguinte:
Isso é um pouco estranho, mas no lado positivo, ainda é um pouco mais simples que o código para extrair uma exceção verificada envolvida em a
RuntimeException
.Felizmente, a
throw t;
declaração é legal aqui, apesar de o tipo de tert
sido verificado, graças a uma regra adicionada no Java 7 sobre a repetição de exceções capturadas.Quando as exceções verificadas atendem ao polimorfismo, o caso oposto também é um problema: quando um método é especificado como potencialmente lançando uma exceção verificada, mas uma implementação substituída não. Por exemplo, a classe abstrata
OutputStream
éwrite
todos os métodos especificarthrows IOException
.ByteArrayOutputStream
é uma subclasse que grava em uma matriz na memória em vez de uma fonte de E / S verdadeira. Seuswrite
métodos substituídos não podem causarIOException
s, portanto, eles não têmthrows
cláusula, e você pode chamá-los sem se preocupar com o requisito de pegar ou especificar.Exceto nem sempre. Suponha que
Widget
tenha um método para salvá-lo em um fluxo:Declarar esse método como simples
OutputStream
é a coisa certa a ser feita, para que possa ser usado polimorficamente com todos os tipos de saídas: arquivos, bancos de dados, rede e assim por diante. E matrizes na memória. Com uma matriz na memória, no entanto, existe um requisito falso para lidar com uma exceção que não pode realmente acontecer:Como sempre, exceções verificadas atrapalham. Se suas variáveis forem declaradas como um tipo de base que possui mais requisitos de exceção em aberto, você precisará adicionar manipuladores para essas exceções, mesmo que saiba que elas não ocorrerão no seu aplicativo.
Mas espere, as exceções verificadas são realmente tão irritantes que nem permitem que você faça o contrário! Imagine que você atualmente captura alguma chamada
IOException
lançada porwrite
umOutputStream
, mas deseja alterar o tipo declarado da variável para aByteArrayOutputStream
, o compilador irá repreendê-lo por tentar capturar uma exceção verificada que diz não poder ser lançada.Essa regra causa alguns problemas absurdos. Por exemplo, um dos três
write
métodos de nãoOutputStream
é substituído por . Especificamente, é um método de conveniência que grava a matriz completa chamando com um deslocamento de 0 e o comprimento da matriz. substitui o método de três argumentos, mas herda o método de conveniência de um argumento como está. O método herdado faz exatamente a coisa certa, mas inclui uma cláusula indesejada . Talvez isso tenha sido um descuido no design , mas eles nunca podem corrigi-lo, porque isso quebraria a compatibilidade do código-fonte com qualquer código que captura a exceção - a exceção que nunca, nunca, nunca e nunca será lançada!ByteArrayOutputStream
write(byte[] data)
write(byte[] data, int offset, int length)
ByteArrayOutputStream
throws
ByteArrayOutputStream
Essa regra também é irritante durante a edição e a depuração. Por exemplo, às vezes comentarei temporariamente uma chamada de método e, se ela pudesse gerar uma exceção verificada, o compilador agora reclamará da existência do local
try
e doscatch
blocos. Portanto, também tenho que comentar isso e, agora, ao editar o código, o IDE recuará para o nível errado, porque os comentários{
e}
serão comentados. Gah! É uma reclamação pequena, mas parece que a única coisa que as exceções verificadas fazem é causar problemas.Estou quase terminando. Minha frustração final com exceções verificadas é que, na maioria dos sites de chamadas , não há nada útil que você possa fazer com eles. Idealmente, quando algo der errado, teríamos um manipulador específico de aplicativo competente que possa informar o usuário sobre o problema e / ou encerrar ou tentar novamente a operação conforme apropriado. Somente um manipulador no topo da pilha pode fazer isso porque é o único que conhece o objetivo geral.
Em vez disso, obtemos o seguinte idioma, que é galopante como uma maneira de calar o compilador:
Em uma GUI ou programa automatizado, a mensagem impressa não será vista. Pior, ele continua com o restante do código após a exceção. A exceção não é realmente um erro? Então não imprima. Caso contrário, algo mais irá explodir em um momento, quando o objeto de exceção original desaparecerá. Esse idioma não é melhor do que o BASIC
On Error Resume Next
ou o PHPerror_reporting(0);
.Chamar algum tipo de classe de logger não é muito melhor:
Isso é tão preguiçoso quanto
e.printStackTrace();
e ainda segue o código em um estado indeterminado. Além disso, a escolha de um sistema de log específico ou de outro manipulador é específica da aplicação, portanto isso prejudica a reutilização do código.Mas espere! Existe uma maneira fácil e universal de encontrar o manipulador específico do aplicativo. É mais alto na pilha de chamadas (ou é definido como o manipulador de exceções não capturado do Thread ). Portanto, na maioria dos lugares, tudo o que você precisa fazer é lançar a exceção mais acima na pilha . Por exemplo
throw e;
,. As exceções verificadas apenas atrapalham.Tenho certeza de que as exceções verificadas soaram como uma boa idéia quando o idioma foi projetado, mas, na prática, achei que elas eram todas incomodadas e sem benefícios.
fonte
RuntimeException
. Observe que uma rotina pode ser declarada simultaneamentethrows IOException
e ainda assim especificar que qualquerIOException
lançamento de uma chamada aninhada deve ser considerada inesperada e quebrada.if (false)
para isso. Isso evita o problema da cláusula throw e o aviso me ajuda a navegar de volta mais rapidamente. +++ Dito isto, concordo com tudo o que você escreveu. As exceções marcadas têm algum valor, mas esse valor é insignificante quando comparado ao seu custo. Quase sempre eles apenas atrapalham.Bem, não se trata de exibir um rastreamento de pilha ou travar silenciosamente. É sobre ser capaz de comunicar erros entre as camadas.
O problema das exceções verificadas é que elas incentivam as pessoas a engolir detalhes importantes (a classe de exceção). Se você optar por não engolir esse detalhe, precisará adicionar declarações de lançamentos em todo o aplicativo. Isso significa 1) que um novo tipo de exceção afetará muitas assinaturas de funções e 2) você pode perder uma instância específica da exceção que realmente deseja capturar (por exemplo, abra um arquivo secundário para uma função que grava dados em um O arquivo secundário é opcional, portanto você pode ignorar seus erros, mas como a assinatura
throws IOException
é fácil ignorar isso).Atualmente, estou lidando com essa situação em um aplicativo. Reempacotamos quase exceções como AppSpecificException. Isso tornou as assinaturas realmente limpas e não precisamos nos preocupar em explodir
throws
em assinaturas.Obviamente, agora precisamos especializar o tratamento de erros nos níveis mais altos, implementando a lógica de repetição e outras coisas. Tudo é AppSpecificException, no entanto, portanto, não podemos dizer "Se uma IOException for lançada, tente novamente" ou "Se ClassNotFound for lançada, aborte completamente". Não temos uma maneira confiável de obter a exceção real, porque as coisas são reembaladas repetidamente à medida que passam entre nosso código e o código de terceiros.
É por isso que sou um grande fã do tratamento de exceções em python. Você pode capturar apenas o que deseja e / ou pode manipular. Todo o resto borbulha como se você o tivesse redesenhado (o que você fez de qualquer maneira).
Descobri várias vezes, e ao longo do projeto que mencionei, que o tratamento de exceções se enquadra em 3 categorias:
fonte
throws
declarações.SNR
Em primeiro lugar, as exceções verificadas diminuem a "relação sinal / ruído" do código. Anders Hejlsberg também fala sobre programação imperativa versus programação declarativa, que é um conceito semelhante. De qualquer forma, considere os seguintes trechos de código:
Atualizar a interface do usuário a partir de um thread que não seja da interface do usuário em Java
Atualizar a interface do usuário a partir de um thread que não seja da interface do usuário em c #:
O que parece muito mais claro para mim. Quando você começa a trabalhar cada vez mais na interface do usuário no Swing, as exceções verificadas começam a se tornar realmente irritantes e inúteis.
Jail Break
Para implementar até as implementações mais básicas, como a interface de lista do Java, as exceções verificadas como uma ferramenta para design por contrato caem. Considere uma lista apoiada por um banco de dados ou sistema de arquivos ou qualquer outra implementação que gere uma exceção verificada. A única implementação possível é capturar a exceção verificada e repeti-la novamente como uma exceção não verificada:
E agora você tem que perguntar qual é o sentido de todo esse código? As exceções verificadas apenas adicionam ruído, a exceção foi capturada, mas não tratada, e o design por contrato (em termos de exceções verificadas) foi quebrado.
Conclusão
Eu escrevi sobre isso anteriormente .
fonte
Artima publicou uma entrevista com um dos arquitetos do .NET, Anders Hejlsberg, que cobre de maneira aguda os argumentos contra exceções verificadas. Uma prova curta:
fonte
Inicialmente, concordei com você, pois sempre fui a favor de exceções verificadas e comecei a pensar por que não gosto de não ter verificado exceções no .Net. Mas então percebi que não considero exceções verificadas.
Para responder à sua pergunta, sim, eu gosto que meus programas mostrem rastreamentos de pilha, de preferência muito feios. Eu quero que o aplicativo exploda em um monte horrível das mensagens de erro mais feias que você possa ver.
E a razão é porque, se isso acontecer, tenho que consertar e preciso consertar imediatamente. Quero saber imediatamente que há um problema.
Quantas vezes você realmente lida com exceções? Não estou falando em capturar exceções - estou falando em lidar com elas? É muito fácil escrever o seguinte:
E eu sei que você pode dizer que é uma prática ruim e que 'a resposta' é fazer algo com a exceção (deixe-me adivinhar, registrá-lo?), Mas no Mundo Real (tm), a maioria dos programadores simplesmente não faz isso. isto.
Então, sim, não quero capturar exceções, se não for necessário, e quero que meu programa exploda espetacularmente quando eu estragar tudo. Falha silenciosa é o pior resultado possível.
fonte
RuntimeExceptions
que você realmente não precisa entender, e eu concordo que você deve deixar o programa explodir. As exceções que você sempre deve capturar e manipular são as exceções verificadas, comoIOException
. Se você receber umIOException
, não há nada para corrigir no seu código; seu programa não deve explodir apenas porque havia um problema de rede.O artigo Exceções eficazes de Java explica bem quando usar desmarcadas e quando usar exceções verificadas. Aqui estão algumas citações desse artigo para destacar os principais pontos:
(Como o SO não permite tabelas, você pode ler o seguinte na página original ...)
fonte
args[]
? Eles são uma contingência ou uma falha?File#openInputStream
retornarEither<InputStream, Problem>
- se é isso que você quer dizer, então podemos concordar.Em resumo:
Exceções são uma pergunta de design da API. -- Nem mais nem menos.
O argumento para exceções verificadas:
Para entender por que as exceções verificadas podem não ser boas, vamos mudar a questão e perguntar: Quando ou por que as exceções verificadas são atraentes, ou seja, por que você deseja que o compilador imponha a declaração de exceções?
A resposta é óbvia: às vezes você precisa capturar uma exceção, e isso só é possível se o código chamado oferecer uma classe de exceção específica para o erro no qual você está interessado.
Portanto, o argumento para exceções verificadas é que o compilador força os programadores a declarar quais exceções são lançadas e, esperançosamente, o programador também documentará classes de exceção específicas e os erros que as causam.
Na realidade, porém, muitas vezes um pacote
com.acme
apenas lançaAcmeException
subclasses em vez de específicas. Os chamadores precisam manipular, declarar ou re-sinalizarAcmeExceptions
, mas ainda não podem ter certeza seAcmeFileNotFoundError
aconteceu ou nãoAcmePermissionDeniedError
.Portanto, se você estiver interessado apenas em um
AcmeFileNotFoundError
, a solução é registrar uma solicitação de recurso com os programadores da ACME e instruí-los a implementar, declarar e documentar essa subclasse deAcmeException
.Então, por que se preocupar?
Portanto, mesmo com exceções verificadas, o compilador não pode forçar os programadores a lançar exceções úteis . Ainda é apenas uma questão de qualidade da API.
Como resultado, os idiomas sem exceções verificadas geralmente não se saem muito pior. Os programadores podem ficar tentados a lançar instâncias inespecíficas de uma
Error
classe geral em vez de umaAcmeException
, mas se elas se preocupam com a qualidade da API, aprenderão a introduzir uma,AcmeFileNotFoundError
afinal.No geral, a especificação e documentação de exceções não são muito diferentes da especificação e documentação de, digamos, métodos comuns. Essas também são uma questão de design da API e, se um programador esquecer de implementar ou exportar um recurso útil, a API precisará ser aprimorada para que você possa trabalhar com ele de maneira útil.
Se você seguir essa linha de raciocínio, deve ser óbvio que o "incômodo" de declarar, capturar e relançar exceções tão comuns em linguagens como Java geralmente agrega pouco valor.
Também é importante notar que a Java VM não possui exceções verificadas - apenas o compilador Java as verifica e os arquivos de classe com declarações de exceção alteradas são compatíveis no tempo de execução. A segurança da Java VM não é aprimorada por exceções verificadas, apenas pelo estilo de codificação.
fonte
AcmeException
e não àAcmeFileNotFoundError
boa sorte para descobrir o que você fez de errado e onde você precisa pegá-lo. As exceções verificadas fornecem aos programadores um pouco de proteção contra o mau design da API.Eu tenho trabalhado com vários desenvolvedores nos últimos três anos em aplicativos relativamente complexos. Temos uma base de código que usa exceções verificadas com bastante frequência com o tratamento adequado de erros e outras que não.
Até agora, achei mais fácil trabalhar com a base de código com exceções verificadas. Quando estou usando a API de outra pessoa, é bom ver exatamente que tipo de condições de erro posso esperar quando chamo o código e manejo-o adequadamente, seja registrando, exibindo ou ignorando (Sim, existem casos válidos para ignorar exceções, como uma implementação do ClassLoader). Isso dá ao código que estou escrevendo uma oportunidade para recuperar. Todas as exceções de tempo de execução são propagadas até que sejam armazenadas em cache e tratadas com algum código genérico de tratamento de erros. Quando encontro uma exceção verificada que realmente não quero tratar em um nível específico ou que considero um erro de lógica de programação, envolvo-a em uma RuntimeException e deixo que ela borbulhe. Nunca, jamais, engula uma exceção sem uma boa razão (e boas razões para fazer isso são bastante escassas)
Quando trabalho com a base de código que não possui exceções verificadas, fica um pouco mais difícil saber de antemão o que posso esperar ao chamar a função, o que pode quebrar terrivelmente algumas coisas.
É claro que tudo isso é uma questão de preferência e habilidade do desenvolvedor. Ambas as formas de programação e manipulação de erros podem ser igualmente eficazes (ou ineficazes), então eu não diria que existe o caminho único.
No geral, acho mais fácil trabalhar com exceções verificadas, especialmente em grandes projetos com muitos desenvolvedores.
fonte
Categorias de exceção
Ao falar sobre exceções, sempre me refiro ao artigo de blog de Eric Lippert, Vexing exceptions . Ele coloca exceções nessas categorias:
OutOfMemoryError
ouThreadAbortException
.ArrayIndexOutOfBoundsException
,NullPointerException
ou qualquerIllegalArgumentException
.NumberFormatException
de emInteger.parseInt
vez de fornecer umInteger.tryParseInt
método que retorna um falso booleano na falha de análise.FileNotFoundException
,.Um usuário da API:
Exceções verificadas
O fato de o usuário da API precisar lidar com uma exceção específica faz parte do contrato do método entre o chamador e o chamado. O contrato especifica, entre outras coisas: o número e os tipos de argumentos que o receptor espera, o tipo de valor de retorno que o chamador pode esperar e as exceções que o chamador deve manipular .
Como as exceções vexatórias não devem existir em uma API, somente essas exceções exógenas devem ser verificadas para fazer parte do contrato do método. Relativamente poucas exceções são exógenas , portanto, qualquer API deve ter relativamente poucas exceções verificadas.
Uma exceção verificada é uma exceção que deve ser tratada . Lidar com uma exceção pode ser tão simples quanto engoli-la. Lá! A exceção é tratada. Período. Se o desenvolvedor quiser lidar com isso dessa maneira, tudo bem. Mas ele não pode ignorar a exceção e foi avisado.
Problemas de API
Mas qualquer API que tenha verificado vexações e exceções fatais (por exemplo, a JCL) sobrecarregará desnecessariamente os usuários da API. Tais exceções devem ser tratadas, mas a exceção é tão comum que não deveria ter sido uma exceção em primeiro lugar, ou nada pode ser feito ao manipulá-la. E isso faz com que os desenvolvedores Java detestem exceções verificadas.
Além disso, muitas APIs não possuem uma hierarquia de classe de exceção adequada, fazendo com que todos os tipos de exceção não exógena sejam representados por uma única classe de exceção verificada (por exemplo
IOException
). E isso também faz com que os desenvolvedores Java detestem exceções verificadas.Conclusão
Exceções exógenas são aquelas que não são culpa sua, não poderiam ter sido evitadas e devem ser tratadas. Eles formam um pequeno subconjunto de todas as exceções que podem ser lançadas. As APIs devem ter verificado apenas exceções exógenas e todas as outras exceções desmarcadas. Isso criará APIs melhores, sobrecarregará menos o usuário da API e, portanto, reduzirá a necessidade de capturar todas, engolir ou refazer exceções não verificadas.
Portanto, não odeie o Java e suas exceções verificadas. Em vez disso, odeie as APIs que usam demais exceções verificadas.
fonte
Ok ... As exceções marcadas não são ideais e têm alguma ressalva, mas elas servem a um propósito. Ao criar uma API, há casos específicos de falhas contratuais dessa API. Quando no contexto de uma linguagem fortemente tipada estaticamente, como Java, se não se usa exceções verificadas, deve-se confiar na documentação e na convenção ad-hoc para transmitir a possibilidade de erro. Isso remove todos os benefícios que o compilador pode trazer no tratamento de erros e você fica completamente à vontade dos programadores.
Portanto, remove-se a exceção Checked, como foi feito em C #, como então se pode transmitir programaticamente e estruturalmente a possibilidade de erro? Como informar o código do cliente que tais e tais erros podem ocorrer e devem ser tratados?
Eu ouço todo tipo de horror ao lidar com exceções verificadas, elas são mal utilizadas, isso é certo, mas também são exceções não verificadas. Eu digo: espere alguns anos quando as APIs estiverem empilhadas em várias camadas e você estará implorando pelo retorno de algum tipo de meio estruturado para transmitir falhas.
Tome o caso quando a exceção foi lançada em algum lugar na parte inferior das camadas da API e simplesmente borbulhou porque ninguém sabia que era possível que esse erro ocorresse, mesmo sendo um tipo de erro bastante plausível quando o código de chamada jogou-o (FileNotFoundException, por exemplo, em oposição a VogonsTrashingEarthExcept ... nesse caso, não importa se lidamos com isso ou não, pois não resta mais nada para lidar com isso).
Muitos argumentaram que não poder carregar o arquivo era quase sempre o fim do mundo para o processo e deve sofrer uma morte horrível e dolorosa. Então sim ... claro ... ok ... você cria uma API para alguma coisa e carrega o arquivo em algum momento ... Eu, como usuário da API, só posso responder ... "Quem diabos é você para decidir quando meu programa deve falhar! " Dado a opção em que as exceções são devoradas e não deixam rastro ou a EletroFlabbingChunkFluxManifoldChuggingException com um rastreamento de pilha mais profundo que a vala de Marianna, eu aceitaria a última sem muita hesitação, mas isso significa que é a maneira desejável de lidar com a exceção ? Não podemos estar em algum lugar no meio, onde a exceção seria reformulada e encapsulada cada vez que atravessasse um novo nível de abstração, para que realmente significasse alguma coisa?
Por fim, a maior parte do argumento que vejo é "Não quero lidar com exceções, muitas pessoas não querem lidar com exceções. Exceções verificadas me forçam a lidar com elas, portanto odeio exceções verificadas" Para eliminar esse mecanismo completamente e relegá-lo ao abismo de ir ao inferno é bobo e carece de jugamento e visão.
Se eliminarmos a exceção verificada, também poderemos eliminar o tipo de retorno para funções e sempre retornar uma variável "anytype" ... Isso tornaria a vida muito mais simples agora, não seria?
fonte
De fato, as exceções verificadas, por um lado, aumentam a robustez e a correção do seu programa (você é forçado a fazer declarações corretas de suas interfaces - as exceções que um método lança são basicamente um tipo de retorno especial). Por outro lado, você enfrenta o problema de que, como as exceções "surgem", muitas vezes é necessário alterar vários métodos (todos os chamadores e chamadores dos chamadores, etc.) ao alterar as exceções método joga.
As exceções verificadas em Java não resolvem o último problema; C # e VB.NET jogam fora o bebê com a água do banho.
Uma boa abordagem que segue o caminho intermediário é descrita neste documento do OOPSLA 2005 (ou no relatório técnico relacionado ).
Em resumo, permite que você diga:, o
method g(x) throws like f(x)
que significa que g lança todas as exceções f lançadas. Voila, verificou exceções sem o problema de alterações em cascata.Embora seja um artigo acadêmico, eu o encorajo a ler (partes dele), pois faz um bom trabalho em explicar quais são os benefícios e as desvantagens das exceções verificadas.
fonte
Este não é um argumento contra o conceito puro de exceções verificadas, mas a hierarquia de classes que Java usa para eles é um show de horrores. Sempre chamamos as coisas simplesmente de "exceções" - o que é correto, porque a especificação da linguagem as chama também - mas como uma exceção é nomeada e representada no sistema de tipos?
Pela classe que
Exception
se imagina? Bem, não, porqueException
s são exceções, e da mesma forma sãoException
s, exceto as exceções que não sãoException
s, porque outras exceções são na verdadeError
s, que são o outro tipo de exceção, um tipo de exceção extra-excepcional que nunca deve acontecer, exceto quando isso acontece, e que você nunca deve capturar, exceto às vezes você precisa. Exceto que isso não é tudo, porque você também pode definir outras exceções que não sãoException
nemError
s, mas meramenteThrowable
exceções.Quais destas são as exceções "marcadas"?
Throwable
s são exceções verificadas, exceto se também sãoError
s, que são exceções não verificadas, e há osException
s, que também sãoThrowable
s, e são o principal tipo de exceção verificada, exceto que também há uma exceção, se eles também sãoRuntimeException
s, porque esse é o outro tipo de exceção desmarcada.Para que servem
RuntimeException
s? Bem, assim como o nome indica, são exceções, como todos osException
s, e acontecem em tempo de execução, como todas as exceções, na verdade, exceto queRuntimeException
s são excepcionais em comparação com outros tempos de execuçãoException
porque não deveriam acontecer, exceto quando você comete algum erro bobo, emboraRuntimeException
s nunca sejaError
s, então são para coisas que são excepcionalmente errôneas, mas que na verdade não sãoError
s. Exceto porRuntimeErrorException
, que realmente é umRuntimeException
paraError
s. Mas todas as exceções não devem representar circunstâncias errôneas, afinal? Sim todos eles. Exceto porThreadDeath
uma exceção excepcionalmente excepcional, pois a documentação explica que é uma "ocorrência normal" e que "Error
De qualquer forma, como estamos dividindo todas as exceções no meio em
Error
s (que são para exceções de execução excepcionais, portanto desmarcadas)Exception
es (que são para erros de execução menos excepcionais, então verificados, exceto quando não existem), agora precisamos de dois tipos diferentes de cada uma das várias exceções. Por isso, precisamosIllegalAccessError
eIllegalAccessException
eInstantiationError
eInstantiationException
eNoSuchFieldError
eNoSuchFieldException
eNoSuchMethodError
eNoSuchMethodException
eZipError
eZipException
.Exceto que, mesmo quando uma exceção é marcada, sempre existem maneiras (bastante fáceis) de enganar o compilador e lançá-lo sem que ele seja verificado. Se você fizer isso, poderá obter um
UndeclaredThrowableException
, exceto em outros casos, onde ele poderá aparecer como umUnexpectedException
, ou umUnknownException
(que não está relacionadoUnknownError
, o que é apenas para "sérias exceções"), ou umExecutionException
, ou umInvocationTargetException
, ou umExceptionInInitializerError
.Ah, e não devemos esquecer o novo snazzy do Java 8
UncheckedIOException
, que é umaRuntimeException
exceção projetada para permitir que você jogue o conceito de verificação de exceção pela janela, envolvendoIOException
exceções verificadas causadas por erros de E / S (que não causamIOError
exceções, embora isso exista também) que são excepcionalmente difíceis de manusear e, portanto, é necessário que eles não sejam verificados.Obrigado Java!
fonte
O problema
O pior problema que vejo com o mecanismo de manipulação de exceção é que ele introduz duplicação de código em grande escala ! Sejamos honestos: na maioria dos projetos, em 95% das vezes, tudo o que os desenvolvedores realmente precisam fazer com exceção é comunicá-lo de alguma forma ao usuário (e, em alguns casos, também à equipe de desenvolvimento, por exemplo, enviando um e -mail com o rastreamento de pilha). Geralmente, a mesma linha / bloco de código é usada em todos os lugares em que a exceção é tratada.
Vamos supor que façamos log simples em cada bloco catch para algum tipo de exceção verificada:
Se for uma exceção comum, pode haver várias centenas desses blocos try-catch em uma base de código maior. Agora, vamos supor que precisamos introduzir um tratamento de exceção baseado em diálogo pop-up em vez do log do console ou começar a enviar um email adicional para a equipe de desenvolvimento.
Espere um momento ... vamos realmente editar todas essas centenas de locais no código ?! Você entendeu meu ponto :-).
A solução
O que fizemos para resolver esse problema foi a introdução do conceito de manipuladores de exceção (aos quais eu irei me referir mais adiante como EH) para centralizar o tratamento de exceções. Para toda classe que precisa manipular exceções, uma instância do manipulador de exceções é injetada por nossa estrutura de Injeção de Dependências . Portanto, o padrão típico de tratamento de exceções agora se parece com isso:
Agora, para personalizar nosso tratamento de exceções, precisamos alterar apenas o código em um único local (código EH).
Obviamente, para casos mais complexos, podemos implementar várias subclasses de EHs e aproveitar os recursos que nossa estrutura de DI nos fornece. Ao alterar nossa configuração da estrutura de DI, podemos facilmente mudar a implementação do EH globalmente ou fornecer implementações específicas do EH para classes com necessidades especiais de tratamento de exceções (por exemplo, usando a anotação Guice @Named).
Dessa forma, podemos diferenciar o comportamento de manipulação de exceções no desenvolvimento e liberar a versão do aplicativo (por exemplo, desenvolvimento - registrando o erro e interrompendo o aplicativo, produzindo o log com mais detalhes e deixando o aplicativo continuar sua execução) sem nenhum esforço.
Última coisa
Por último, mas não menos importante, pode parecer que o mesmo tipo de centralização pode ser obtido apenas passando nossas exceções "para cima" até que cheguem a alguma classe de tratamento de exceção de nível superior. Mas isso leva à confusão de códigos e assinaturas de nossos métodos e introduz problemas de manutenção mencionados por outras pessoas neste segmento.
fonte
catch
blocos nesse projeto real faz algo realmente "útil" com as exceções? 10% seria bom. Problemas comuns que geram exceções são como tentar ler a configuração de um arquivo que não existe, OutOfMemoryErrors, NullPointerExceptions, erros de integridade de restrição de banco de dados, etc. etc. Você está realmente tentando se recuperar de todos eles? Eu não acredito em você :). Muitas vezes, simplesmente não há como se recuperar.Anders fala sobre as armadilhas das exceções verificadas e por que as deixou de fora do C # no episódio 97 do rádio Engenharia de Software.
fonte
Minha redação no c2.com ainda permanece praticamente inalterada em relação à sua forma original: CheckedExceptionsAreIncompatibleWithVisitorPattern
Em suma:
O Visitor Pattern e seus parentes são uma classe de interfaces em que o chamador indireto e a implementação da interface conhecem uma exceção, mas a interface e o chamador direto formam uma biblioteca que não pode saber.
A suposição fundamental de CheckedExceptions é que todas as exceções declaradas podem ser lançadas a partir de qualquer ponto que chama um método com essa declaração. O VisitorPattern revela que esta suposição está com defeito.
O resultado final das exceções verificadas em casos como esses é um monte de código inútil que essencialmente remove a restrição de exceção verificada do compilador no tempo de execução.
Quanto ao problema subjacente:
Minha ideia geral é que o manipulador de nível superior precisa interpretar a exceção e exibir uma mensagem de erro apropriada. Quase sempre vejo exceções de E / S, exceções de comunicação (por algum motivo, as APIs distinguem) ou erros fatais de tarefas (erros de programa ou problema grave no servidor de backup), portanto, isso não deve ser muito difícil se permitirmos um rastreamento de pilha para um problema grave. problema no servidor.
fonte
Para tentar abordar apenas a pergunta não respondida:
A questão contém raciocínio ilusório IMHO. Só porque a API diz o que lança, não significa que você lida com ela da mesma maneira em todos os casos. Em outras palavras, as exceções que você precisa capturar variam dependendo do contexto em que você usa o componente que está lançando a exceção.
Por exemplo:
Se estou escrevendo um testador de conexão para um banco de dados ou algo para verificar a validade de um usuário digitado no XPath, provavelmente gostaria de capturar e relatar todas as exceções verificadas e não verificadas lançadas pela operação.
Se, no entanto, estiver escrevendo um mecanismo de processamento, provavelmente tratarei uma XPathException (marcada) da mesma maneira que um NPE: deixaria que ela fosse executada até a parte superior do encadeamento de trabalho, ignoraria o restante desse lote, registre o problema (ou envie-o para um departamento de suporte para diagnóstico) e deixe um feedback para o usuário entrar em contato com o suporte.
fonte
Este artigo é a melhor parte de texto sobre manipulação de exceções em Java que eu já li.
Favorece desmarcadas as exceções verificadas, mas essa opção é explicada minuciosamente e com base em argumentos fortes.
Eu não quero citar muito do conteúdo do artigo aqui (é melhor lê-lo como um todo), mas ele cobre a maioria dos argumentos de defensores de exceções não verificadas desse segmento. Especialmente esse argumento (que parece bastante popular) é abordado:
O autor "respostas":
E o principal pensamento ou artigo é:
Portanto, se " ninguém sabia que era possível que esse erro ocorresse ", há algo errado com esse projeto. Essa exceção deve ser tratada pelo menos pelo manipulador de exceções mais genérico (por exemplo, aquele que lida com todas as exceções não tratadas por manipuladores mais específicos), como sugere o autor.
Tão triste que poucas pessoas parecem descobrir este ótimo artigo :-(. Eu recomendo sinceramente a todos que hesitam em qual abordagem é melhor levar algum tempo e lê-la.
fonte
As exceções verificadas foram, em sua forma original, uma tentativa de lidar com contingências ao invés de falhas. O objetivo louvável era destacar pontos previsíveis específicos (não foi possível conectar, arquivo não encontrado etc.) e garantir que os desenvolvedores os tratassem.
O que nunca foi incluído no conceito original foi forçar uma vasta gama de falhas sistêmicas e irrecuperáveis a serem declaradas. Essas falhas nunca foram corretas para serem declaradas como exceções verificadas.
Geralmente, as falhas são possíveis no código, e os contêineres EJB, web e Swing / AWT já atendem a isso, fornecendo um manipulador de exceção de "solicitação falhada" mais externa. A estratégia correta mais básica é reverter a transação e retornar um erro.
Um ponto crucial é que o tempo de execução e as exceções verificadas são funcionalmente equivalentes. Não há manipulação ou recuperação que exceções verificadas possam fazer, que exceções de tempo de execução não podem.
O maior argumento contra as exceções "verificadas" é que a maioria das exceções não pode ser corrigida. O simples fato é que não possuímos o código / subsistema que quebrou. Não podemos ver a implementação, não somos responsáveis por ela e não podemos corrigi-la.
Se nosso aplicativo não for um banco de dados ... não devemos tentar consertá-lo. Isso violaria o princípio do encapsulamento .
Particularmente problemáticas foram as áreas de JDBC (SQLException) e RMI para EJB (RemoteException). Em vez de identificar contingências corrigíveis de acordo com o conceito original de "exceção verificada", esses problemas de confiabilidade sistêmica generalizada forçada, que na verdade não são corrigíveis, devem ser amplamente declarados.
A outra falha grave no design do Java era que o tratamento de exceções deveria ser colocado corretamente no nível "comercial" ou "solicitação" mais alto possível. O princípio aqui é "jogue cedo, pegue tarde". As exceções verificadas fazem pouco, mas atrapalham isso.
Temos um problema óbvio em Java de exigir milhares de blocos try-catch do-nothing, com uma proporção significativa (40% +) sendo codificada incorretamente. Quase nenhum deles implementa qualquer manipulação ou confiabilidade genuína, mas impõe uma grande sobrecarga de codificação.
Por fim, as "exceções verificadas" são praticamente incompatíveis com a programação funcional do FP.
Sua insistência em "manipular imediatamente" está em desacordo com as melhores práticas de manipulação de exceções "atrasadas" e com qualquer estrutura de FP que abstraia laços / ou fluxo de controle.
Muitas pessoas falam sobre "manipular" exceções verificadas, mas estão falando com seus próprios chapéus. Continuar após uma falha com dados nulos, incompletos ou incorretos para fingir sucesso não está lidando com nada. É uma má prática de engenharia / confiabilidade da forma mais baixa.
Se falhar corretamente, é a estratégia correta mais básica para lidar com uma exceção. Revertendo a transação, registrando o erro e relatando uma resposta de "falha" ao usuário são boas práticas - e o mais importante, evitam que dados comerciais incorretos sejam comprometidos no banco de dados.
Outras estratégias para o tratamento de exceções são "repetir", "reconectar" ou "ignorar" no nível de negócios, subsistema ou solicitação. Todas essas são estratégias gerais de confiabilidade e funcionam bem / melhor com exceções de tempo de execução.
Por fim, é muito preferível falhar do que executar com dados incorretos. A continuação causará erros secundários, distantes da causa original e mais difíceis de depurar; ou eventualmente resultará na confirmação de dados incorretos. As pessoas são demitidas por isso.
Consulte:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/
fonte
Como as pessoas já declararam, as exceções verificadas não existem no bytecode Java. Eles são simplesmente um mecanismo de compilador, não muito diferente de outras verificações de sintaxe. Eu vejo exceções verificadas muito como eu vejo o compilador reclamando sobre uma condicional redundante:
if(true) { a; } b;
. Isso é útil, mas eu posso ter feito isso de propósito, então deixe-me ignorar seus avisos.O fato é que você não será capaz de forçar todos os programadores a "fazer a coisa certa" se aplicar exceções verificadas e todos os outros forem danos colaterais que apenas o odeiam pela regra que você fez.
Corrija os programas ruins por aí! Não tente consertar o idioma para não permitir! Para a maioria das pessoas, "fazer algo sobre uma exceção" é realmente apenas dizer ao usuário sobre isso. Também posso informar ao usuário sobre uma exceção não verificada, portanto, mantenha suas classes de exceção verificadas fora da minha API.
fonte
Um problema com exceções verificadas é que as exceções geralmente são anexadas aos métodos de uma interface se pelo menos uma implementação dessa interface a usar.
Outro problema com exceções verificadas é que elas tendem a ser mal utilizadas. O exemplo perfeito disso está em
java.sql.Connection
'sclose()
método. Pode gerar umSQLException
, mesmo que você já tenha declarado explicitamente que terminou a conexão. Quais informações podem ser fechadas () com o que você se importa?Normalmente, quando fecho () uma conexão
*
, é algo como isto:Além disso, não me inicie nos vários métodos de análise e NumberFormatException ... O TryParse do .NET, que não gera exceções, é muito mais fácil de usar, é doloroso ter que voltar ao Java (usamos Java e C # onde eu trabalho).
*
Como um comentário adicional, o Connection.close () de PooledConnection nem fecha uma conexão, mas você ainda precisa capturar o SQLException por ser uma exceção verificada.fonte
O programador precisa conhecer todas as exceções que um método pode lançar, para usá-lo corretamente. Portanto, bater na cabeça dele com apenas algumas exceções não ajuda necessariamente um programador descuidado a evitar erros.
O pequeno benefício é superado pelo custo oneroso (especialmente em bases de código maiores e menos flexíveis, em que a modificação constante das assinaturas da interface não é prática).
A análise estática pode ser agradável, mas a análise estática verdadeiramente confiável geralmente exige inflexivelmente o trabalho estrito do programador. Há um cálculo de custo-benefício, e a barra precisa ser definida como alta para uma verificação que leva a um erro de tempo de compilação. Seria mais útil se o IDE assumisse o papel de comunicar quais exceções um método pode lançar (incluindo quais são inevitáveis). Embora talvez não seja tão confiável sem declarações de exceção forçadas, a maioria das exceções ainda será declarada na documentação, e a confiabilidade de um aviso de IDE não é tão crucial.
fonte
Penso que esta é uma excelente pergunta e nada argumentativa. Eu acho que as bibliotecas de terceiros devem (em geral) lançar exceções não verificadas . Isso significa que você pode isolar suas dependências na biblioteca (ou seja, você não precisa repetir suas exceções ou lançar
Exception
- geralmente práticas ruins). A camada DAO do Spring é um excelente exemplo disso.Por outro lado, as exceções do núcleo API Java deve ser geral na verificado se eles poderiam nunca ser tratada. Pegue
FileNotFoundException
ou (meu favorito)InterruptedException
. Essas condições quase sempre devem ser tratadas especificamente (ou seja, sua reação a umInterruptedException
não é a mesma que sua reação a umIllegalArgumentException
). O fato de suas exceções serem verificadas força os desenvolvedores a pensar se uma condição é manipulável ou não. (Dito isto, raramente viInterruptedException
manipulados adequadamente!)Mais uma coisa -
RuntimeException
nem sempre é "onde um desenvolvedor tem algo errado". Uma exceção de argumento ilegal é lançada quando você tenta criar umenum
usovalueOf
e não existeenum
esse nome. Isso não é necessariamente um erro do desenvolvedor!fonte
enum
nomes de membros, simplesmente porque eles usamenum
objetos. Portanto, um nome errado só pode vir de fora, seja um arquivo de importação ou o que for. Uma maneira possível de lidar com esses nomes é ligarMyEnum#valueOf
e pegar o IAE. Outra maneira é usar um pré-preenchidoMap<String, MyEnum>
, mas esses são detalhes de implementação.Aqui está um argumento contra exceções verificadas (de joelonsoftware.com):
fonte
goto
palavra - chave. Com um loop, você pode ver a chave de fechamento ou a palavra-chavebreak
oucontinue
. Todos eles saltam para um ponto no método atual. Mas você não pode sempre ver athrow
, porque muitas vezes não é no método atual, mas em outro método que ele chama (possivelmente indiretamente.)As vantagens comprovam que a exceção verificada não é necessária são:
Fiquei feliz se o java me desse uma escolha do que usar ao trabalhar com bibliotecas principais, como E / S. O Like fornece duas cópias das mesmas classes - uma empacotada com RuntimeEception. Então podemos comparar o que as pessoas usariam . Por enquanto, porém, é melhor que muitas pessoas optem por alguma estrutura no topo do java, ou linguagem diferente. Como Scala, JRuby tanto faz. Muitos acreditam que o SUN estava certo.
fonte
RuntimeException
com uma exceção interna apropriada). É lamentável que seja mais conciso ter o método externothrows
uma exceção do método interno, do que envoltar exceções do método interno, quando o último curso de ação seria mais correto.Uma coisa importante que ninguém mencionou é como ela interfere nas interfaces e nas expressões lambda.
Digamos que você defina a
MyAppException extends Exception
. É a exceção de nível superior herdada por todas as exceções lançadas pelo seu aplicativo. Todo método declarathrows MyAppException
que é um pouco de nuissance, mas gerenciável. Um manipulador de exceção registra a exceção e notifica o usuário de alguma forma.Tudo parece bom até que você queira implementar alguma interface que não seja sua. Obviamente, ele não declara a intenção de lançar
MyApException
, portanto, o compilador não permite que você lance a exceção a partir daí.No entanto, se a sua exceção for estendida
RuntimeException
, não haverá problemas com as interfaces. Você pode mencionar voluntariamente a exceção no JavaDoc, se desejar. Mas, além disso, ele simplesmente muda de forma silenciosa através de qualquer coisa, para ser capturado na sua camada de manipulação de exceções.fonte
Vimos algumas referências ao arquiteto chefe do C #.
Aqui está um ponto de vista alternativo de um desenvolvedor Java sobre quando usar exceções verificadas. Ele reconhece muitos dos negativos que outros mencionaram: Exceções eficazes
fonte
Eu li muito sobre o tratamento de exceções, mesmo que (na maioria das vezes) não consiga realmente dizer que estou feliz ou triste com a existência de exceções verificadas, esta é minha opinião: exceções verificadas no código de baixo nível (IO, rede , SO, etc) e exceções não verificadas em APIs de alto nível / nível de aplicativo.
Mesmo que não seja tão fácil traçar uma linha entre eles, acho realmente irritante / difícil integrar várias APIs / bibliotecas sob o mesmo teto, sem envolver o tempo todo muitas exceções verificadas, mas por outro lado, às vezes é útil / melhor ser forçado a capturar alguma exceção e fornecer uma diferente que faça mais sentido no contexto atual.
O projeto em que estou trabalhando pega muitas bibliotecas e as integra na mesma API, que é completamente baseada em exceções não verificadas.Este frameworks fornece uma API de alto nível que no começo estava cheia de exceções verificadas e tinha apenas várias não verificadas exceções (exceção de inicialização, exceção de configuração, etc) e devo dizer que não foi muito amigável . Na maioria das vezes, era necessário capturar ou repetir exceções que você não sabe como lidar ou que nem se importa (para não se confundir, você deve ignorar as exceções), especialmente no lado do cliente, onde um único o clique pode gerar 10 possíveis exceções (marcadas).
A versão atual (3ª) usa apenas exceções não verificadas e possui um manipulador de exceção global responsável por lidar com qualquer coisa não capturada. A API fornece uma maneira de registrar manipuladores de exceção, que decidem se uma exceção é considerada um erro (na maioria das vezes, esse é o caso), o que significa registrar e notificar alguém, ou pode significar outra coisa - como essa exceção, AbortException, o que significa interromper o encadeamento de execução atual e não registrar nenhum erro, porque não é desejado. Obviamente, para resolver todos os encadeamentos personalizados, é necessário manipular o método run () com uma tentativa {...} catch (all).
public void run () {
}
Isso não é necessário se você usar o WorkerService para agendar tarefas (Runnable, Callable, Worker), que lida com tudo para você.
Claro que essa é apenas a minha opinião, e pode não ser a correta, mas parece uma boa abordagem para mim. Verei depois que lançarei o projeto se o que acho que é bom para mim, é bom para os outros também ... :)
fonte