Pergunta

Ao analisar algum código legado com FXCop, ocorreu-me se é realmente ruim capturar um erro de exceção geral dentro de um bloco try ou se você está procurando por uma exceção específica.Pensamentos em um cartão postal, por favor.

Foi útil?

Solução

Obviamente esta é uma daquelas questões onde a única resposta real é “depende”.

A principal coisa que depende é de onde você está capturando a exceção.Em geral, as bibliotecas devem ser mais conservadoras na captura de exceções, enquanto no nível superior do seu programa (por exemplo,no seu método principal ou no topo do método de ação em um controlador, etc.) você pode ser mais liberal com o que captura.

A razão para isso é que, por ex.você não deseja capturar todas as exceções em uma biblioteca porque pode mascarar problemas que não têm nada a ver com sua biblioteca, como "OutOfMemoryException", que você realmente prefere que apareçam bolhas para que o usuário possa ser notificado, etc.Por outro lado, se você está falando sobre capturar exceções dentro do seu método main() que captura a exceção, a exibe e depois sai...bem, provavelmente é seguro capturar qualquer exceção aqui.

A regra mais importante sobre como capturar todas as exceções é que você nunca deve engolir todas as exceções silenciosamente...por exemplo.algo assim em Java:

try { 
    something(); 
} catch (Exception ex) {}

ou isto em Python:

try:
    something()
except:
    pass

Porque esses podem ser alguns dos problemas mais difíceis de rastrear.

Uma boa regra é que você só deve capturar exceções com as quais possa lidar adequadamente.Se você não consegue lidar com a exceção completamente, deixe-a chegar a alguém que possa.

Outras dicas

A menos que você esteja registrando e limpando o código no front-end do seu aplicativo, acho que é ruim capturar todas as exceções.

Minha regra básica é capturar todas as exceções que você espera e qualquer outra coisa é um bug.

Se você pegar tudo e continuar, é como colocar um esparadrapo sobre a luz de advertência no painel do seu carro.Você não consegue mais ver, mas não significa que está tudo bem.

Sim!(exceto no "topo" da sua aplicação)

Ao capturar uma exceção e permitir que a execução do código continue, você afirma que sabe como lidar e contornar ou corrigir um problema específico.Você está afirmando que isso é uma situação recuperável.Capturar Exception ou SystemException significa que você detectará problemas como erros de IO, erros de rede, erros de falta de memória, erros de código ausente, desreferenciação de ponteiro nulo e similares.É uma mentira dizer que você pode lidar com isso.

Em uma aplicação bem organizada, esses problemas irrecuperáveis ​​devem ser tratados no topo da pilha.

Além disso, conforme o código evolui, você não quer que sua função capture uma nova exceção adicionada no futuro para um método chamado.

Na minha opinião, você deve capturar todas as exceções que você esperar, mas esta regra se aplica a tudo menos à lógica da sua interface.Em toda a pilha de chamadas, você provavelmente deve criar uma maneira de capturar todas as exceções, fazer algum registro/fornecer feedback ao usuário e, se necessário e possível, encerrar normalmente.

Nada é pior do que um aplicativo travando com algum stacktrace hostil ao usuário despejado na tela.Isso não apenas fornece informações (talvez indesejadas) sobre o seu código, mas também confunde o usuário final e, às vezes, até o assusta para um aplicativo concorrente.

Tem havido muitas discussões filosóficas (mais como argumentos) sobre esta questão.Pessoalmente, acredito que a pior coisa que você pode fazer é engolir exceções.O próximo pior é permitir que uma exceção chegue à superfície, onde o usuário recebe uma tela desagradável cheia de bobagens técnicas.

A questão é dupla, eu acho.

Em primeiro lugar, se você não sabe qual exceção ocorreu, como pode esperar se recuperar dela?Se você espera que um usuário digite um nome de arquivo errado, você pode esperar uma FileNotFoundException e pedir ao usuário para tentar novamente.Se esse mesmo código gerasse uma NullReferenceException e você simplesmente dissesse ao usuário para tentar novamente, ele não saberia o que aconteceu.

Em segundo lugar, as diretrizes do FxCop se concentram no código da Biblioteca/Framework - nem todas as suas regras são projetadas para serem aplicáveis ​​a sites EXE ou ASP.Net.Portanto, é bom ter um manipulador de exceções global que registre todas as exceções e saia do aplicativo de maneira adequada.

O problema de capturar todas as exceções é que você pode estar capturando aquelas que não espera, ou mesmo aquelas que deveria não estar pegando.O fato é que uma exceção de qualquer tipo indica que algo deu errado e você precisa resolver o problema antes de continuar, caso contrário poderá acabar com problemas de integridade de dados e outros bugs que não são tão fáceis de rastrear.

Para dar um exemplo, em um projeto implementei um tipo de exceção chamado CriticalException.Isso indica uma condição de erro que requer intervenção dos desenvolvedores e/ou equipe administrativa, caso contrário, os clientes serão cobrados incorretamente ou poderão ocorrer outros problemas de integridade de dados.Também pode ser usado em outros casos semelhantes, quando apenas registrar a exceção não for suficiente e um alerta por e-mail precisar ser enviado.

Outro desenvolvedor que não entendeu corretamente o conceito de exceções envolveu algum código que poderia potencialmente lançar essa exceção em um bloco try...catch genérico que descartou todas as exceções.Felizmente, eu descobri isso, mas poderia ter resultado em problemas sérios, especialmente porque o caso "muito incomum" que ele deveria detectar acabou sendo muito mais comum do que eu esperava.

Portanto, em geral, capturar exceções genéricas é ruim, a menos que você tenha 100% de certeza de que sabe exatamente quais tipos de exceções serão lançadas e sob quais circunstâncias.Em caso de dúvida, deixe-os passar para o manipulador de exceções de nível superior.

Uma regra semelhante aqui é nunca lançar exceções do tipo System.Exception.Você (ou outro desenvolvedor) pode querer capturar sua exceção específica no topo da pilha de chamadas enquanto permite que outras pessoas passem.

(Há um ponto a ser observado, no entanto.No .NET 2.0, se um thread encontrar alguma exceção não detectada, ele descarrega todo o domínio do aplicativo.Portanto, você deve agrupar o corpo principal de um thread em um bloco try...catch genérico e passar quaisquer exceções capturadas lá para seu código global de tratamento de exceções.)

Bem, não vejo diferença entre capturar uma exceção geral ou específica, exceto que, ao ter vários blocos catch, você pode reagir de maneira diferente dependendo de qual é a exceção.

Concluindo, você pegará os dois IOException e NullPointerException com um genérico Exception, mas a maneira como seu programa deve reagir provavelmente é diferente.

Eu gostaria de bancar o advogado do diabo para capturar Exception, registrá-lo e relança-lo.Isso pode ser necessário se, por exemplo, você estiver em algum lugar do código e ocorrer uma exceção inesperada, você pode capturá-la, registrar informações de estado significativas que não estariam disponíveis em um rastreamento de pilha simples e, em seguida, relançá-las nas camadas superiores para tratar.

Existem dois casos de uso completamente diferentes.O primeiro é aquele em que a maioria das pessoas está pensando, colocar um try/catch em alguma operação que requer uma exceção verificada.Isto não deve ser um problema de forma alguma.

A segunda, entretanto, é impedir que seu programa seja interrompido quando ele poderia continuar.Esses casos são:

  • O topo de todos os threads (por padrão, as exceções desaparecerão sem deixar rastros!)
  • Dentro de um loop de processamento principal que você espera que nunca saia
  • Dentro de um Loop processando uma lista de objetos onde uma falha não deveria impedir outras
  • Parte superior do thread "principal" - Você pode controlar uma falha aqui, como despejar alguns dados no stdout quando ficar sem memória.
  • Se você tiver um "Runner" que executa código (por exemplo, se alguém adicionar um ouvinte a você e você chamar o ouvinte), ao executar o código, você deverá capturar Exception para registrar o problema e continuar notificando outros ouvintes.

Nesses casos, você SEMPRE deseja capturar Exception (talvez até Throwable às vezes) para capturar erros de programação/inesperados, registrá-los e continuar.

Acho que uma boa diretriz é capturar apenas exceções específicas de dentro de uma estrutura (para que o aplicativo host possa lidar com casos extremos, como o preenchimento do disco, etc.), mas não vejo por que não deveríamos ser capazes de capturar todos exceções do nosso código de aplicação.Simplesmente, há momentos em que você não quer que o aplicativo trave, não importa o que possa dar errado.

Na maioria das vezes, não é necessário capturar uma exceção geral.Claro que há situações em que você não tem escolha, mas neste caso acho melhor verificar por que você precisa pegá-lo.Talvez haja algo errado em seu design.

Pegando uma exceção geral, sinto que é como segurar uma banana de dinamite dentro de um prédio em chamas e apagar a detonação.Ajuda por um tempo, mas a dinamite explodirá de qualquer maneira depois de um tempo.

É claro que pode haver situações em que seja necessária a captura de uma exceção geral, mas apenas para fins de depuração.Erros e bugs devem ser corrigidos, não ocultos.

Para minha classe IabManager, que usei com faturamento no aplicativo (do exemplo online do TrivialDrive), percebi que às vezes lidava com muitas exceções.Chegou ao ponto em que era imprevisível.

Percebi que, contanto que eu parasse de tentar consumir um produto no aplicativo após ocorrer uma exceção, que é onde a maioria das exceções aconteceria (no consumo, em vez de na compra), eu estaria seguro.

Acabei de alterar todas as exceções para uma exceção geral e agora não preciso me preocupar com o lançamento de outras exceções aleatórias e imprevisíveis.

Antes:

    catch (final RemoteException exc)
    {
        exc.printStackTrace();
    }
    catch (final IntentSender.SendIntentException exc)
    {
        exc.printStackTrace();
    }
    catch (final IabHelper.IabAsyncInProgressException exc)
    {
        exc.printStackTrace();
    }
    catch (final NullPointerException exc)
    {
        exc.printStackTrace();
    }
    catch (final IllegalStateException exc)
    {
        exc.printStackTrace();
    }

Depois:

    catch (final Exception exc)
    {
        exc.printStackTrace();
    }

Opinião impopular:Na verdade.

Capture todos os erros dos quais você pode se recuperar de forma significativa.Às vezes são todos eles.

Na minha experiência, é mais importante onde a exceção veio de onde a exceção é realmente lançada.Se você mantiver suas exceções em espaços apertados, normalmente não engolirá nada que de outra forma seria útil.A maioria das informações codificadas no tipo de erro são informações auxiliares, então você geralmente acaba capturando efetivamente todos deles de qualquer maneira (mas agora você precisa consultar a documentação da API para obter o conjunto total de exceções possíveis).

Tenha em mente que algumas exceções que devem aparecer no topo em quase todos os casos, como a do Python KeyboardInterrupt e SystemExit.Felizmente para o Python, eles são mantidos em um ramo separado da hierarquia de exceções, então você pode deixá-los crescer capturando Exception.Uma hierarquia de exceções bem projetada torna esse tipo de coisa realmente simples.

O principal momento em que capturar exceções gerais causará sérios problemas é quando se lida com recursos que precisam ser limpos (talvez em um finally cláusula), já que um manipulador abrangente pode facilmente perder esse tipo de coisa.Felizmente, isso não é realmente um problema para idiomas com defer, construções como as do Python with, ou RAII em C++ e Rust.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top