TL; DR
Premissa
- As exceções de tempo de execução devem ser lançadas quando o erro é irrecuperável: quando o erro está no código e não depende do estado externo (portanto, a recuperação estaria corrigindo o código).
- As exceções marcadas devem ser lançadas quando o código estiver correto, mas o estado externo não for o esperado: nenhuma conectividade de rede, arquivo não encontrado ou corrompido etc.
Conclusão
Podemos repetir uma exceção verificada como uma exceção de tempo de execução se o código de propagação ou interface assumir que a implementação subjacente depende do estado externo, quando claramente não.
Esta seção discute o tópico de quando uma das exceções deve ser lançada. Você pode pular para a próxima barra horizontal se quiser ler uma explicação mais detalhada para a conclusão.
Quando é apropriado lançar uma exceção de tempo de execução? Você lança uma exceção de tempo de execução quando fica claro que o código está incorreto e que a recuperação é apropriada modificando o código.
Por exemplo, é apropriado lançar uma exceção de tempo de execução para o seguinte:
float nan = 1/0;
Isso gerará uma divisão por exceção de zero tempo de execução. Isso é apropriado porque o código está com defeito.
Ou, por exemplo, aqui está uma parte do HashMap
construtor:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// more irrelevant code...
}
Para corrigir a capacidade inicial ou o fator de carga, é apropriado que você edite o código para garantir que os valores corretos sejam transmitidos. Não depende de algum servidor distante estar ativo, no estado atual do disco, um arquivo ou outro programa. O construtor que está sendo chamado com argumentos inválidos depende da correção do código de chamada, seja um cálculo incorreto que levou a parâmetros inválidos ou fluxo inadequado que perdeu um erro.
Quando é apropriado lançar uma exceção marcada? Você lança uma exceção verificada quando o problema é recuperável sem alterar o código. Ou, em termos diferentes, você lança uma exceção verificada quando o erro está relacionado ao estado enquanto o código está correto.
Agora, a palavra "recuperar" pode ser complicada aqui. Isso pode significar que você encontra outra maneira de atingir a meta: por exemplo, se o servidor não responder, tente o próximo servidor. Se esse tipo de recuperação é possível para o seu caso, isso é ótimo, mas essa não é a única coisa que significa recuperação - a recuperação pode estar simplesmente exibindo uma caixa de diálogo de erro para o usuário que explica o que aconteceu ou, se for um aplicativo de servidor, pode ser enviando um email para o administrador ou simplesmente registrando o erro de forma adequada e concisa.
Vamos pegar o exemplo mencionado na resposta dos mrmuggles:
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}
Essa não é a maneira correta de lidar com a exceção verificada. A simples incapacidade de lidar com a exceção no escopo deste método não significa que o aplicativo deve ser travado. Em vez disso, é apropriado propagá-lo para um escopo mais alto como este:
public Data dataAccessCode() throws SQLException {
// some code that communicates with the database
}
O que permite a possibilidade de recuperação pelo chamador:
public void loadDataAndShowUi() {
try {
Data data = dataAccessCode();
showUiForData(data);
} catch(SQLException e) {
// Recover by showing an error alert dialog
showCantLoadDataErrorDialog();
}
}
As exceções verificadas são uma ferramenta de análise estática; elas deixam claro para um programador o que poderia dar errado em uma determinada chamada sem exigir que eles aprendam a implementação ou passem por um processo de tentativa e erro. Isso facilita garantir que nenhuma parte do fluxo de erros seja ignorada. A reconfiguração de uma exceção verificada como uma exceção de tempo de execução está funcionando contra esse recurso de análise estática que economiza trabalho.
Também vale a pena mencionar que a camada de chamada tem um contexto melhor do esquema maior de coisas, como foi demonstrado acima. Pode haver muitas causas para que dataAccessCode
isso seja chamado, o motivo específico da chamada é visível apenas para o chamador - portanto, ele pode tomar uma decisão melhor na recuperação correta em caso de falha.
Agora que esclarecemos essa distinção, podemos deduzir quando está correto repetir uma exceção verificada como uma exceção de tempo de execução.
Dado o exposto, quando é apropriado repetir novamente uma exceção verificada como uma RuntimeException? Quando o código que você está usando assume dependência do estado externo, mas você pode afirmar claramente que ele não depende do estado externo.
Considere o seguinte:
StringReader sr = new StringReader("{\"test\":\"test\"}");
try {
doesSomethingWithReader(sr); // calls #read, so propagates IOException
} catch (IOException e) {
throw new IllegalStateException(e);
}
Neste exemplo, o código está se propagando IOException
porque a API de Reader
foi projetada para acessar o estado externo; no entanto, sabemos que a StringReader
implementação não acessa o estado externo. Nesse escopo, onde certamente podemos afirmar que as partes envolvidas na chamada não acessam o IO ou qualquer outro estado externo, podemos retroceder com segurança a exceção como uma exceção de tempo de execução, sem surpreender os colegas que desconhecem nossa implementação (e possivelmente assumindo que o código de acesso IO gerará um IOException
).
O motivo para manter estritamente as exceções dependentes do estado externo verificadas é que elas não são determinísticas (diferentemente das exceções dependentes da lógica, que serão reproduzidas previsivelmente todas as vezes para uma versão do código). Por exemplo, se você tentar dividir por 0, sempre produzirá uma exceção. Se você não dividir por 0, nunca produzirá uma exceção e não precisará lidar com esse caso de exceção, porque isso nunca acontecerá. No entanto, no caso de acessar um arquivo, obter êxito uma vez não significa que você terá êxito na próxima vez - o usuário pode ter alterado as permissões, outro processo pode ter sido excluído ou modificado. Portanto, você sempre precisa lidar com esse caso excepcional ou provavelmente terá um bug.