Silenciar IOException de close() é uma “solução” perigosa

Compartilhar/Favoritos

Após estudar um pouco para o post anterior, comecei a pensar sobre como tratamos nossos streams e conexões. No post, abordei o Automatic Resource Management do Java 7. Um padrão bastante comum ao lidar com fechamento de resources é o seguinte:

public void closeQuietly(Closeable out) {
    try {
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
 
public void executa() throws IOException {
    MyOutputStream out = new MyOutputStream();
    try {
        out.write(new byte[1]);
    } finally {
        closeQuietly(out);
    }
}

Ótimo! Este padrão resolve o problema de uma exceção no close silenciar outra no corpo do try, mas causa um problema potencialmente catastrófico: o que acontece se ocorrer uma exceção apenas dentro do close e ela for grave?

Vejamos um exemplo mais interessante:

public static boolean moveFile(File source, File dest)
{
    boolean ok = false;                                                          
 
    if ((dest.exists() && dest.canWrite())
        || (!dest.exists() && dest.getParentFile().canWrite()))
        {
            OutputStream fos = null;
            InputStream fis = null;
            try
            {
                fos = new FileOutputStream(dest);
                fis = new FileInputStream(source);
                ok = copyStream(32768,null,fis,fos,false);
            }
            catch (IOException ioe)
            {
                Log.log(Log.WARNING, IOUtilities.class,
                        "Error moving file: " + ioe + " : " + ioe.getMessage());
            }
            finally
            {
                closeQuietly(fos);
                closeQuietly(fis);
            }                                                                    
 
            if(ok)
                source.delete();
        }
    return ok;
}

Para não dizer que este padrão é coisa da minha cabeça, usei um exemplo real. O trecho acima foi pego do projeto jEdit (certamente já ouviu falar) o primeiro projeto onde procurei o padrão. Vamos dar uma analisada nele.

Notem que o método faz uma cópia de um arquivo para outro local e em seguida apaga a origem. Os desenvolvedores tomaram cuidado para apagar a origem apenas se o método copyStream for bem sucedido. Não vou colar aqui o copyStream, mas acredite que ele apenas faz a cópia entre dois streams e retorna true se o processo não for interrompido por qualquer motivo (o botão Cancelar). Vamos então supor que a cópia foi bem sucedida e a execução chegou no início do bloco finally. Como o stream destino ainda está aberto, nada garante que os dados escritos no stream já foram para o disco. Neste momento, algo de errado acontece com o dispositivo de destino (o cabo do HD é retirado, o pen drive é desmontado ou puxado, qualquer coisa).

O que ocorrerá a seguir é assustador. Os métodos close são executados e o correspondente ao arquivo de destino falha, mas nada é apresentado. A variável ok continua true… O arquivo de origem então é apagado… Pronto, agora você está sem o arquivo de origem e sem a cópia do arquivo de destino e o aplicativo acha que está tudo bem…. Mas não está – e você só vai descobrir quando tentar ler o seu arquivo.

Felizmente este problema só ocorre quando algo dá errado entre a última operação com o stream e o close, o que deve ser bem raro. Algo que pode ser feito para minimizar este risco é executar flush() como última operação antes do finally.

You may also like...

Deixe uma resposta

O seu endereço de email não será publicado Campos obrigatórios são marcados *

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Translate »