O caso contra exceções verificadas


456

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(exceto RuntimeException) é 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 IOExceptiondeve 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 de Exception 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 RuntimeExceptionporque não gostam de se estender Exceptione / ou por que capturam uma exceção e, em seguida, repetem uma RuntimeExceptionvez em vez de adicionar lances a o método deles. Quero entender a motivação para não gostar de exceções verificadas.


46
Eu não acho que seja completamente subjetivo - é um recurso de linguagem que foi projetado para ter um uso específico, em vez de todos decidirem o que é para si. E não é especialmente argumentativo, ele aborda antecipadamente refutações específicas que as pessoas poderiam facilmente ter apresentado.
5309 Gareth

6
Vamos. Visto como um recurso de idioma, este tópico foi e pode ser abordado de maneira objetiva.
Kurt Schelfthout 5/03/09

6
@cletus "respondendo sua própria pergunta" se eu tivesse a resposta, não teria feito a pergunta!
TofuBeer 05/03/09

8
Ótima pergunta. No C ++, não há exceções verificadas e, na minha opinião, torna o recurso de exceção inutilizável. Você acaba em uma situação em que precisa capturar todas as chamadas de funções que faz, porque simplesmente não sabe se isso pode gerar alguma coisa.
Dimitri C.

4
O argumento mais forte que conheço para exceções verificadas é que elas não estavam originalmente no Java e que, quando foram introduzidas, descobriram centenas de bugs no JDK. Isso é um pouco anterior ao Java 1.0. Pessoalmente, não ficaria sem eles e discordo violentamente de Bruce Eckel e de outros.
Marquês de Lorne #

Respostas:


277

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 #.

http://www.artima.com/intv/handcuffs.html

Embora eu seja fã de Hejlsberg e de seu trabalho, esse argumento sempre me pareceu falso. Basicamente, tudo se resume a:

"As exceções verificadas são ruins porque os programadores apenas as abusam, sempre as capturando e descartando, o que leva a problemas ocultos e ignorados que, de outra forma, seriam apresentados ao usuário".

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

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

onde fopenindica 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 com

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

e 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.

try {
  f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
  // deal with it. No really, deal with it!
  ... // this is me dealing with it
}

Vê aquela pegadinha? Aqui está a assinatura para esse método de API:

public FileInputStream(String name)
                throws FileNotFoundException

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:

public RowData getRowData(int row) 

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:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(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 FileNotFoundExceptionnã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 isso CheckedInvalidRowNumberExceptioné 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:

  1. 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 .

  2. 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 RowDataespalha por diferentes métodos. Em particular, eu vou escrever um monte de código como

    if (model.getRowData().getCell(0).isEmpty())

    e será doloroso ter que tentar sempre tentar / capturar.

  3. 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:

    "Error: could not find the file 'goodluckfindingthisfile'"

    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:

    "Internal error occured: IllegalArgumentException in ...."

    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 FileNotFoundExceptionque 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 RuntimeExceptionpara isso, mas você também pode criar o seu próprio facilmente. Aqui está o construtor:

public RuntimeException(Throwable cause)

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.

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

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

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}

110
A abertura de arquivos é realmente um bom exemplo de exceções verificadas contraproducentes. Porque na maioria das vezes o código que abre o arquivo NÃO pode fazer nada útil sobre a exceção - é melhor fazer várias camadas na pilha de chamadas. A exceção verificada obriga a desorganizar as assinaturas de método, para que você possa fazer o que faria de qualquer maneira - lide com a exceção no local em que faz mais sentido fazê-lo.
Michael Borgwardt

116
@ Michael: Concordo que você pode geralmente lidar com essas exceções IO vários níveis mais elevados - mas eu não ouvi-lo negando que como um cliente API que você tem de antecipar estes. Por esse motivo, acho que as exceções verificadas são apropriadas. Sim, você precisará declarar "throws" em cada método acima da pilha de chamadas, mas eu discordo que isso seja confuso. São informações declarativas úteis nas assinaturas de seu método. Você está dizendo: meus métodos de baixo nível podem ser executados em um arquivo ausente, mas eles deixarão a manipulação dele para você. Não vejo nada a perder e apenas código bom e limpo a ser ganho #
Rhubarb

23
@ Ruibarbo: +1, resposta muito interessante, mostra argumentos para os dois lados. Ótimo. Sobre o seu último comentário: observe que nem sempre é possível declarar arremessos em cada método na pilha de chamadas, especialmente se você estiver implementando uma interface ao longo do caminho.
R. Martinho Fernandes

8
Esse é um argumento muito forte (e eu concordo com ele em geral), mas há um caso em que não funciona: retornos de chamada genéricos. Se eu tiver uma biblioteca que chame algum código definido pelo usuário, não há como (que eu saiba, em java) propagação de exceções verificadas do código de usuário que chama para o código de usuário que chama. Esse problema também se estende a outras partes do sistema de tipos de muitas linguagens estaticamente tipadas que não oferecem suporte a tipos genéricos apropriados. Essa resposta seria melhorada com uma menção a isso (e talvez uma resposta).
Mankarse

7
@Rhubarb errado. As exceções verificadas destinavam-se a 'contingências' que poderiam e deveriam ser tratadas, mas sempre eram incompatíveis com as melhores práticas de "jogar cedo, pegar tarde". A abertura de um arquivo é um exemplo escolhido de cereja, que pode ser tratado. A maioria das IOException (por exemplo, escrever um byte), SQLException, RemoteException é impossível de lidar de maneira significativa. Falha ou nova tentativa deve estar no nível "comercial" ou "solicitação" e as exceções verificadas - conforme usadas nas bibliotecas Java - são um erro que dificulta isso. literatejava.com/exceptions/…
Thomas W

184

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]:

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

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:

  1. aumenta desnecessariamente o acoplamento;
  2. torna as assinaturas de interface muito frágeis para mudar;
  3. torna o código menos legível;
  4. é tão irritante que a reação comum do programador é derrotar o sistema fazendo algo horrível como 'lança Exception', 'catch (Exception e) {}' ou envolve tudo em um RuntimeException (o que dificulta a depuração).

E há muitas exceções ridículas da biblioteca, como:

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

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.


14
No seu exemplo de código, seria melhor encadear as exceções para que a causa original possa ser encontrada ao ler os logs: throw CanNeverHappenException (e);
Esko Luontola 6/03/09

5
Discordo. Exceções, marcadas ou não, são condições excepcionais. Exemplo: um método que recupera um objeto via HTTP. O valor de retorno é o objeto ou nada; todas as coisas que podem dar errado são excepcionais. Trate-os como valores de retorno, como é feito em C, apenas leva a confusão e design deficiente.
Mister Smith

15
@Mister: O que estou dizendo é que as exceções verificadas implementadas em Java se comportam, na prática, mais como valores de retorno que em C do que as tradicionais 'exceções' que podemos reconhecer do C ++ e de outras linguagens pré-Java. E que a IMO realmente leva a confusão e design deficiente.
bobince

9
Concorde que as bibliotecas padrão usam indevidamente as exceções verificadas definitivamente adicionadas à confusão e ao mau comportamento de captura. E, geralmente, é apenas com documentação inadequada, por exemplo, um método de desmontagem como oconnect () que lança IOException quando "algum outro erro de E / S ocorre". Bem, eu estava desconectando! Estou vazando uma alça ou outro recurso? Preciso tentar novamente? Sem saber por que isso aconteceu, não posso derivar a ação que devo tomar e, portanto, tenho que adivinhar se devo engolir, tentar novamente ou pagar a fiança.
charstar

14
+1 para "valores de retorno alternativos da API". Maneira interessante de ver as exceções verificadas.
Zsolt Török

75

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:

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

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:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

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.


4
Mas se uma exceção for capturada corretamente, você poderá informar o usuário, executar outras tarefas (log?) E sair do aplicativo de maneira controlada. Concordo que algumas partes da API poderiam ter sido projetadas melhor. E pelo motivo preguiçoso do programador, acho que como programador você é 100% responsável pelo seu código.
Mister Smith

3
observe que o try-catch-rethrow permite que você especifique uma mensagem - eu geralmente a uso para adicionar informações sobre o conteúdo de variáveis ​​de estado. Um exemplo frequente é IOExceptions adicionar o absolutePathName () do arquivo em questão.
Thorbjørn Ravn Andersen 29/10

15
Eu acho que IDEs como o Eclipse têm muita culpa pelo número de vezes que você viu o bloco vazio. Realmente, eles devem repetir novamente por padrão.
Artbristol #

12
"99% das vezes não consigo fazer nada" - errado, você pode mostrar ao usuário uma mensagem dizendo "Não foi possível conectar-se ao servidor" ou "O dispositivo IO falhou", em vez de apenas deixar o aplicativo travar devido a um pequeno soluço na rede. Ambos os seus exemplos são artes do trabalho de programadores ruins. Você deve estar atacando os programadores ruins e não verificando as próprias exceções. É como eu atacar a insulina por não ajudar no meu diabetes quando o uso como molho para salada.
AxiomaticNexus

2
@YasmaniLlanes Você nem sempre pode fazer essas coisas. Às vezes você tem uma interface para aderir. E isso é especialmente verdadeiro quando você cria boas APIs de manutenção, porque você não pode simplesmente começar a lançar efeitos colaterais em todo o lugar. Tanto isso quanto a complexidade que ela introduzirá o morderão severamente em larga escala. Então, sim, 99% das vezes, não há nada a ser feito sobre isso.
MasterMastic

50

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 Listinterface Java . Temos implementações comuns na memória como ArrayListe LinkedList. Também temos a classe esquelética, AbstractListque facilita o design de novos tipos de lista. Para uma lista somente leitura, precisamos implementar apenas dois métodos: size()e get(int index).

Esta WidgetListclasse de exemplo lê alguns objetos de tamanho fixo do tipo Widget(não mostrados) de um arquivo:

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

Ao expor os Widgets usando a Listinterface familiar , você pode recuperar itens ( list.get(123)) ou iterar uma lista ( for (Widget w : list) ...) sem precisar saber sobre WidgetListsi mesmo. Pode-se passar essa lista para qualquer método padrão que use listas genéricas ou envolvê-la em um Collections.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 a Listinterface foi implementada corretamente.

Exceto que não é. A classe acima não é compilada porque os métodos de acesso a arquivos podem IOExceptiongerar 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 da Listinterface. E não há nenhuma maneira útil de WidgetListlidar 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:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((Edit: Java 8 adicionou uma UncheckedIOExceptionclasse exatamente para este caso: para capturar e refazer IOExceptions 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 Mapapoiado por um banco de dados ou uma implementação de java.util.Randomconectado 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 Runnableinterface 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 a RuntimeException.

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):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

Viva! Usando isso, podemos lançar uma exceção marcada em qualquer profundidade da pilha sem declará-la, sem agrupá-la em RuntimeExceptione sem bagunçar o rastreamento da pilha! Usando o exemplo "WidgetList" novamente:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

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:

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

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 ter tsido 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é writetodos os métodos especificar throws IOException. ByteArrayOutputStreamé uma subclasse que grava em uma matriz na memória em vez de uma fonte de E / S verdadeira. Seus writemétodos substituídos não podem causar IOExceptions, portanto, eles não têm throwscláusula, e você pode chamá-los sem se preocupar com o requisito de pegar ou especificar.

Exceto nem sempre. Suponha que Widgettenha um método para salvá-lo em um fluxo:

public void writeTo(OutputStream out) throws IOException;

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:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

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 IOExceptionlançada por writeum OutputStream, mas deseja alterar o tipo declarado da variável para a ByteArrayOutputStream, 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 writemé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!ByteArrayOutputStreamwrite(byte[] data)write(byte[] data, int offset, int length)ByteArrayOutputStreamthrowsByteArrayOutputStream

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 trye dos catchblocos. 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:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

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 Nextou o PHP error_reporting(0);.

Chamar algum tipo de classe de logger não é muito melhor:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

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.


Para o seu método de tamanho com o WidgetList, eu armazenaria em cache o tamanho em uma variável e o definiria no construtor. O construtor é livre para lançar uma exceção. Isso não funcionará se o arquivo for alterado ao usar o WidgetList, o que provavelmente seria ruim se o fizesse.
TofuBeer 27/11/13

2
SomeStupidExceptionOmgWhoCare bem alguém se importou o suficiente para jogá-lo. Portanto, ou nunca deveria ter sido lançado (design ruim) ou você deveria realmente lidar com isso. O mesmo se aplica à implementação incorreta de uma classe anterior à 1.0 (o fluxo de saída da matriz de bytes) em que o design estava, infelizmente ruim.
TofuBeer

2
O idioma apropriado teria sido uma diretiva que capturasse quaisquer exceções especificadas lançadas por chamadas de sub-rotina aninhadas e as repetisse novamente envoltas em a RuntimeException. Observe que uma rotina pode ser declarada simultaneamente throws IOExceptione ainda assim especificar que qualquer IOExceptionlançamento de uma chamada aninhada deve ser considerada inesperada e quebrada.
Supercat

15
Eu sou um desenvolvedor profissional de C # com alguma experiência em Java que tropeçou neste post. Estou confuso sobre o porquê de alguém apoiar esse comportamento bizarro. No .NET, se eu quiser capturar um tipo específico de exceção, posso capturar isso. Se eu quiser deixar a pilha subir, não há nada a fazer. Eu gostaria que o Java não fosse tão peculiar. :)
aikeru

Em relação a "às vezes vou comentar temporariamente uma chamada de método" - aprendi a usar 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.
Maaartinus 25/05

45

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 throwsem 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:

  1. Pegar e manusear um exceção específica . Isso é para implementar lógica de repetição, por exemplo.
  2. Capturar e relançar outras exceções. Tudo o que acontece aqui é geralmente o log, e geralmente é uma mensagem trivial como "Não é possível abrir o $ filename". Esses são erros sobre os quais você não pode fazer nada; apenas níveis mais altos sabem o suficiente para lidar com isso.
  3. Pegue tudo e exiba uma mensagem de erro. Isso geralmente está na raiz de um despachante, e tudo o que faz é garantir que ele possa comunicar o erro ao chamador por meio de um mecanismo que não seja de exceção (diálogo pop-up, empacotando um objeto de erro de RPC, etc.).

5
Você poderia ter criado subclasses específicas de AppSpecificException para permitir a separação, mantendo as assinaturas simples do método.
Thorbjørn Ravn Andersen

1
Uma adição muito importante ao item 2 é que ele permite ADICIONAR INFORMAÇÕES à exceção capturada (por exemplo, aninhando uma RuntimeException). É muito, muito melhor ter o nome do arquivo não encontrado no rastreamento da pilha do que oculto no fundo de um arquivo de log.
Thorbjørn Ravn Andersen

1
Basicamente, seu argumento é "Gerenciar exceções é cansativo, por isso prefiro não lidar com isso". À medida que a exceção surge, perde significado e a criação de contexto é praticamente inútil. Como designer de uma API, você deve deixar claro contratualmente o que pode ser esperado quando algo der errado, se meu programa falhar porque eu não fui informado de que essa ou aquela exceção pode "borbulhar", você, como designer, falhou e Como resultado de sua falha, meu sistema não é tão estável quanto possível.
Newtopian

4
Não é isso que estou dizendo. Sua última frase realmente concorda comigo. Se tudo estiver agrupado em AppSpecificException, ele não ocorrerá bolhas (e o significado / contexto será perdido) e, sim, o cliente da API não será informado - é exatamente isso que acontece com as exceções verificadas (como em java) , porque as pessoas não querem lidar com funções com muitas throwsdeclarações.
Richard Levasseur

6
@ Newtopian - as exceções só podem ser tratadas no nível "comercial" ou "solicitação". Faz sentido falhar ou tentar novamente em grande granularidade, não para cada minúscula falha em potencial. Por esse motivo, as práticas recomendadas para tratamento de exceções são resumidas como "jogue cedo, pegue tarde". As exceções verificadas dificultam o gerenciamento da confiabilidade no nível correto e incentivam um grande número de blocos de captura com códigos incorretos. literatejava.com/exceptions/…
Thomas W

24

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

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

Atualizar a interface do usuário a partir de um thread que não seja da interface do usuário em c #:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

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:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

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

  • Capturar exceções é diferente de lidar com elas.
  • As exceções verificadas adicionam ruído ao código.
  • O tratamento de exceções funciona bem em C # sem eles.

Eu escrevi sobre isso anteriormente .


3
No exemplo, Java e C # estão apenas deixando as exceções se propagarem sem manipulá-las (Java via IllegalStateException). A diferença é que você pode manipular uma FileNotFoundException, mas é improvável que manipular InvocationTargetException ou InterruptedException seja útil.
Lucas Quinane

3
E da maneira C #, como sei que a exceção de E / S pode ocorrer? Além disso, eu nunca lançaria uma exceção de execução ... Considero que abusam do tratamento de exceções. Desculpe, mas para essa parte do seu código, ainda posso ver o seu lado.
TofuBeer 01/04/09

7
Estamos chegando lá :-) Então, a cada novo lançamento de uma API, você precisa vasculhar todas as suas chamadas e procurar novas exceções que possam acontecer? Isso pode acontecer facilmente com as APIs internas de uma empresa, pois elas não precisam se preocupar com a compatibilidade com versões anteriores.
TofuBeer 02/04/09

3
Você quis dizer diminuir a relação sinal / ruído?
neo2862

3
@TofuBeer Não está sendo forçado a atualizar seu código depois que a interface de uma API subjacente mudou uma coisa boa? Se você tivesse apenas exceções desmarcadas lá, teria terminado com um programa quebrado / incompleto sem saber.
François Bourgeois

23

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:

A cláusula throws, pelo menos da maneira como é implementada em Java, não necessariamente força você a lidar com as exceções, mas se você não lidar com elas, obriga a reconhecer com precisão quais exceções podem passar. Exige que você capture exceções declaradas ou as coloque na sua própria cláusula throws. Para contornar esse requisito, as pessoas fazem coisas ridículas. Por exemplo, eles decoram todos os métodos com "lança exceção". Isso simplesmente derrota completamente o recurso, e você acabou de fazer o programador escrever uma besteira mais confusa. Isso não ajuda ninguém.


2
De fato, o arquiteto-chefe.
trap

18
Eu li que, para mim, seu argumento se resume a "existem maus programadores por aí".
TofuBeer 05/03/09

6
TofuBeer, de maneira alguma. O ponto principal é que muitas vezes você não sabe o que fazer com a exceção que o método chamado lança, e o caso em que você está realmente interessado nem é mencionado. Você abre um arquivo, obtém uma exceção de E / S, por exemplo ... isso não é problema meu, então eu vomito. Mas o método de chamada de nível superior apenas deseja interromper o processamento e informar ao usuário que há um problema desconhecido. A exceção marcada não ajudou em nada. Foi uma das milhões de coisas estranhas que podem acontecer.
Dan Rosenstark 27/05/2009

4
@ yar, se você não gostar da exceção verificada, faça um "throw new RuntimeException (" não esperávamos isso ao fazer Foo.bar () ", e)" e pronto.
Thorbjørn Ravn Andersen

4
@ ThorbjørnRavnAndersen: Uma fraqueza fundamental do projeto em Java, que .net infelizmente copiou, é que ele usa o tipo de uma exceção como o principal meio para decidir se deve agir, e o principal meio de indicar o tipo geral de coisa isso deu errado, quando na verdade as duas questões são em grande parte ortogonais. O que importa não é o que deu errado, mas em que objetos de estado estão. Além disso, o .net e o Java assumem, por padrão, que agir e resolver uma exceção geralmente é a mesma coisa, quando na verdade eles geralmente são diferentes.
Supercat 29/10

20

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:

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

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.


Java incentiva você a fazer esse tipo de coisa, para que você não precise adicionar todo tipo de exceção a cada assinatura de método.
yfeldblum 5/03/09

17
Engraçado .. desde que eu abracei as exceções corretamente e as usei apropriadamente, meus programas pararam de explodir em uma enorme pilha fumegante de insatisfação do cliente. Se durante o desenvolvimento você tiver um grande e feio rastreio de pilha, o cliente também será obrigado a obtê-lo. D'love vê seu rosto quando vê ArrayIndexOutOfBoundsException com um rastreamento de pilha de milha em seu sistema travado, em vez de uma pequena notificação de bandeja dizendo que a configuração de cores do botão XYZ não pôde ser analisada, portanto o padrão foi usado com o software zumbindo alegremente along
Newtopian 7/08/2009

1
Talvez o que o Java precise seja de uma declaração "cantHandle" que especifique que um método ou bloco de código try / catch não esteja preparado para lidar com uma exceção específica que ocorra nele e que qualquer exceção que ocorra por outros meios que não sejam explícitos o lance dentro desse método (em oposição a um método chamado) deve ser automaticamente quebrado e retrocedido em um RuntimeException. IMHO, as exceções verificadas raramente devem propagar a pilha de chamadas sem serem quebradas.
Supercat

3
@ Newtopian - escrevo servidor e software de alta confiabilidade e faço isso há 25 anos. Meus programas nunca explodiram e eu trabalho com sistemas militares e financeiros de alta disponibilidade, nova tentativa e reconexão. Eu tenho uma base objetiva absoluta para preferir exceções de tempo de execução. As exceções verificadas tornam mais difícil seguir as melhores práticas corretas de "jogar cedo, atrasar". A confiabilidade correta e o tratamento de erros estão no nível "comercial", "conexão" ou "solicitação". (Ou ocasionalmente ao analisar dados). As exceções verificadas atrapalham o processo.
Thomas W

1
As exceções de que você está falando aqui são as RuntimeExceptionsque 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, como IOException. Se você receber um IOException, não há nada para corrigir no seu código; seu programa não deve explodir apenas porque havia um problema de rede.
AxiomaticNexus

20

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:

Contingência: Uma condição esperada que exige uma resposta alternativa de um método que pode ser expresso em termos da finalidade pretendida do método. O chamador do método espera esses tipos de condições e tem uma estratégia para lidar com elas.

Falha: Uma condição não planejada que impede um método de atingir seu objetivo pretendido, que não pode ser descrito sem referência à implementação interna do método.

(Como o SO não permite tabelas, você pode ler o seguinte na página original ...)

Contingência

  • Considera-se: Uma parte do design
  • Prevê-se que aconteça: regularmente, mas raramente
  • Quem se importa com isso: o código upstream que chama o método
  • Exemplos: modos de retorno alternativos
  • Melhor mapeamento: uma exceção verificada

Culpa

  • Considera-se: Uma surpresa desagradável
  • É esperado que aconteça: nunca
  • Quem se importa com isso: as pessoas que precisam resolver o problema
  • Exemplos: erros de programação, mau funcionamento do hardware, erros de configuração, arquivos ausentes, servidores indisponíveis
  • Melhor mapeamento: uma exceção desmarcada

Eu sei quando usá-los, eu quero saber por que as pessoas que não seguem esse conselho ... não seguem esse conselho :-)
TofuBeer

O que são erros de programação e como diferenciá-los dos erros de uso ? É um erro de programação se o usuário passa os argumentos errados para o programa? Do ponto de vista Java, pode não haver um erro de programação, mas do ponto de vista do script shell, é um erro de programação. Então, em que argumentos inválidos args[]? Eles são uma contingência ou uma falha?
ceving 12/03/2013

3
@TofuBeer - Como os projetistas da biblioteca Java escolheram colocar todos os tipos de falhas irrecuperáveis ​​de baixo nível como exceções verificadas quando claramente deveriam ter sido desmarcadas . FileNotFound é a única IOException que deve ser verificada, por exemplo. No que diz respeito ao JDBC - apenas conectar-se ao banco de dados, pode razoavelmente ser considerado uma contingência . Todas as outras SQLExceptions deveriam ter sido falhas e desmarcadas. O tratamento de erros deve estar corretamente no nível "comercial" ou "solicitação" - consulte as práticas recomendadas "jogue cedo, pegue tarde". As exceções verificadas são uma barreira para isso.
Thomas W

1
Há uma única falha enorme no seu argumento. "Contingência" NÃO deve ser tratada por meio de exceções, mas por meio de códigos de negócios e valores de retorno de método. Exceções são para, como a palavra diz, situações excepcionais, portanto, falhas.
Matteo Mosca

@MatteoMosca Os códigos de retorno de erro tendem a ser ignorados e isso é suficiente para desqualificá-los. Na verdade, qualquer coisa incomum muitas vezes pode ser tratada apenas em algum lugar da pilha e esse é um caso de uso para exceções. Eu poderia imaginar algo como File#openInputStreamretornar Either<InputStream, Problem>- se é isso que você quer dizer, então podemos concordar.
Maaartinus

19

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.acmeapenas lança AcmeExceptionsubclasses em vez de específicas. Os chamadores precisam manipular, declarar ou re-sinalizar AcmeExceptions, mas ainda não podem ter certeza se AcmeFileNotFoundErroraconteceu ou não AcmePermissionDeniedError.

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 de AcmeException.

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 Errorclasse geral em vez de uma AcmeException, mas se elas se preocupam com a qualidade da API, aprenderão a introduzir uma, AcmeFileNotFoundErrorafinal.

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.


4
Seu argumento argumenta contra si mesmo. Se "às vezes você precisar capturar uma exceção" e a qualidade da API geralmente for ruim, sem exceções verificadas, você não saberá se o designer esqueceu de documentar que um determinado método lança uma exceção que precisa ser capturada. Junte isso ao arremessar AcmeExceptione não à AcmeFileNotFoundErrorboa 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.
Eva

1
O design da biblioteca Java cometeu erros graves. As 'exceções verificadas' eram para contingências previsíveis e recuperáveis ​​- como arquivo não encontrado, falha na conexão. Eles nunca foram projetados ou adequados para falhas sistêmicas de baixo nível. Seria bom forçar a abertura de um arquivo a ser verificado, mas não há nova tentativa ou recuperação sensata por falha na gravação de um único byte / execução de uma consulta SQL etc. A nova tentativa ou recuperação é tratada corretamente na solicitação "comercial" ou " ", que exceções verificadas dificultam inutilmente. literatejava.com/exceptions/…
Thomas W

17

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.


6
Eu faço para. Para mim, eles são uma parte essencial de um contrato. Sem precisar detalhar a documentação da API, posso conhecer rapidamente os cenários de erro mais prováveis.
23411 Wayne Hartman

2
Aceita. Experimentei a necessidade de exceções verificadas no .Net uma vez quando tentei fazer chamadas de rede. Sabendo que um soluço de rede poderia acontecer a qualquer momento, eu tive que ler toda a documentação das APIs para descobrir que exceção é essa que eu precisava capturar especificamente para esse cenário. Se o C # tivesse verificado exceções, eu saberia imediatamente. Outros desenvolvedores de C # provavelmente apenas deixariam o aplicativo travar devido a um simples erro de rede.
AxiomaticNexus

13

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:

  • Fatal - Essas exceções não são sua culpa : você não pode impedir isso e não pode lidar com elas de maneira sensata. Por exemplo, OutOfMemoryErrorou ThreadAbortException.
  • Boneheaded - Essas exceções são sua culpa : você deveria tê-las evitado e elas representam erros no seu código. Por exemplo, ArrayIndexOutOfBoundsException, NullPointerExceptionou qualquer IllegalArgumentException.
  • Vexatório - Essas exceções não são excepcionais , não são sua culpa, você não pode evitá-las, mas precisará lidar com elas. Geralmente, são o resultado de uma decisão de design infeliz, como lançar NumberFormatExceptionde em Integer.parseIntvez de fornecer um Integer.tryParseIntmétodo que retorna um falso booleano na falha de análise.
  • Exógenos - Essas exceções geralmente são excepcionais , não são sua culpa, você não pode (razoavelmente) evitá-las, mas deve tratá-las . Por exemplo FileNotFoundException,.

Um usuário da API:

  • não deve lidar com exceções fatais ou estúpidas .
  • deve lidar com exceções irritantes , mas elas não devem ocorrer em uma API ideal.
  • deve lidar com exceções exógenas .

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.


E use-os mal por não ter uma hierarquia.
TofuBeer

1
FileNotFound e o estabelecimento de uma conexão JDBC / rede são contingências e corretas para serem verificadas exceções, pois são previsíveis e (possíveis) recuperáveis. A maioria das outras IOExceptions, SQLExceptions, RemoteException etc. são falhas imprevisíveis e irrecuperáveis , e deveriam ter sido exceções de tempo de execução. Devido ao design incorreto da biblioteca Java, todos nós fomos afetados por esse erro e agora estamos usando o Spring & Hibernate (que acertou o design).
Thomas W

Você geralmente deve lidar com exceções de cabeça para baixo, embora não queira chamá-lo de "manipulação". Por exemplo, em um servidor Web, eu os registro e mostro 500 ao usuário. Como a exceção é inesperada, há tudo o que posso fazer antes da correção do bug.
Maaartinus

6

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?


2
As exceções verificadas seriam úteis se houvesse um meio declarativo de dizer que nenhuma das chamadas de método dentro de um bloco deve lançar algumas (ou nenhuma) exceção verificada, e essas exceções devem ser quebradas e retrocedidas automaticamente. Eles poderiam ser ainda mais úteis se chamadas para métodos declarados como lançando exceções verificadas fossem trocadas pela velocidade / retorno da chamada pela velocidade de tratamento de exceções (para que as exceções esperadas pudessem ser tratadas quase tão rapidamente quanto o fluxo normal do programa). Nenhuma situação atualmente se aplica, no entanto.
supercat

6

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.


5

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 Exceptionse imagina? Bem, não, porque Exceptions são exceções, e da mesma forma são Exceptions, exceto as exceções que não são Exceptions, porque outras exceções são na verdade Errors, 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ão Exceptionnem Errors, mas meramente Throwableexceções.

Quais destas são as exceções "marcadas"? Throwables são exceções verificadas, exceto se também são Errors, que são exceções não verificadas, e há os Exceptions, que também são Throwables, e são o principal tipo de exceção verificada, exceto que também há uma exceção, se eles também são RuntimeExceptions, porque esse é o outro tipo de exceção desmarcada.

Para que servem RuntimeExceptions? Bem, assim como o nome indica, são exceções, como todos os Exceptions, e acontecem em tempo de execução, como todas as exceções, na verdade, exceto que RuntimeExceptions são excepcionais em comparação com outros tempos de execução Exceptionporque não deveriam acontecer, exceto quando você comete algum erro bobo, embora RuntimeExceptions nunca seja Errors, então são para coisas que são excepcionalmente errôneas, mas que na verdade não são Errors. Exceto por RuntimeErrorException, que realmente é um RuntimeExceptionpara Errors. Mas todas as exceções não devem representar circunstâncias errôneas, afinal? Sim todos eles. Exceto por ThreadDeathuma 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 Errors (que são para exceções de execução excepcionais, portanto desmarcadas) Exceptiones (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, precisamos IllegalAccessErrore IllegalAccessExceptione InstantiationErrore InstantiationExceptione NoSuchFieldErrore NoSuchFieldExceptione NoSuchMethodErrore NoSuchMethodExceptione ZipErrore ZipException.

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 um UnexpectedException, ou um UnknownException(que não está relacionado UnknownError, o que é apenas para "sérias exceções"), ou um ExecutionException, ou um InvocationTargetException, ou um ExceptionInInitializerError.

Ah, e não devemos esquecer o novo snazzy do Java 8 UncheckedIOException, que é uma RuntimeExceptionexceção projetada para permitir que você jogue o conceito de verificação de exceção pela janela, envolvendo IOExceptionexceções verificadas causadas por erros de E / S (que não causam IOErrorexceçõ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!


2
Tanto quanto posso dizer, esta resposta diz apenas "As exceções de Java são uma bagunça" de uma maneira cheia de ironia, sem dúvida divertida. O que parece não fazer é explicar por que os programadores tendem a evitar entender como essas coisas devem funcionar. Além disso, nos casos da vida real (pelo menos aqueles com os quais tive a chance de lidar), se os programadores não tentarem deliberadamente tornar suas vidas mais difíceis, as exceções não serão nem de longe tão complicadas quanto você descreveu.
CptBartender

5

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:

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

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:

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

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.


6
As exceções são inventadas para fazer algo útil com elas. Gravá-los em um arquivo de log ou renderizar uma janela bonita não é útil, porque o problema original não é resolvido por isso. Fazer algo útil requer tentar uma estratégia de solução diferente. Exemplos: se não for possível obter meus dados do servidor AI, experimente-o no servidor B. Ou se o algoritmo A produz um estouro de heap, experimento o algoritmo B, que é muito mais lento, mas pode ter êxito.
ceving 12/03/2013

2
@ ceving Sim, é tudo de bom e verdadeiro em teoria. Mas agora vamos voltar a praticar palavras. Por favor, responda honestamente com que frequência você faz isso no seu projeto de palavras reais? Que parte dos catchblocos 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.
Piotr Sobczyk

3
@PiotrSobczyk: Se um programa executar alguma ação como resultado de uma solicitação de processamento, e a operação falhar de alguma maneira que não tenha danificado nada no estado do sistema, notificar o usuário de que a operação não pôde ser concluída é perfeitamente útil maneira de lidar com a situação. A maior falha de exceções em C # e .net é que não há maneira consistente de verificar se alguma coisa no estado do sistema pode ter sido danificada.
Supercat 15/03

4
Correto, @PiotrSobczyk. Na maioria das vezes, a única ação correta a ser tomada em resposta a uma exceção é reverter a transação e retornar uma resposta de erro. As idéias de "solucionar exceções" implicam conhecimento e autoridade que não temos (e não devíamos) e violam o encapsulamento. Se nosso aplicativo não for um banco de dados, não devemos tentar corrigi -lo. Falhar de forma limpa e evitar a gravação de dados errados, ceving, é útil o suficiente .
Thomas W

@PiotrSobczyk Ontem, lidei com uma exceção "não foi possível ler o objeto" (que só ocorrerá porque o banco de dados subjacente foi atualizado antes do software - o que nunca deve acontecer, mas é uma possibilidade devido a erro humano) ao fazer o failover para um versão histórica do banco de dados garantida para apontar para uma versão antiga do objeto.
Eponymous 10/10

4

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.


4

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.


1
Você deve ter algo como DAGNodeException na interface, capturar o IOException e convertê-lo em um DAGNodeException: chamada de anulação pública (DAGNode arg) lança DAGNodeException;
TofuBeer 07/11/2009

2
@TofuBeer, esse é exatamente o meu ponto. Acho que constantemente quebrar e desembrulhar exceções é pior do que remover as exceções verificadas.
Joshua

2
Bem, então discordamos completamente ... mas seu artigo ainda não responde à verdadeira questão subjacente de como você impede que seu aplicativo exiba um rastreamento de pilha para o usuário quando uma exceção de tempo de execução é lançada.
TofuBeer 08/11/2009

1
@TofuBeer - Quando falha, informar ao usuário que está com falha está correto! Qual é a sua alternativa, além de "encobrir" a falha com dados 'nulos' ou incompletos / incorretos? Fingir que conseguiu é uma mentira, que só piora as coisas. Com 25 anos de experiência em sistemas de alta confiabilidade, a lógica de repetição deve ser usada com cuidado e sempre que apropriado. Eu também esperaria que um Visitante provavelmente falhasse novamente, não importa quantas vezes você o tente novamente. A menos que você esteja pilotando um avião, a troca para uma segunda versão do mesmo algoritmo é impraticável e implausível (e pode falhar de qualquer maneira).
Thomas W

4

Para tentar abordar apenas a pergunta não respondida:

Se você lançar subclasses RuntimeException em vez de subclasses Exception, como você sabe o que deve capturar?

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.


2
Exatamente. Fácil e direto, como é o tratamento de exceções. Como Dave diz, o tratamento correto de exceções normalmente é feito em um nível alto . "Jogue cedo, pegue tarde" é o princípio. As exceções verificadas tornam isso difícil.
Thomas W

4

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:

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).

O autor "respostas":

É absolutamente incorreto supor que todas as exceções de tempo de execução não devam ser capturadas e permitidas a propagação até o "topo" do aplicativo. (...) Para todas as condições excepcionais que precisam ser tratadas de maneira distinta - pelos requisitos do sistema / negócios - os programadores devem decidir onde capturá-lo e o que fazer quando a condição for capturada. Isso deve ser feito estritamente de acordo com as necessidades reais do aplicativo, não com base em um alerta do compilador. Todos os outros erros devem ter permissão de se propagar livremente para o manipulador superior, onde seriam registrados e uma ação normal (talvez finalização) será executada.

E o principal pensamento ou artigo é:

Quando se trata de tratamento de erros em software, a única suposição segura e correta que pode ser feita é que uma falha pode ocorrer em absolutamente todas as sub-rotinas ou módulos existentes!

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.


4

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/


1
Meu ponto é falhar corretamente como estratégia geral. Exceções não verificadas ajudam isso, pois não forçam a interposição de blocos de captura. A captura e o registro de erros podem ser deixados para alguns manipuladores mais externos, em vez de serem codificados incorretamente milhares de vezes em toda a base de código (que é realmente o que oculta bugs) . Para falhas arbitrárias, as exceções não verificadas são absolutamente as mais corretas. Contingências - resultados previsíveis, como fundos insuficientes - são as únicas exceções que valem a pena ser verificadas legitimamente.
Thomas W

1
Minha resposta já acima aborda isso. Em primeiro lugar, 1) O manipulador de falhas mais externas deve capturar tudo. Além disso, apenas para sites específicos identificados, 2) Contingências esperadas específicas podem ser capturadas e tratadas - no local imediato em que são lançadas. Isso significa que o arquivo não foi encontrado, fundos insuficientes etc. no momento em que eles poderiam ser recuperados - não mais. O princípio do encapsulamento significa que as camadas externas não podem / não devem ser responsáveis ​​por entender / recuperar de falhas profundas. Terceiro, 3) Todo o resto deve ser jogado para fora - desmarcado, se possível.
Thomas W

1
O manipulador mais externo captura Exception, registra-o e retorna uma resposta "com falha" ou mostra uma caixa de diálogo de erro. Muito simples, não é difícil de definir. O ponto é que toda exceção que não é imediata e localmente recuperável é uma falha irrecuperável devido ao princípio do encapsulamento. Se o código que você deve conhecer não puder recuperá-lo, a solicitação falhará de maneira geral e correta. Este é o caminho certo para fazê-lo corretamente.
Thomas W

1
Incorreta. O trabalho do manipulador externo é falhar corretamente e registrar erros no limite de 'solicitação'. A solicitação quebrada falha corretamente, a exceção é relatada e o thread pode continuar atendendo a próxima solicitação. Esse manipulador mais externo é um recurso padrão nos contêineres Tomcat, AWT, Spring, EJB e no encadeamento 'principal' do Java.
Thomas W

1
Por que é perigoso relatar "bugs genuínos" no limite da solicitação ou no manipulador mais externo ??? Frequentemente trabalho na integração e confiabilidade de sistemas, onde a engenharia correta de confiabilidade é realmente importante e uso abordagens de "exceção não verificada" para fazer isso. Não tenho muita certeza do que você está realmente debatendo - parece que você pode realmente gastar três meses da maneira desmarcada de exceção, ter uma ideia e, em seguida, talvez possamos discutir mais. Obrigado.
Thomas W

4

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.


Certo, eu só queria enfatizar a diferença entre código inacessível (que gera um erro) e condicionais com um resultado previsível. Retirei este comentário mais tarde.
Holger

3

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's close()método. Pode gerar um SQLException, 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:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

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.


2
Certo, qualquer um dos drivers pode ... a pergunta é: "por que o programador deveria se importar?" como ele acabou de acessar o banco de dados de qualquer maneira. Os documentos até avisam que você deve sempre confirmar () ou reverter () a transação atual antes de chamar close ().
Powerlord

Muitas pessoas pensam que fechar um arquivo não pode gerar uma exceção ... stackoverflow.com/questions/588546/… você tem 100% de certeza de que não há casos importantes?
TofuBeer 5/03/09

Estou 100% certo de que não há casos que se importam e que o chamador não iria colocar em um try / catch.
Martin

1
Excelente exemplo de fechamento de conexões, Martin! Só posso reformular você: se apenas declaramos explicitamente que terminamos uma conexão, por que incomodar o que está acontecendo quando a fechamos? Existem mais casos como este em que o programador realmente não se importa se a exceção ocorre e ele está absolutamente certo sobre isso.
Piotr Sobczyk

1
@PiotrSobczyk: Alguns drivers SQL irão gritar se alguém fechar uma conexão após iniciar uma transação, mas não a confirme nem a reverta. IMHO, o chiado é melhor do que ignorar o problema silenciosamente, pelo menos nos casos em que o chiado não fará com que outras exceções se percam.
achou

3

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.


2

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 FileNotFoundExceptionou (meu favorito) InterruptedException. Essas condições quase sempre devem ser tratadas especificamente (ou seja, sua reação a um InterruptedExceptionnão é a mesma que sua reação a um IllegalArgumentException). 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 vi InterruptedExceptionmanipulados adequadamente!)

Mais uma coisa - RuntimeExceptionnem sempre é "onde um desenvolvedor tem algo errado". Uma exceção de argumento ilegal é lançada quando você tenta criar um enumuso valueOfe não existe enumesse nome. Isso não é necessariamente um erro do desenvolvedor!


1
Sim, é um erro do desenvolvedor. Eles claramente não usaram o nome certo, então precisam voltar e corrigir o código.
AxiomaticNexus

@AxiomaticNexus Nenhum desenvolvedor sensato usa enumnomes de membros, simplesmente porque eles usam enumobjetos. 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 é ligar MyEnum#valueOfe pegar o IAE. Outra maneira é usar um pré-preenchido Map<String, MyEnum>, mas esses são detalhes de implementação.
Maaartinus

@maaartinus Há casos em que nomes de membros enum são usados ​​sem que a string seja proveniente de fora. Por exemplo, quando você deseja percorrer todos os membros dinamicamente para fazer algo com cada um. Além disso, se a corda vem de fora ou não é irrelevante. O desenvolvedor possui todas as informações necessárias para saber se a passagem de x para "MyEnum # valueOf" resultará em um erro antes de ser transmitida. Passar x string para "MyEnum # valueOf" de qualquer maneira, quando isso causaria erro, seria claramente um erro da parte do desenvolvedor.
AxiomaticNexus

2

Aqui está um argumento contra exceções verificadas (de joelonsoftware.com):

O raciocínio é que considero as exceções não melhores que as "goto's", consideradas prejudiciais desde os anos 1960, na medida em que criam um salto abrupto de um ponto do código para outro. Na verdade, eles são significativamente piores que os de Goto:

  • Eles são invisíveis no código fonte. Observando um bloco de código, incluindo funções que podem ou não gerar exceções, não há como ver quais exceções podem ser lançadas e de onde. Isso significa que mesmo uma inspeção cuidadosa do código não revela possíveis erros.
  • Eles criam muitos pontos de saída possíveis para uma função. Para escrever o código correto, você realmente precisa pensar em cada caminho de código possível através de sua função. Toda vez que você chama uma função que pode gerar uma exceção e não capturá-la no local, você cria oportunidades para erros surpreendentes causados ​​por funções que terminaram abruptamente, deixando os dados em um estado inconsistente ou outros caminhos de código que você não identificou. pense sobre.

3
+1 Você pode resumir o argumento em sua resposta? São como gotos invisíveis e saídas precoces para suas rotinas, espalhadas por todo o programa.
5119 MarkJ

13
Isso é mais um argumento contra exceções em geral.
Ionuţ G. Stan

10
você realmente leu o artigo !! Primeiro, ele fala sobre exceções em geral, em segundo lugar, a seção "Eles são invisíveis no código fonte" se aplica especificamente à exceção UNCHECKED. Este é o ponto inteiro de exceção verificada ... assim que você sabe o código lança o onde
Newtopian

2
@ Eva Eles não são os mesmos. Com uma declaração goto, você pode ver a gotopalavra - chave. Com um loop, você pode ver a chave de fechamento ou a palavra-chave breakou continue. Todos eles saltam para um ponto no método atual. Mas você não pode sempre ver a throw, porque muitas vezes não é no método atual, mas em outro método que ele chama (possivelmente indiretamente.)
finnw

5
As funções @finnw são elas mesmas uma forma de ir. Você geralmente não sabe quais funções as funções que você está chamando estão chamando. Se você programou sem funções, não teria problemas com exceções invisíveis. O que significa que o problema não está especificamente vinculado a exceções e não é um argumento válido contra exceções em geral. Você poderia dizer que os códigos de erro são mais rápidos, as mônadas são mais limpas, mas o argumento goto é tolo.
Eva

2

As vantagens comprovam que a exceção verificada não é necessária são:

  1. Muitas estruturas que funcionam com Java. Como o Spring, que agrupa a exceção JDBC em exceções não verificadas, lançando mensagens no log
  2. Muitos idiomas que vieram após o java, mesmo no topo da plataforma java - eles não os usam
  3. Exceções verificadas, é uma previsão gentil sobre como o cliente usaria o código que gera uma exceção. Mas um desenvolvedor que escreve esse código nunca saberia sobre o sistema e os negócios nos quais o cliente está trabalhando. Como exemplo, os métodos Interfcace que forçam a lançar uma exceção verificada. Existem 100 implementações no sistema, 50 ou mesmo 90 implementações não lançam essa exceção, mas o cliente ainda deve capturar essa exceção se ele fizer referência a essa interface. Essas implementações de 50 ou 90 tendem a manipular essas exceções dentro de si, colocando exceção no log (e isso é um bom comportamento para elas). O que devemos fazer com isso? É melhor eu ter uma lógica de segundo plano que faça todo esse trabalho - enviando mensagem para o log. E se eu, como cliente de código, sentir que preciso lidar com a exceção - farei isso.
  4. Outro exemplo, quando estou trabalhando com E / S em java, me força a verificar todas as exceções, se o arquivo não existir? o que devo fazer com isso? Se não existir, o sistema não passará para a próxima etapa. O cliente desse método não obteria o conteúdo esperado desse arquivo - ele pode lidar com a exceção de tempo de execução, caso contrário, devo primeiro verificar a exceção verificada, colocar uma mensagem no log e lançar a exceção do método. Não ... não - seria melhor fazê-lo automaticamente com o RuntimeEception, que faz / ilumina automaticamente. Não há nenhum sentido em lidar com isso manualmente - eu ficaria feliz em ver uma mensagem de erro no log (o AOP pode ajudar com isso ... algo que corrige o java). Se, eventualmente, eu considerar que o sistema deve mostrar uma mensagem pop-up para o usuário final - mostrarei isso, não é um problema.

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.


1
Em vez de ter duas versões de classes, deve haver uma maneira concisa de especificar que não se espera que nenhuma das chamadas de método feitas por um bloco de código gere exceções de certos tipos e que essas exceções devem ser agrupadas por alguns meios e métodos especificados. relançado (por padrão, crie um novo RuntimeExceptioncom uma exceção interna apropriada). É lamentável que seja mais conciso ter o método externo throwsuma 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.
Supercat

2

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 declara throws MyAppExceptionque é 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.


1

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


2
O problema com exceções verificadas em Java deriva de um problema mais profundo, que é o excesso de informações encapsuladas no TYPE da exceção, e não nas propriedades de uma instância. Seria útil ter exceções verificadas, se ser "marcado" fosse um atributo de sites de lançamento / captura e se alguém pudesse declarar de forma declarativa se uma exceção verificada que escapa a um bloco de código deve permanecer como uma exceção verificada ou ser vista por qualquer bloco anexo como uma exceção não verificada; da mesma forma, os blocos de captura devem poder especificar que desejam apenas exceções verificadas.
Supercat 02/08

1
Suponha que uma rotina de pesquisa de dicionário seja especificada para lançar algum tipo específico de exceção se for feita uma tentativa de acessar uma chave inexistente. Pode ser razoável para o código do cliente capturar essa exceção. Se, no entanto, algum método usado pela rotina de pesquisa lançar esse mesmo tipo de exceção de uma maneira que a rotina de pesquisa não espera, o código do cliente provavelmente não deve capturá-lo. A verificação ser uma propriedade de instâncias de exceção , sites de lançamento e sites de captura evitaria esses problemas. O cliente capturava exceções 'verificadas' desse tipo, evitando as inesperadas.
Supercat 02/08

0

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 () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

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 ... :)

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.