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 fopen
indica falha retornando 0 e C (tolamente) permite tratar 0 como um booleano e ... Basicamente, você aprende esse idioma e está bem. Mas e se você é um noob e não aprendeu o idioma. Então, é claro, você começa 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 FileNotFoundException
não é apenas uma coisa boa, mas uma ferramenta essencial na caixa de ferramentas dos programadores de API para definir a API da maneira mais útil para o programador cliente. Mas 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:
Fora do controle do cliente ou Fechado x Aberto:
As exceções verificadas devem ser usadas apenas quando o caso de erro estiver fora de controle da API e do programador do cliente. Isso tem a ver com o quão aberto ou fechado o sistema é. Em uma interface de usuário restrita em que o programador cliente tem controle, digamos, de todos os botões, comandos de teclado etc. que adicionam e excluem linhas da exibição de tabela (um sistema fechado), é um erro de programação do cliente se tentar buscar dados de uma linha inexistente. Em um sistema operacional baseado em arquivo no qual qualquer número de usuários / aplicativos pode adicionar e excluir arquivos (um sistema aberto), é concebível que o arquivo que o cliente está solicitando tenha sido excluído sem o conhecimento deles, portanto, espera-se que eles lidem com ele .
Ubiquidade:
As exceções marcadas não devem ser usadas em uma chamada de API feita frequentemente pelo cliente. Com frequência, quero dizer de muitos lugares no código do cliente - não com frequência no tempo. Portanto, um código de cliente não tende a abrir muito o mesmo arquivo, mas minha exibição de tabela se RowData
espalha por diferentes métodos. Em particular, eu vou escrever um monte de código como
if (model.getRowData().getCell(0).isEmpty())
e será doloroso ter que tentar sempre tentar / capturar.
Informando o Usuário:
As exceções marcadas devem ser usadas nos casos em que você pode imaginar uma mensagem de erro útil sendo apresentada ao usuário final. Este é o "e o que você fará quando isso acontecer?" questão que levantei acima. Ele também se refere ao item 1. Como você pode prever que algo fora do sistema da API do cliente pode fazer com que o arquivo não esteja lá, você pode razoavelmente informar o usuário sobre ele:
"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 FileNotFoundException
que a exceção verificada é uma parte tão importante e útil do contrato da API quanto os tipos de parâmetro. Portanto, não devemos dispensá-lo apenas porque é mal utilizado.
Desculpe, não pretendia fazer isso tão longo e waffly. Deixe-me terminar com duas sugestões:
R: Programadores de API: use as exceções verificadas com moderação para preservar sua utilidade. Em caso de dúvida, use uma exceção desmarcada.
B: Programadores clientes: adquira o hábito de criar uma exceção agrupada (pesquise no google) logo no início do seu desenvolvimento. O JDK 1.4 e posterior fornecem um construtor RuntimeException
para isso, mas você também pode criar o seu próprio facilmente. Aqui está o construtor:
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) */}