Analisadores de código estático como o Fortify "reclamam" quando uma exceção pode ser lançada dentro de um finally
bloco, dizendo isso Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally
. Normalmente eu concordo com isso. Mas recentemente me deparei com este código:
SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} catch (...) {
//exception handling
} finally {
if (writer!= null) writer.close();
}
Agora, se o writer
não puder ser fechado corretamente, o writer.close()
método lançará uma exceção. Uma exceção deve ser lançada porque (provavelmente) o arquivo não foi salvo após a gravação.
Eu poderia declarar uma variável extra, defini-la se houvesse um erro ao fechar o writer
e lançar uma exceção após o bloco finalmente. Mas esse código funciona bem e não tenho certeza se o altero ou não.
Quais são as desvantagens de lançar uma exceção dentro do finally
bloco?
Respostas:
Basicamente,
finally
existem cláusulas para garantir a liberação adequada de um recurso. No entanto, se uma exceção for lançada dentro do bloco final, essa garantia desaparecerá. Pior, se o seu bloco de código principal lançar uma exceção, a exceção gerada nofinally
bloco o ocultará. Parece que o erro foi causado pela chamadaclose
, não pelo motivo real.Algumas pessoas seguem um padrão desagradável de manipuladores de exceção aninhados, engolindo quaisquer exceções lançadas no
finally
bloco.Nas versões mais antigas do Java, você pode "simplificar" esse código agrupando recursos em classes que fazem essa limpeza "segura" para você. Um bom amigo meu cria uma lista de tipos anônimos, cada um deles fornecendo a lógica para limpar seus recursos. Então, seu código simplesmente percorre a lista e chama o método de descarte dentro do
finally
bloco.fonte
O que Travis Parks disse é verdade que as exceções no
finally
bloco consumirão quaisquer valores de retorno ou exceções dostry...catch
blocos.Se você estiver usando Java 7, no entanto, o problema pode ser resolvido usando um bloco try-with-resources . De acordo com os documentos, desde que o recurso seja implementado
java.lang.AutoCloseable
(a maioria dos escritores / leitores de bibliotecas o faz agora), o bloco try-with-resources o fechará para você. O benefício adicional aqui é que qualquer exceção que ocorra durante o fechamento será suprimida, permitindo que o valor de retorno original ou a exceção passem.A partir de
Para
http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
fonte
close()
. "qualquer exceção que ocorra durante o fechamento será suprimida, permitindo que o valor de retorno original ou a exceção passem" - isso simplesmente não é verdade . Uma exceção doclose()
método será suprimida somente quando outra exceção será lançada do bloco try / catch. Portanto, se otry
bloco não lançar nenhuma exceção, a exceção doclose()
método será lançada (e o valor retornado não será retornado). Pode até ser capturado nocatch
bloco atual .Eu acho que isso é algo que você precisará abordar caso a caso. Em alguns casos, o que o analisador está dizendo está correto, pois o código que você possui não é tão bom e precisa ser repensado. Mas pode haver outros casos em que jogar ou até voltar a jogar pode ser a melhor coisa. Não é algo que você pode ordenar.
fonte
Essa será uma resposta conceitual para o porquê desses avisos existirem, mesmo com abordagens de tentativa com recursos. Infelizmente, também não é o tipo fácil de solução que você espera obter.
A recuperação de erros não pode falhar
finally
modela um fluxo de controle pós-transação que é executado independentemente de uma transação ser bem-sucedida ou falhar.No caso de falha,
finally
captura a lógica que é executada no meio da recuperação de um erro, antes de ser totalmente recuperada (antes de chegarmos ao nossocatch
destino).Imagine o problema conceitual que ele apresenta para encontrar um erro no meio da recuperação de um erro.
Imagine um servidor de banco de dados em que estamos tentando confirmar uma transação, e ela falha no meio (digamos que o servidor ficou sem memória no meio). Agora, o servidor deseja reverter a transação para um ponto como se nada tivesse acontecido. No entanto, imagine que ele encontre outro erro no processo de reversão. Agora, acabamos tendo uma transação semi-confirmada no banco de dados - a atomicidade e a natureza indivisível da transação estão quebradas e a integridade do banco de dados agora será comprometida.
Esse problema conceitual existe em qualquer linguagem que lide com erros, seja C com propagação manual de código de erro, C ++ com exceções e destruidores ou Java com exceções e
finally
.finally
não pode falhar em linguagens que o fornecem da mesma maneira que os destruidores não podem falhar no C ++ no processo de encontrar exceções.A única maneira de evitar esse problema conceitual e difícil é garantir que o processo de reverter transações e liberar recursos no meio não possa encontrar uma exceção / erro recursivo.
Portanto, o único design seguro aqui é um design em
writer.close()
que não pode falhar. Geralmente, existem maneiras no design de evitar cenários em que essas coisas podem falhar no meio da recuperação, tornando impossível.Infelizmente, é a única maneira - a recuperação de erros não pode falhar. A maneira mais fácil de garantir isso é tornar esses tipos de funções de "liberação de recursos" e "efeitos colaterais reversos" incapazes de falhar. Não é fácil - a recuperação de erros adequada é difícil e, infelizmente, também é difícil de testar. Mas o caminho para alcançá-lo é garantir que quaisquer funções que "destruam", "fechem", "desliguem", "recuperem" etc. etc. não possam encontrar um erro externo no processo, pois essas funções geralmente precisam ser executadas. ser chamado no meio da recuperação de um erro existente.
Exemplo: Log
Digamos que você queira registrar coisas dentro de um
finally
bloco. Isso costuma ser um problema enorme, a menos que o log não possa falhar . O log quase certamente pode falhar, pois pode querer acrescentar mais dados ao arquivo e isso pode facilmente encontrar muitos motivos para falhar.Portanto, a solução aqui é fazer com que qualquer função de log usada em
finally
blocos não seja lançada para o chamador (pode falhar, mas não será lançada). Como podemos fazer isso? Se o seu idioma permitir a execução dentro do contexto de, finalmente, desde que exista um bloco try / catch aninhado, essa seria uma maneira de evitar lançar para o chamador engolindo exceções e transformando-as em códigos de erro, por exemplo, talvez o registro possa ser feito em um separado processo ou encadeamento que pode falhar separadamente e fora de uma pilha de recuperação de erro existente, desenrola-se. Contanto que você possa se comunicar com esse processo sem a possibilidade de encontrar um erro, isso também seria seguro para exceções, pois o problema de segurança só existe nesse cenário se estivermos lançando recursivamente dentro do mesmo encadeamento.Nesse caso, podemos nos safar da falha de registro, desde que ela não atire, já que deixar de registrar e não fazer nada não é o fim do mundo (não está vazando nenhum recurso ou não revertendo efeitos colaterais, por exemplo).
De qualquer forma, tenho certeza de que você já pode começar a imaginar como é incrivelmente difícil tornar verdadeiramente um software seguro contra exceções. Pode não ser necessário procurar isso ao máximo em todos os softwares menos críticos para a missão. Mas vale a pena tomar nota de como realmente obter segurança de exceção, já que mesmo os autores de bibliotecas de propósito geral geralmente se atrapalham aqui e destroem toda a segurança de exceção de seu aplicativo usando a biblioteca.
SomeFileWriter
Se
SomeFileWriter
puder jogar dentroclose
, eu diria que geralmente é incompatível com o tratamento de exceções, a menos que você nunca tente fechá-lo em um contexto que envolva a recuperação de uma exceção existente. Se o código estiver fora do seu controle, poderemos ser SOL, mas vale a pena notificar os autores desse flagrante problema de segurança de exceção. Se estiver sob seu controle, minha principal recomendação é garantir que o fechamento não possa falhar por qualquer meio necessário.Imagine se um sistema operacional realmente falha ao fechar um arquivo. Agora, qualquer programa que tente fechar um arquivo ao desligar falhará . O que devemos fazer agora, basta manter o aplicativo aberto e no limbo (provavelmente não), apenas vazar o recurso do arquivo e ignorar o problema (talvez seja bom se não for tão crítico)? O design mais seguro: torne impossível fechar o arquivo.
fonte