Normalmente, concordo com a maioria dos avisos de análise de código e tento segui-los. No entanto, estou tendo mais dificuldade com este:
CA1031: Não captura tipos de exceção gerais
Eu entendo a lógica dessa regra. Mas, na prática, se eu quero executar a mesma ação, independentemente da exceção lançada, por que lidaria com cada uma delas especificamente? Além disso, se eu lidar com exceções específicas, e se o código que estou chamando for alterado para lançar uma nova exceção no futuro? Agora eu tenho que mudar meu código para lidar com essa nova exceção. Considerando que se eu simplesmente peguei Exception
meu código não precisa mudar.
Por exemplo, se Foo chama Bar e Foo precisa parar de processar, independentemente do tipo de exceção lançada por Bar, há alguma vantagem em ser específico sobre o tipo de exceção que estou capturando?
Talvez um exemplo melhor:
public void Foo()
{
// Some logic here.
LogUtility.Log("some message");
}
public static void Log()
{
try
{
// Actual logging here.
}
catch (Exception ex)
{
// Eat it. Logging failures shouldn't stop us from processing.
}
}
Se você não capturar uma exceção geral aqui, precisará capturar todos os tipos de exceção possíveis. Patrick tem um bom argumento que OutOfMemoryException
não deve ser tratado dessa maneira. E daí se eu quiser ignorar todas as exceções OutOfMemoryException
?
fonte
OutOfMemoryException
? O mesmo código de manipulação que tudo o resto?OutOfMemoryError
, que é separado daException
árvore de herança por essa mesma razãoRespostas:
Essas regras geralmente são uma boa idéia e, portanto, devem ser seguidas.
Mas lembre-se de que estas são regras genéricas. Eles não cobrem todas as situações. Eles cobrem as situações mais comuns. Se você tem uma situação específica e pode argumentar que sua técnica é melhor (e deve poder escrever um comentário no código para articular seu argumento para fazê-lo), faça-o (e faça uma revisão por pares).
No lado contrário da discussão.
Não vejo o seu exemplo acima como uma boa situação para isso. Se o sistema de registro estiver falhando (presumivelmente registrando outra exceção), provavelmente não quero que o aplicativo continue. Saia e imprima a exceção na saída para que o usuário possa ver o que aconteceu.
fonte
Sim, capturar exceções gerais é uma coisa ruim. Uma exceção geralmente significa que o programa não pode fazer o que você pediu.
Existem alguns tipos de exceções que você pode manipular:
Ah, e como regra geral: se você não souber o que fazer com uma exceção, se a capturar, é melhor simplesmente falhar rapidamente (passe a exceção para o chamador e deixe-o lidar com isso)
fonte
O loop externo mais alto deve ter um destes para imprimir tudo o que puder e, em seguida, sofrer uma morte horrível, violenta e barulhenta (como isso não deveria acontecer e alguém precisa ouvir).
Caso contrário, você geralmente deve ter muito cuidado, pois provavelmente não antecipou tudo o que poderia acontecer neste local e, portanto, provavelmente não o tratará corretamente. Seja o mais específico possível, para pegar apenas aqueles que sabe que vão acontecer e deixar que os que não foram vistos antes borbulhem até a morte barulhenta acima mencionada.
fonte
Não é que seja ruim, é apenas que capturas específicas são melhores. Quando você é específico, significa que você realmente entende, mais concretamente, o que seu aplicativo está fazendo e tem mais controle sobre ele. Em geral, se você se deparar com uma situação em que simplesmente captura uma exceção, registra e continua, provavelmente há algumas coisas ruins que estão acontecendo de qualquer maneira. Se você está capturando especificamente as exceções que sabe que um bloco ou método de código pode lançar, há uma probabilidade maior de recuperar, em vez de apenas registrar e esperar o melhor.
fonte
As duas possibilidades não são mutuamente exclusivas.
Em uma situação ideal, você capturaria todos os tipos possíveis de exceção que seu método poderia gerar, os trataria por exceção e, no final, adicionaria uma
catch
cláusula geral para capturar exceções futuras ou desconhecidas. Dessa forma, você obtém o melhor dos dois mundos.Lembre-se de que, para capturar as exceções mais específicas, você deve colocá-las em primeiro lugar.
Editar: por motivos declarados nos comentários, foi adicionada uma repetição na última captura.
fonte
Estive pensando sobre o mesmo recentemente, e minha conclusão preliminar é que a mera pergunta surge porque a hierarquia do .NET Exception está severamente bagunçada.
Pegue, por exemplo, o modesto
ArgumentNullException
que pode ser um candidato razoável para uma exceção que você não deseja capturar, porque tende a indicar um bug no código, em vez de um erro legítimo de tempo de execução. Ah sim. O mesmo acontece com aNullReferenceException
exceção de queNullReferenceException
derivaSystemException
diretamente, portanto, não há nenhum balde no qual você possa colocar todos os "erros lógicos" para capturar (ou não capturar).Depois, há o problema principal da IMNSHO deQuando você obtém umaSEHException
derivar (viaExternalException
)SystemException
e, assim, torná-la "normal"SystemException
SEHException
, deseja escrever um dump e terminar o mais rápido possível - e começando pelo .NET 4 pelo menos alguns As exceções SEH são consideradas Exceções de Estado Corruptas que não serão capturadas. Uma coisa boa e um fato que torna aCA1031
regra ainda mais inútil, porque agora o seu preguiçosocatch (Exception)
não pegará esses piores tipos de qualquer maneira.Então, parece que as outras coisas do Framework derivam de maneira inconsistente,
Exception
direta ou viaSystemException
, fazendo qualquer tentativa de agrupar cláusulas de captura por algo como a gravidade da gravidade.Há uma peça do Sr. Lippert da
C#
fama, chamada Vexing Exception , em que ele expõe algumas categorias úteis de exceções: você pode argumentar que deseja capturar apenas "exógenas", exceto ...C#
a linguagem e o design do .NET as exceções da estrutura tornam impossível "capturar apenas as exógenas" de maneira sucinta. (e, por exemplo,OutOfMemoryException
pode muito bem ser um erro recuperável totalmente normal para uma API que precisa alocar buffers de alguma forma grandes)O ponto principal para mim é que, da maneira como
C#
os blocos catch funcionam e como a hierarquia de exceções do Framework é projetada, a regraCA1031
está além de totalmente inútil . Ele finge para ajudar com o problema de raiz de "não engolir exceções", mas engolir exceções tem zero a ver com o que você pegar, mas com o que você , em seguida, fazer:Existem pelo menos quatro maneiras de lidar legitimamente com um pego
Exception
, eCA1031
apenas parece lidar superficialmente com um deles (a saber, o caso de re-lançamento).Como uma observação lateral, há um recurso do C # 6 chamado Filtros de Exceção que se tornará
CA1031
um pouco mais válido novamente desde então, você poderá filtrar corretamente as exceções que deseja capturar, e há menos motivos para escrever um filtro não filtradocatch (Exception)
.fonte
OutOfMemoryException
é que não existe uma maneira agradável de o código ter certeza de que "apenas" indica a falha de uma alocação específica que a pessoa estava preparada para falhar. É possível que alguma outra exceção mais séria tenha sido lançada e tenhaOutOfMemoryException
ocorrido durante o desenrolamento da pilha dessa outra exceção. O Java pode ter se atrasado para a parte com sua "tentativa com recursos", mas lida com exceções durante o desenrolamento da pilha um pouco melhor que o .NET.finally
blocos contra exceções que, realisticamente, se pode esperar que ocorram de tal maneira que as exceções originais e novas sejam registradas. Infelizmente, isso exigirá muitas vezes alocação de memória, o que pode causar falhas próprias.O manuseio de exceção de Pokemon (tem que pegar todos!) Certamente nem sempre é ruim. Quando você expõe um método a um cliente, especialmente um usuário final, geralmente é melhor capturar tudo e qualquer coisa, em vez de travar e queimar seu aplicativo.
Geralmente, eles devem ser evitados onde você puder. A menos que você possa executar uma ação específica com base no tipo de exceção, é melhor não tratá-la e permitir que a exceção borbulhe em vez de engolir a exceção ou manipulá-la incorretamente.
Dê uma olhada nesta resposta para mais informações.
fonte
LoadDocument()
, é essencialmente impossível identificar todas as coisas que podem dar errado, mas 99% das exceções que podem ser lançadas significarão simplesmente "Não foi possível interpretar o conteúdo de um arquivo com o nome dado como um documento; lide com ele ". Se alguém tentar abrir algo que não é um arquivo de documento válido, isso não deve travar o aplicativo e matar outros documentos abertos. O tratamento de erros de Pokemon nesses casos é feio, mas não conheço boas alternativas.A captura de exceção geral é ruim porque deixa seu programa em um estado indefinido. Você não sabe onde as coisas deram errado e não sabe o que o seu programa realmente fez ou não fez.
Onde eu permitiria pegar tudo é ao fechar um programa. Contanto que você possa limpar tudo bem. Nada tão irritante quanto um programa que você fecha, que apenas gera uma caixa de diálogo de erro que não faz nada além de ficar sentada ali, sem ir embora e impedindo o fechamento do computador.
Em um ambiente distribuído, seu método de log pode sair pela culatra: capturar uma exceção geral pode significar que seu programa ainda mantém um bloqueio no arquivo de log, impedindo que outros usuários façam logs.
fonte
Como outros já disseram, é realmente difícil (se não impossível) imaginar alguma ação que você deseja executar independentemente da exceção lançada. Apenas um exemplo são situações em que o estado do programa foi corrompido e qualquer processamento adicional pode levar a problemas (esta é a lógica por trás
Environment.FailFast
).Para o código do hobby, é bom entender
Exception
, mas para o código de nível profissional, a introdução de um novo tipo de exceção deve ser tratada com o mesmo respeito que uma alteração na assinatura do método, ou seja, ser considerada uma alteração de quebra. Se você se inscrever neste ponto de vista, será imediatamente óbvio que voltar a alterar (ou verificar) o código do cliente é o único curso de ação correto.Claro, porque você não estará capturando apenas exceções geradas
Bar
. Também haverá exceções que os clientes de Bar ou mesmo o tempo de execução poderão lançar durante o tempo queBar
estiver na pilha de chamadas. Um bem escritoBar
deve definir seu próprio tipo de exceção, se necessário, para que os chamadores possam capturar especificamente as exceções emitidas por si mesmas.IMHO esta é a maneira errada de pensar sobre o tratamento de exceções. Você deve estar operando em listas brancas (capturar os tipos de exceção A e B), não em listas negras (capturar todas as exceções, exceto X).
fonte
Talvez . Há exceções para todas as regras e nenhuma regra deve ser seguida acriticamente. Seu exemplo pode ser um dos casos em que faz sentido engolir todas as exceções. Por exemplo, se você deseja adicionar rastreio a um sistema crítico de produção e deseja ter certeza absoluta de que as alterações não atrapalham a tarefa principal do aplicativo.
No entanto , você deve pensar cuidadosamente sobre os possíveis motivos da falha antes de decidir ignorá-los silenciosamente. Por exemplo, e se o motivo da exceção for:
Você não deseja ser notificado imediatamente de que esse problema está presente para poder corrigi-lo? Engolir a exceção significa que você nunca sabe que algo deu errado.
Alguns problemas (como o disco está cheio) também podem causar falhas em outras partes do aplicativo - mas essa falha não está registrada agora, então você nunca sabe!
fonte
Eu gostaria de abordar isso de uma perspectiva lógica e não técnica,
Bem, alguém teria que lidar com isso. Essa é a ideia. Os escritores do código da biblioteca seriam prudentes ao adicionar novos tipos de exceção, porque, como é provável que eles quebrem clientes, você não deve encontrar isso com muita frequência.
Sua pergunta é basicamente "E se eu não me importo com o que deu errado? Eu realmente tenho que passar pelo trabalho de descobrir o que era?"
Aqui está a parte da beleza: não, você não.
"Então, posso apenas olhar para o outro lado e fazer com que qualquer coisa desagradável apareça varrida para debaixo do tapete automaticamente e acabe com isso?"
Não, também não é assim que funciona.
O ponto é que a coleção de possíveis exceções é sempre maior que a coleção que você espera e está interessada no contexto do seu pequeno problema local. Você lida com aqueles que antecipa e, se algo inesperado acontecer, você deixa para os manipuladores de nível superior, em vez de engolir. Se você não se importa com uma exceção que você não esperava, você aposta alguém na pilha de chamadas e seria sabotagem eliminar uma exceção antes que ela atinja seu manipulador.
"Mas ... mas ... então uma dessas outras exceções com as quais eu não me importo pode fazer com que minha tarefa falhe!"
Sim. Mas eles sempre serão mais importantes que os gerenciados localmente. Como um alarme de incêndio ou o chefe dizendo para você parar o que está fazendo e pegar uma tarefa mais urgente que apareceu.
fonte
Você deve capturar exceções gerais no nível superior de cada processo e tratá-lo relatando o bug da melhor maneira possível e finalizando o processo.
Você não deve capturar exceções gerais e tentar continuar a execução.
fonte