Tentará / Finalmente (sem o Catch) bolha a exceção?

115

Tenho quase certeza de que a resposta é SIM. Se eu usar um bloco Try Finalmente, mas não usar um bloco Catch, então qualquer exceção VAI surgir. Corrigir?

Alguma opinião sobre a prática em geral?

Seth

Seth Spearman
fonte

Respostas:

130

Sim, com certeza vai. Assumindo que seu finallybloco não lance uma exceção, é claro, caso em que isso efetivamente "substituirá" aquele que foi originalmente lançado.

Jon Skeet
fonte
13
@David: Você não pode retornar de um bloco finally em C #.
Jon Skeet
3
A documentação do msdn também confirma esta resposta: Como alternativa, você pode capturar a exceção que pode ser lançada no bloco try de uma instrução try-finally no alto da pilha de chamadas. Ou seja, você pode capturar a exceção no método que chama o método que contém a instrução try-finally, ou no método que chama esse método, ou em qualquer método na pilha de chamadas. Se a exceção não for detectada, a execução do bloco finally depende se o sistema operacional escolhe acionar uma operação de desenrolamento da exceção.
banda larga de
60

Alguma opinião sobre a prática em geral?

Sim. Cuidado . Quando o seu bloco finally está em execução, é inteiramente possível que esteja em execução porque uma exceção inesperada e não tratada foi lançada . Isso significa que algo está quebrado e algo completamente inesperado pode estar acontecendo.

Nessa situação, pode-se argumentar que você não deve executar o código em blocos finally. O código no bloco finally pode ser construído para assumir que os subsistemas dos quais ele depende estão íntegros, quando na verdade eles podem estar profundamente corrompidos. O código no bloco finally pode piorar as coisas.

Por exemplo, costumo ver este tipo de coisa:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

O autor deste código está pensando "Estou fazendo uma mutação temporária no estado do mundo; preciso restaurar o estado ao que era antes de ser chamado". Mas vamos pensar em todas as maneiras como isso pode dar errado.

Primeiro, o acesso ao recurso já pode ser desabilitado pelo chamador; nesse caso, este código o reativa, possivelmente prematuramente.

Em segundo lugar, se DoSomethingToTheResource lançar uma exceção, é a coisa certa a fazer para permitir o acesso ao recurso ??? O código que gerencia o recurso é quebrado inesperadamente . Este código diz, com efeito, "se o código de gerenciamento for quebrado, certifique-se de que outro código possa chamar esse código quebrado o mais rápido possível, para que ele também possa falhar terrivelmente ." Isso parece uma má ideia.

Terceiro, se DoSomethingToTheResource lançar uma exceção, então como sabemos que EnableAccessToTheResource também não lançará uma exceção? Qualquer que seja o problema com o uso do recurso, também pode afetar o código de limpeza, caso em que a exceção original será perdida e o problema será mais difícil de diagnosticar.

Eu tendo a escrever código assim sem usar blocos try-finally:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

Agora, o estado não sofre mutação, a menos que seja necessário. Agora, o estado do chamador não é alterado. E agora, se DoSomethingToTheResource falhar, não reativamos o acesso. Presumimos que algo está profundamente danificado e não corremos o risco de piorar a situação tentando manter o código em execução. Deixe o chamador lidar com o problema, se puder.

Então, quando é uma boa ideia executar um bloco finally? Primeiro, quando a exceção é esperada. Por exemplo, você pode esperar que uma tentativa de bloquear um arquivo possa falhar, porque outra pessoa o bloqueou. Nesse caso, faz sentido capturar a exceção e relatá-la ao usuário. Nesse caso, a incerteza sobre o que está quebrado é reduzida; é improvável que você piore as coisas limpando.

Em segundo lugar, quando o recurso que você está limpando é um recurso escasso do sistema. Por exemplo, faz sentido fechar um identificador de arquivo em um bloco finally. (Um "uso" é, obviamente, apenas outra maneira de escrever um bloco try-finally.) O conteúdo do arquivo pode estar corrompido, mas não há nada que você possa fazer sobre isso agora. O identificador de arquivo será fechado eventualmente, então pode ser mais cedo ou mais tarde.

Eric Lippert
fonte
7
Ainda mais exemplos de como ainda não atuamos juntos como indústria quando se trata de tratamento de erros. Não que eu tenha algo melhor a sugerir do que exceções, mas espero que o futuro traga algo mais provável de levar à tomada de ação certa com razoável facilidade.
Jon Skeet
No vb.net, é possível para o código em um bloco finally saber qual exceção está pendente. Se alguém configurar uma hierarquia de exceção para distinguir "não fez isso; estado de outra forma ok" de "estado está quebrado", pode-se ter um bloco Finalmente executado se a exceção pendente não for uma das ruins. Eu gosto de ter rotinas de eliminação / limpeza capturando exceções e lançando uma DisposerFailedException (que está na categoria "ruim" e inclui a exceção original como InnerException). Gostaria de ver uma interface iDisposableEx padrão com um Dispose (Ex as Exception) para facilitar isso.
supercat
Embora eu reconheça que há casos em que uma exceção pode ocorrer enquanto um objeto está em um estado ruim e a tentativa de limpeza piorará as coisas, acho que esses problemas devem ser tratados no código de limpeza, possivelmente com a ajuda de delegados. Um wrapper de bloqueio, por exemplo, poderia expor um delegado de função "CheckIfSafeToUnlock (Ex as Exception)", que poderia ser definido quando o objeto bloqueado estivesse em um estado inválido e limpo caso contrário. Antes de liberar o bloqueio, o wrapper pode verificar o delegado; se não estiver vazio, ele pode executá-lo e liberar o bloqueio apenas se retornar verdadeiro.
supercat
Ter um wrapper usando tal delegado permitiria ao código que manipulou o estado do objeto selecionar a ação de limpeza apropriada se ele for interrompido. Essas ações podem incluir reparar a estrutura de dados e retornar True, lançar outra exceção "mais grave" que envolve a original ou deixar a exceção original se infiltrar enquanto retorna False.
supercat
2
Eric, obrigado por sua ótima resposta. Acho que deveria ter feito duas perguntas porque você e Jon estão corretos. Em qualquer caso, você recebeu votos positivos. Obrigado pela sua atenção à questão.
Seth Spearman