Sempre acreditei que, se um método pode gerar uma exceção, é imprudente não proteger essa chamada com um bloqueio de tentativa significativo.
Acabei de publicar ' Você SEMPRE deve atender chamadas que podem tentar, pegar blocos. 'a essa pergunta e disseram que era' um conselho notavelmente ruim '- eu gostaria de entender o porquê.
exception
language-agnostic
try-catch
Konrad
fonte
fonte
Respostas:
Um método só deve capturar uma exceção quando pode lidar com isso de alguma maneira sensata.
Caso contrário, passe adiante, na esperança de que um método acima da pilha de chamadas possa fazer sentido.
Como outros observaram, é uma boa prática ter um manipulador de exceções sem tratamento (com log) no nível mais alto da pilha de chamadas para garantir que quaisquer erros fatais sejam registrados.
fonte
try
blocos. Há uma boa discussão no "C ++ mais eficaz" de Scott Meyers.try
blocos são livres em qualquer compilador C moderno; essas informações são datadas de Nick. Também não concordo em ter um manipulador de exceção de nível superior porque você perde as informações da localidade (o local real onde a instrução falhou).terminate
) . É mais um mecanismo de segurança. Além disso,try/catch
são mais ou menos gratuitos quando não há nenhuma exceção. Quando há uma propagação, ela consome tempo cada vez que é lançada e capturada; portanto, uma cadeiatry/catch
desse único relançamento não custa nada.Como Mitch e outros declararam, você não deve capturar uma exceção que não planeja manipular de alguma forma. Você deve considerar como o aplicativo manipulará sistematicamente as exceções ao projetá-lo. Isso geralmente leva a ter camadas de tratamento de erros com base nas abstrações - por exemplo, você lida com todos os erros relacionados ao SQL em seu código de acesso a dados para que a parte do aplicativo que está interagindo com os objetos do domínio não seja exposta ao fato de que é um DB sob o capô em algum lugar.
Existem alguns odores de código relacionados que você definitivamente deseja evitar, além do cheiro de "pegar tudo em qualquer lugar" .
"catch, log, rehrow" : se você deseja log baseado em escopo, escreva uma classe que emita uma instrução de log em seu destruidor quando a pilha estiver desenrolando devido a uma exceção (ala
std::uncaught_exception()
). Tudo o que você precisa fazer é declarar uma instância de log no escopo em que você está interessado e, voila, você possui log e nenhuma lógicatry
/ desnecessáriacatch
."pegar, jogar traduzido" : isso geralmente aponta para um problema de abstração. A menos que esteja implementando uma solução federada na qual esteja traduzindo várias exceções específicas em mais uma genérica, você provavelmente terá uma camada desnecessária de abstração ... e não diga "Talvez eu precise disso amanhã" .
"pegar, limpar, relançar" : esse é um dos meus ódios de estimação. Se você perceber muito disso, aplique técnicas de Aquisição de recursos e inicialização e coloque a parte de limpeza no destruidor de uma instância de objeto de zelador .
Considero que o código repleto de
try
/catch
blocks é um bom alvo para a revisão e refatoração de código. Indica que o tratamento de exceções não é bem compreendido ou o código se tornou um amœba e está com séria necessidade de refatoração.fonte
Logger
classe semelhante àlog4j.Logger
que inclui o ID do thread em todas as linhas de log e emiti um aviso no destruidor quando uma exceção estava ativa.Como a próxima pergunta é "Eu peguei uma exceção, o que devo fazer a seguir?" O que você vai fazer? Se você não fizer nada - isso é ocultação de erros e o programa "simplesmente não funciona" sem chance de descobrir o que aconteceu. Você precisa entender o que exatamente fará depois de capturar a exceção e capturar apenas se souber.
fonte
Você não precisa cobrir todos os blocos com try-catchs porque um try-catch ainda pode capturar exceções sem tratamento lançadas em funções mais abaixo na pilha de chamadas. Portanto, em vez de todas as funções terem um try-catch, você pode ter uma na lógica de nível superior do seu aplicativo. Por exemplo, pode haver uma
SaveDocument()
rotina de nível superior, que chama muitos métodos que chamam outros métodos etc. Esses sub-métodos não precisam de suas próprias tentativas de captura, porque se lançarem, ainda serão pegos pelaSaveDocument()
captura.Isso é bom por três razões: é útil porque você tem um único local para relatar um erro: o
SaveDocument()
(s) bloco (s) de captura. Não há necessidade de repetir isso em todos os sub-métodos, e é o que você deseja: um único local para fornecer ao usuário um diagnóstico útil sobre algo que deu errado.Segundo, o salvamento é cancelado sempre que uma exceção é lançada. Com cada sub-método de tentativa de captura, se uma exceção é lançada, você começa no bloco catch desse método, as folhas de execução da função, e continua através
SaveDocument()
. Se algo já deu errado, você provavelmente quer parar por aí.Terceiro, todos os seus sub-métodos podem assumir que todas as chamadas são bem-sucedidas . Se uma chamada falhar, a execução passará para o bloco catch e o código subsequente nunca será executado. Isso pode tornar seu código muito mais limpo. Por exemplo, aqui está o código de erro:
Veja como isso pode ser escrito com exceções:
Agora está muito mais claro o que está acontecendo.
Observe que o código seguro de exceção pode ser mais difícil de escrever de outras maneiras: você não deseja vazar memória se uma exceção for lançada. Verifique se você conhece RAII , contêineres STL, ponteiros inteligentes e outros objetos que liberam seus recursos em destruidores, pois os objetos são sempre destruídos antes das exceções.
fonte
try
-catch
blocos que tentativa de flag-se todas as permutações ligeiramente diferente de algum erro com uma mensagem um pouco diferente, quando na realidade todos eles devem terminar a mesma: transação ou programa de fracasso e saída! Se ocorrer uma falha digna de exceção, aposto que a maioria dos usuários só quer recuperar o que pode ou, pelo menos, ficar sozinha sem precisar lidar com 10 níveis de mensagem sobre isso.Herb Sutter escreveu sobre este problema aqui . Com certeza vale a pena ler.
Um teaser:
fonte
Conforme declarado em outras respostas, você deve capturar uma exceção apenas se puder fazer algum tipo de tratamento sensível a erros.
Por exemplo, na pergunta que gerou sua pergunta, o questionador pergunta se é seguro ignorar exceções para a
lexical_cast
de um número inteiro para uma sequência. Tal elenco nunca deve falhar. Se falhasse, algo daria terrivelmente errado no programa. O que você poderia fazer para se recuperar nessa situação? Provavelmente é melhor deixar o programa morrer, pois está em um estado que não pode ser confiável. Portanto, não lidar com a exceção pode ser a coisa mais segura a se fazer.fonte
Se você sempre manipula exceções imediatamente no chamador de um método que pode gerar uma exceção, as exceções se tornam inúteis e é melhor usar códigos de erro.
O ponto principal das exceções é que elas não precisam ser tratadas em todos os métodos da cadeia de chamadas.
fonte
O melhor conselho que ouvi é que você deve sempre capturar exceções em pontos onde possa sensatamente fazer algo sobre a condição excepcional e que "capturar, registrar e liberar" não é uma boa estratégia (se ocasionalmente inevitável nas bibliotecas).
fonte
Concordo com a orientação básica da sua pergunta para lidar com tantas exceções quanto possível no nível mais baixo.
Algumas das respostas existentes são do tipo "Você não precisa lidar com a exceção. Outra pessoa fará isso na pilha". Para minha experiência, isso é uma desculpa ruim para não pensar no tratamento de exceções no pedaço de código desenvolvido no momento, fazendo com que o tratamento de exceções seja o problema de outra pessoa ou posteriormente.
Esse problema cresce dramaticamente no desenvolvimento distribuído, onde você pode precisar chamar um método implementado por um colega de trabalho. E então você deve inspecionar uma cadeia aninhada de chamadas de método para descobrir por que ele / ela está lançando alguma exceção para você, que poderia ter sido tratada com muito mais facilidade no método aninhado mais profundo.
fonte
O conselho que meu professor de ciência da computação me deu uma vez foi: "Use os blocos Try and Catch apenas quando não for possível lidar com o erro usando meios padrão".
Como exemplo, ele nos disse que se um programa tivesse algum problema sério em um local onde não seria possível fazer algo como:
Então você deve usar try, catch blocks. Embora você possa usar exceções para lidar com isso, geralmente não é recomendado porque as exceções são caras em termos de desempenho.
fonte
Se você deseja testar o resultado de todas as funções, use códigos de retorno.
O objetivo das exceções é para que você possa testar os resultados com menos frequência. A idéia é separar condições excepcionais (incomuns, mais raras) do seu código mais comum. Isso mantém o código comum mais limpo e simples - mas ainda é capaz de lidar com essas condições excepcionais.
No código bem projetado, funções mais profundas podem ser lançadas e funções mais altas podem ser detectadas. Mas a chave é que muitas funções "intermediárias" estarão livres do ônus de lidar com condições excepcionais. Eles só precisam ser "protegidos contra exceções", o que não significa que devam pegar.
fonte
Gostaria de acrescentar a esta discussão que, desde C ++ 11 , faz muito sentido, desde que cada
catch
bloco sejarethrow
uma exceção até o ponto em que possa / deva ser tratado. Dessa forma, um backtrace pode ser gerado . Portanto, acredito que as opiniões anteriores estão em parte desatualizadas.Use
std::nested_exception
estd::throw_with_nested
É descrito no StackOverflow aqui e aqui como conseguir isso.
Como você pode fazer isso com qualquer classe de exceção derivada, você pode adicionar muitas informações a esse backtrace! Você também pode dar uma olhada no meu MWE no GitHub , onde um backtrace seria algo assim:
fonte
Tive a "oportunidade" de salvar vários projetos e os executivos substituíram toda a equipe de desenvolvimento porque o aplicativo apresentava muitos erros e os usuários estavam cansados dos problemas e contornos. Todas essas bases de código tinham tratamento centralizado de erros no nível do aplicativo, como a resposta mais votada descreve. Se essa resposta é a melhor prática, por que não funcionou e permitiu que a equipe de desenvolvimento anterior resolvesse problemas? Talvez às vezes não funcione? As respostas acima não mencionam quanto tempo os desenvolvedores passam consertando problemas únicos. Se o tempo para resolver problemas é a métrica principal, o código de instrumentação com blocos try..catch é uma prática recomendada.
Como minha equipe resolveu os problemas sem alterar significativamente a interface do usuário? Simples, todos os métodos foram instrumentados com o try..catch bloqueado e tudo foi registrado no ponto de falha com o nome do método, os valores dos parâmetros do método concatenados em uma sequência passada juntamente com a mensagem de erro, a mensagem de erro, o nome do aplicativo, a data, e versão. Com essas informações, os desenvolvedores podem executar análises sobre os erros para identificar a exceção que mais ocorre! Ou o espaço para nome com o maior número de erros. Também pode validar que um erro que ocorre em um módulo seja tratado adequadamente e não seja causado por vários motivos.
Outro benefício profissional disso é que os desenvolvedores podem definir um ponto de interrupção no método de registro de erros e, com um ponto de interrupção e um único clique no botão de depuração "sair", eles estão no método que falhou com acesso total ao objetos no ponto de falha, convenientemente disponíveis na janela imediata. Isso facilita a depuração e permite arrastar a execução de volta ao início do método para duplicar o problema e encontrar a linha exata. O tratamento centralizado de exceções permite que um desenvolvedor replique uma exceção em 30 segundos? Não.
A declaração "Um método só deve capturar uma exceção quando pode lidar com isso de alguma maneira sensata". Isso implica que os desenvolvedores podem prever ou encontrarão todos os erros que podem ocorrer antes do lançamento. Se isso fosse verdade, o manipulador de exceções de aplicativos não seria necessário e não haveria mercado para Elastic Search e logstash.
Essa abordagem também permite que os desenvolvedores encontrem e corrijam problemas intermitentes na produção! Deseja depurar sem um depurador na produção? Ou você prefere atender e receber e-mails de usuários chateados? Isso permite que você corrija problemas antes que alguém saiba e sem precisar enviar e-mail, mensagens instantâneas ou Slack com suporte, pois tudo o que é necessário para corrigir o problema está aqui. 95% dos problemas nunca precisam ser reproduzidos.
Para funcionar corretamente, ele precisa ser combinado com o log centralizado que pode capturar o espaço para nome / módulo, nome da classe, método, entradas e mensagem de erro e armazenar em um banco de dados para que possa ser agregado para destacar qual método falha mais, para que possa ser corrigido primeiro.
Às vezes, os desenvolvedores escolhem lançar exceções na pilha a partir de um bloco catch, mas essa abordagem é 100 vezes mais lenta que o código normal que não gera. Captura e liberação com registro é o preferido.
Essa técnica foi usada para estabilizar rapidamente um aplicativo que falhou a cada hora para a maioria dos usuários em uma empresa da Fortune 500 desenvolvida por 12 desenvolvedores ao longo de 2 anos. Usando essas 3000 exceções diferentes, foram identificadas, corrigidas, testadas e implantadas em 4 meses. A média é de uma correção a cada 15 minutos, em média, por 4 meses.
Concordo que não é divertido digitar tudo o que é necessário para instrumentar o código e prefiro não olhar para o código repetitivo, mas adicionar 4 linhas de código a cada método vale a pena a longo prazo.
fonte
Além do conselho acima, pessoalmente eu uso alguns try + catch + throw; pelo seguinte motivo:
fonte
Sinto-me compelido a acrescentar outra resposta, embora a resposta de Mike Wheat resuma os pontos principais muito bem. Eu penso assim. Quando você tem métodos que fazem várias coisas, você está multiplicando a complexidade, não a adicionando.
Em outras palavras, um método envolvido em uma tentativa de captura tem dois resultados possíveis. Você tem o resultado de não exceção e o resultado de exceção. Quando você lida com muitos métodos, isso explode exponencialmente além da compreensão.
Exponencialmente, porque se cada método se ramifica de duas maneiras diferentes, toda vez que você chama outro método, está elevando o número anterior de possíveis resultados. Quando você chama cinco métodos, você tem até 256 resultados possíveis, no mínimo. Compare isto a não fazer um try / catch em cada método único e você só tem um caminho a seguir.
É basicamente assim que eu olho para isso. Você pode ficar tentado a argumentar que qualquer tipo de ramificação faz a mesma coisa, mas as tentativas / capturas são um caso especial porque o estado do aplicativo basicamente se torna indefinido.
Então, resumindo, try / catch torna o código muito mais difícil de entender.
fonte
Você não precisa cobrir todas as partes do seu código
try-catch
. O principal uso dotry-catch
bloco é o tratamento de erros e possui bugs / exceções no seu programa. Algum uso detry-catch
-try-catch
bloco.fonte
try
/catch
é completamente separado / ortogonal disso. Se você deseja dispor objetos em um escopo menor, basta abrir um novo{ Block likeThis; /* <- that object is destroyed here -> */ }
- não é necessário agrupar issotry
/ acatch
menos que você realmente precise decatch
alguma coisa, é claro.