Lançando uma exceção dentro finalmente


27

Analisadores de código estático como o Fortify "reclamam" quando uma exceção pode ser lançada dentro de um finallybloco, 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 writernã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 writere 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 finallybloco?


4
Se for Java e você puder usar o Java 7, verifique se os blocos ARM podem resolver seu problema.
Landei

@Landei, isso resolve-lo, mas, infelizmente, nós não estamos usando Java 7.
SuperM

Eu diria que o código que você mostrou não é "Usando uma instrução throw dentro de um bloco finalmente" e, como tal, a progressão lógica é ótima.
Mike

@ Mike, usei o resumo padrão que o Fortify mostra, mas direta ou indiretamente, há uma exceção lançada finalmente.
SuperM

Infelizmente, o bloco try-with-resources também é detectado pelo Fortify como uma exceção finalmente lançada .. é muito inteligente, caramba .. ainda não sabe como superá-lo, parecia que a razão do try-with-resources era garantir o fechamento do recursos, finalmente, e agora cada tal afirmação é relatado por Fortify como uma ameaça à segurança ..
mmona

Respostas:


18

Basicamente, finallyexistem 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 no finallybloco o ocultará. Parece que o erro foi causado pela chamada close, 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 finallybloco.

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

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


1
+1 Embora eu esteja entre aqueles que usam esse código desagradável. Mas, para minha justificativa, direi que nem sempre faço isso, apenas quando a exceção não é crítica.
SuperM

2
Eu me vejo fazendo isso também. Algumas vezes, a criação de qualquer gerenciador de recursos inteiro (anônimo ou não) prejudica o objetivo do código.
Travis Parks1

+1 de mim também; você basicamente disse exatamente o que eu estava indo, mas melhor. É importante notar que algumas das implementações do Stream em Java não podem realmente lançar Exceções em close (), mas a interface declara isso porque algumas delas o fazem. Portanto, em algumas circunstâncias, você pode adicionar um bloco de captura que nunca será realmente necessário.
precisa saber é o seguinte

1
O que há de tão desagradável em usar um bloco try / catch dentro de um bloco finalmente? Prefiro fazer isso do que inchar todo o meu código, tendo que quebrar todas as minhas conexões e fluxos, etc., para que possam ser fechados silenciosamente - o que, por si só, introduz um novo problema de não saber se o fechamento de uma conexão ou fluxo ( ou qualquer outro) causa uma exceção - que é algo que você pode realmente gostaria de saber sobre ou fazer algo sobre ...
ban-geoengenharia

10

O que Travis Parks disse é verdade que as exceções no finallybloco consumirão quaisquer valores de retorno ou exceções dos try...catchblocos.

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

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

Para

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


1
Isso às vezes é útil. Mas no caso em que eu realmente preciso lançar uma exceção quando o arquivo não puder ser fechado, essa abordagem oculta os problemas.
SuperM

1
@superM Na verdade, tentar com recursos não esconde uma exceção 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 do close()método será suprimida somente quando outra exceção será lançada do bloco try / catch. Portanto, se o trybloco não lançar nenhuma exceção, a exceção do close()método será lançada (e o valor retornado não será retornado). Pode até ser capturado no catchbloco atual .
Ruslan Stelmachenko

0

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.


0

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, finallycaptura a lógica que é executada no meio da recuperação de um erro, antes de ser totalmente recuperada (antes de chegarmos ao nosso catchdestino).

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 finallybloco. 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 finallyblocos 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 SomeFileWriterpuder jogar dentro close, 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.

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.