Como usar o try catch para o tratamento de exceções é a melhor prática

enquanto mantenho o código do meu colega até de alguém que afirma ser um desenvolvedor sênior, geralmente vejo o seguinte código:

try { //do something } catch { //Do nothing } 

ou, às vezes, eles gravam informações de registro em log para registrar arquivos como seguir.

 try { //do some work } catch(Exception exception) { WriteException2LogFile(exception); } 

Eu só estou querendo saber se o que eles fizeram é a melhor prática? Isso me deixa confuso porque no meu pensamento os usuários devem saber o que acontece com o sistema.

Por favor me dê um conselho.

Minha estratégia de tratamento de exceções é:

  • Para capturar todas as exceções não tratadas conectando-se ao Application.ThreadException event , decida:

    • Para um aplicativo de UI: pop-lo para o usuário com uma mensagem de desculpas (winforms)
    • Para um serviço ou um aplicativo de console: registre-o em um arquivo (serviço ou console)

Então eu sempre incluo cada parte do código que é executado externamente em try/catch :

  • Todos os events triggersdos pela infraestrutura do WinForms (Load, Click, SelectedChanged …)
  • Todos os events triggersdos por componentes de terceiros

Então eu coloquei em ‘try / catch’

  • Todas as operações que conheço podem não funcionar o tempo todo (operações de IO, cálculos com potencial de divisão zero …). Nesse caso, eu jogo um novo ApplicationException("custom message", innerException) para acompanhar o que realmente aconteceu

Além disso, eu tento o meu melhor para classificar as exceções corretamente . Existem exceções que:

  • precisa ser mostrado para o usuário imediatamente
  • requer algum processamento extra para juntar as coisas quando elas acontecem para evitar problemas em cascata (isto é: coloque .EndUpdate na seção finally durante um preenchimento TreeView )
  • o usuário não se importa, mas é importante saber o que aconteceu. Então eu sempre faço o log delas:

    • No log de events
    • ou em um arquivo .log no disco

É uma boa prática projetar alguns methods estáticos para manipular exceções nos manipuladores de erro de nível superior do aplicativo.

Eu também me forço a tentar:

  • Lembre-se de que todas as exceções são geradas até o nível superior . Não é necessário colocar manipuladores de exceção em todos os lugares.
  • As funções reutilizáveis ​​ou chamadas profundas não precisam exibir ou registrar exceções: elas são geradas automaticamente ou relançadas com algumas mensagens personalizadas em meus manipuladores de exceções.

Então finalmente:

Mau:

 // DON'T DO THIS, ITS BAD try { ... } catch { // only air... } 

Sem utilidade:

 // DONT'T DO THIS, ITS USELESS try { ... } catch(Exception ex) { throw ex; } 

Ter uma tentativa, finalmente, sem uma captura é perfeitamente válido:

 try { listView1.BeginUpdate(); // If an exception occurs in the following code, then the finally will be executed // and the exception will be thrown ... } finally { // I WANT THIS CODE TO RUN EVENTUALLY REGARDLESS AN EXCEPTION OCCURED OR NOT listView1.EndUpdate(); } 

O que eu faço no nível superior:

 // ie When the user clicks on a button try { ... } catch(Exception ex) { ex.Log(); // Log exception -- OR -- ex.Log().Display(); // Log exception, then show it to the user with apologies... } 

O que eu faço em algumas funções chamadas:

 // Calculation module try { ... } catch(Exception ex) { // Add useful information to the exception throw new ApplicationException("Something wrong happened in the calculation module :", ex); } // IO module try { ... } catch(Exception ex) { throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex); } 

Há muito a ver com o tratamento de exceções (exceções personalizadas), mas as regras que tento ter em mente são suficientes para os aplicativos simples que eu faço.

Aqui está um exemplo de methods de extensões para manipular exceções capturadas de uma maneira confortável. Eles são implementados de uma forma que podem ser encadeados, e é muito fácil adicionar seu próprio processamento de exceção capturado.

 // Usage: try { // boom } catch(Exception ex) { // Only log exception ex.Log(); -- OR -- // Only display exception ex.Display(); -- OR -- // Log, then display exception ex.Log().Display(); -- OR -- // Add some user-friendly message to an exception new ApplicationException("Unable to calculate !", ex).Log().Display(); } // Extension methods internal static Exception Log(this Exception ex) { File.AppendAllText("CaughtExceptions" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", DateTime.Now.ToString("HH:mm:ss") + ": " + ex.Message + "\n" + ex.ToString() + "\n"); return ex; } internal static Exception Display(this Exception ex, string msg = null, MessageBoxImage img = MessageBoxImage.Error) { MessageBox.Show(msg ?? ex.Message, "", MessageBoxButton.OK, img); return ex; } 

A melhor prática é que o tratamento de exceções nunca deve ocultar problemas . Isso significa que try-catch blocos try-catch devem ser extremamente raros.

Existem 3 circunstâncias em que usar um try-catch faz sentido.

  1. Sempre lide com as exceções conhecidas o mais baixo possível. No entanto, se você está esperando uma exceção, geralmente é melhor testar primeiro. Por exemplo, a análise, a formatação e as exceções aritméticas são quase sempre melhor tratadas por verificações lógicas primeiro, em vez de um try-catch específico.

  2. Se você precisar fazer algo em uma exceção (por exemplo, registrar ou reverter uma transação), em seguida, lance novamente a exceção.

  3. Sempre lide com exceções desconhecidas o mais alto possível – o único código que deve consumir uma exceção e não recarregá-la deve ser a interface do usuário ou a API pública.

Suponha que você esteja se conectando a uma API remota, e saiba que espera certos erros (e tem coisas nessas circunstâncias), então esse é o caso 1:

 try { remoteApi.Connect() } catch(ApiConnectionSecurityException ex) { // User's security details have expired return false; } return true; 

Observe que nenhuma outra exceção é detectada, pois não é esperada.

Agora, suponha que você esteja tentando salvar algo no database. Nós temos que revertê-lo se falhar, então temos o caso 2:

 try { DBConnection.Save(); } catch { // Roll back the DB changes so they aren't corrupted on ANY exception DBConnection.Rollback(); // Re-throw the exception, it's critical that the user knows that it failed to save throw; } 

Note que nós lançamos novamente a exceção – o código mais alto ainda precisa saber que algo falhou.

Finalmente, temos a interface do usuário – aqui não queremos exceções completamente não tratadas, mas também não queremos ocultá-las. Aqui temos um exemplo de caso 3:

 try { // Do something } catch(Exception ex) { // Log exception for developers WriteException2LogFile(ex); // Display message to users DisplayWarningBox("An error has occurred, please contact support!"); } 

No entanto, a maioria das estruturas de API ou UI tem maneiras genéricas de executar o caso 3. Por exemplo, o ASP.Net possui uma canvas de erro amarela que elimina os detalhes da exceção, mas que pode ser substituída por uma mensagem mais genérica no ambiente de produção. Seguir essas é uma prática recomendada, pois você economiza muito código, mas também porque o registro de erros e a exibição devem ser decisões de configuração em vez de codificadas.

Isso tudo significa que o caso 1 (exceções conhecidas) e o caso 3 (manipulação única de interface do usuário) têm padrões melhores (evitam o erro esperado ou o tratamento de erros manuais para a interface do usuário).

Mesmo o caso 2 pode ser substituído por padrões melhores, por exemplo, os escopos de transação ( using blocos que reverteram qualquer transação não confirmada durante o bloco) tornam mais difícil para os desenvolvedores obter o padrão de melhores práticas errado.

Por exemplo, suponha que você tenha um aplicativo ASP.Net de grande escala. O registro de erros pode ser via ELMAH , a exibição de erros pode ser um YSoD informativo localmente e uma mensagem localizada agradável na produção. As conexões de database podem ser todas através de escopos de transação e using blocos. Você não precisa de um único bloco try-catch .

TL; DR: A melhor prática é, na verdade, não usar blocos try-catch .

Uma exceção é um erro de bloqueio .

Primeiro de tudo, a melhor prática deve ser não lançar exceções para qualquer tipo de erro, a menos que seja um erro de bloqueio .

Se o erro estiver bloqueando , ative a exceção. Uma vez que a exceção já tenha sido lançada, não há necessidade de ocultá-la porque é excepcional; deixe o usuário saber sobre isso (você deve reformatar toda a exceção para algo útil para o usuário na interface do usuário).

Seu trabalho como desenvolvedor de software é se esforçar para evitar um caso excepcional em que algum parâmetro ou situação de tempo de execução possa terminar em uma exceção. Ou seja, as exceções não devem ser silenciadas, mas devem ser evitadas .

Por exemplo, se você souber que alguma input de número inteiro poderia vir com um formato inválido, use int.TryParse vez de int.Parse . Há muitos casos em que você pode fazer isso em vez de apenas dizer “se falhar, simplesmente lançar uma exceção”.

Atirar exceções é caro.

Se, afinal de contas, uma exceção for lançada, em vez de gravar a exceção no log depois que ela for lançada, uma das melhores práticas é capturá-la em um manipulador de exceções de primeira chance . Por exemplo:

  • ASP.NET: Global.asax Application_Error
  • Outros: evento AppDomain.FirstChanceException .

Minha postura é que os try / catches locais são mais adequados para lidar com casos especiais em que você pode converter uma exceção em outra, ou quando você quer “silenciá-la” para um caso muito, muito, muito, muito especial lançando uma exceção não relacionada que você precisa silenciar para solucionar o bug inteiro).

Para o resto dos casos:

  • Tente evitar exceções.
  • Se isso não for possível: manipuladores de exceções de primeira chance.
  • Ou use um aspecto PostSharp (AOP).

Respondendo a @thewhiteambit em algum comentário …

@thewhiteambit disse:

Exceções não são erros fatais, são exceções! Às vezes, eles não são nem mesmo Erros, mas considerá-los Erros Fatais é uma falsa compreensão do que são Exceções.

Primeiro de tudo, como uma exceção não pode ser um erro?

  • Nenhuma conexão de database => exceção.
  • Formato de cadeia inválido para analisar algum tipo => exception
  • Tentando analisar JSON e enquanto a input não é, na verdade, JSON => exceção
  • Argumento null enquanto object era esperado => exceção
  • Alguma biblioteca tem um bug => lança uma exceção inesperada
  • Há uma conexão de soquete e ela é desconectada. Então você tenta enviar uma mensagem => exceção

Podemos listar 1k casos de quando uma exceção é lançada e, afinal, qualquer um dos casos possíveis será um erro .

Uma exceção é um erro, porque no final do dia é um object que coleta informações de diagnóstico – ele tem uma mensagem e acontece quando algo dá errado.

Ninguém lançaria uma exceção quando não houvesse caso excepcional. Exceções devem ser erros de bloqueio, porque uma vez lançadas, se você não tentar entrar no uso try / catch e exceções para implementar o stream de controle, elas significam que seu aplicativo / serviço parará a operação que entrou em um caso excepcional .

Além disso, sugiro a todos que verifiquem o paradigma fail-fast publicado por Martin Fowler (e escrito por Jim Shore) . É assim que sempre entendi como lidar com exceções, mesmo antes de chegar a este documento há algum tempo.

[…] considerá-los Fatal-Erros é completamente falsa compreensão do que são exceções.

Normalmente, as exceções cortam algum stream de operação e são manipuladas para convertê-las em erros que podem ser entendidos pelo usuário. Assim, parece que uma exceção, na verdade, é um paradigma melhor para lidar com casos de erro e trabalhar neles para evitar uma falha de aplicativo / serviço completo e notificar o usuário / consumidor de que algo deu errado.

Mais respostas sobre as preocupações do @thewhiteambit

Por exemplo, no caso de uma conexão de database ausente, o programa poderia excepcionalmente continuar com a gravação em um arquivo local e enviar as alterações para o database assim que estiver disponível novamente. Sua conversão inválida de String-To-Number poderia ser tentada para analisar novamente com interpretação de idioma local em Exception, como quando você tenta o idioma padrão do Inglês para Parse (“1,5”) falhar e você tenta novamente com interpretação alemã que é completamente bem porque usamos vírgula em vez de ponto como separador. Você vê que essas exceções não devem estar bloqueadas, elas só precisam de algum tratamento de exceção.

  1. Se seu aplicativo puder funcionar offline sem persistir dados no database, você não deve usar exceções , pois a implementação do stream de controle usando o comando try/catch é considerada como um antipadrão. O trabalho offline é um possível caso de uso; portanto, você implementa o stream de controle para verificar se o database está acessível ou não, não aguarda até que seja inacessível .

  2. A coisa de análise também é um caso esperado ( não CASO EXCEPCIONAL ). Se você espera isso, não use exceções para fazer o stream de controle! . Você obtém alguns metadados do usuário para saber o que é sua cultura e usa formatadores para isso! O .NET também suporta este e outros ambientes, e uma exceção porque a formatação do número deve ser evitada se você espera um uso específico da cultura do seu aplicativo / serviço .

Uma Exceção não tratada normalmente se torna um Erro, mas as Exceções em si não são codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors

Este artigo é apenas uma opinião ou um ponto de vista do autor.

Uma vez que a Wikipedia também pode ser apenas a opinião do (s) autor (es) articulado (s), eu não diria que é o dogma , mas verifique o que Coding by exception article diz em algum lugar em algum parágrafo:

[…] Usando estas exceções para lidar com erros específicos que surgem para continuar o programa é chamado de codificação por exceção. Esse antipadrão pode degradar rapidamente o software em desempenho e capacidade de manutenção.

Também diz em algum lugar:

Uso incorreto de exceção

Muitas vezes, a codificação por exceção pode levar a mais problemas no software com o uso incorreto da exceção. Além de usar o tratamento de exceção para um problema exclusivo, o uso incorreto da exceção leva isso adiante, executando o código mesmo após a exceção ser gerada. Este método de programação pobre se assemelha ao método goto em muitas linguagens de software, mas só ocorre depois que um problema no software é detectado.

Honestamente, acredito que o software não pode ser desenvolvido sem levar a sério casos de uso. Se você sabe disso …

  • Seu database pode ficar offline …
  • Alguns arquivos podem ser bloqueados …
  • Alguma formatação pode não ser suportada …
  • Alguma validação de domínio pode falhar …
  • Seu aplicativo deve funcionar no modo off-line …
  • seja qual for o caso de uso

você não vai usar exceções para isso . Você apoiaria esses casos de uso usando o stream de controle regular.

E se algum caso de uso inesperado não for coberto, seu código falhará rapidamente, porque ele lançará uma exceção . Certo, porque uma exceção é um caso excepcional .

Por outro lado, e por fim, às vezes você cobre casos excepcionais lançando exceções esperadas , mas não os lança para implementar o stream de controle. Você faz isso porque deseja notificar as camadas superiores de que não suporta algum caso de uso ou seu código não funciona com alguns argumentos ou dados / propriedades do ambiente.

A única vez que você deve preocupar seus usuários sobre algo que aconteceu no código é se há algo que eles podem ou precisam fazer para evitar o problema. Se eles puderem alterar dados em um formulário, pressione um botão ou altere a configuração de um aplicativo para evitar o problema e informe-os. Mas avisos ou erros que o usuário não tem capacidade de evitar apenas os faz perder a confiança em seu produto.

Exceções e registros são para você, o desenvolvedor, não o seu usuário final. Compreender a coisa certa a fazer quando você pegar cada exceção é muito melhor do que apenas aplicar alguma regra de ouro ou confiar em uma rede de segurança para toda a aplicação.

A codificação sem mente é o ÚNICO tipo de codificação incorreta. O fato de você sentir que há algo melhor que pode ser feito nessas situações mostra que você está investindo em uma boa codificação, mas evite tentar estampar algumas regras genéricas nessas situações e entender o motivo de algo a ser lançado em primeiro lugar e o que você pode fazer para se recuperar dele.

Eu sei que esta é uma pergunta antiga, mas ninguém aqui mencionou o artigo do MSDN, e foi o documento que realmente esclareceu para mim, o MSDN tem um documento muito bom sobre isso, você deve pegar exceções quando as seguintes condições forem verdadeiras:

  • Você tem um bom entendimento de por que a exceção pode ser lançada e pode implementar uma recuperação específica, como solicitar que o usuário insira um novo nome de arquivo ao capturar um object FileNotFoundException.

  • Você pode criar e lançar uma exceção nova e mais específica.

 int GetInt(int[] array, int index) { try { return array[index]; } catch(System.IndexOutOfRangeException e) { throw new System.ArgumentOutOfRangeException( "Parameter index is out of range."); } } 
  • Você deseja manipular parcialmente uma exceção antes de passá-la para manipulação adicional. No exemplo a seguir, um bloco catch é usado para adicionar uma input a um log de erros antes de lançar novamente a exceção.
  try { // Try to access a resource. } catch (System.UnauthorizedAccessException e) { // Call a custom error logging procedure. LogError(e); // Re-throw the error. throw; } 

Eu sugeriria ler toda a seção ” Exceções e tratamento de exceções ” e também as Melhores práticas para exceções .

A melhor abordagem é a segunda (aquela na qual você especifica o tipo de exceção). A vantagem disso é que você sabe que esse tipo de exceção pode ocorrer em seu código. Você está lidando com esse tipo de exceção e pode continuar. Se houver alguma outra exceção, significa que algo está errado, o que ajudará você a encontrar erros em seu código. O aplicativo acabará por falhar, mas você vai saber que há algo que você perdeu (bug) que precisa ser corrigido.

A segunda abordagem é boa.

Se você não quiser mostrar o erro e confundir o usuário do aplicativo mostrando a exceção de tempo de execução (isto é, erro) que não está relacionada a ele, apenas registre o erro e a equipe técnica pode procurar o problema e resolvê-lo.

 try { //do some work } catch(Exception exception) { WriteException2LogFile(exception);//it will write the or log the error in a text file } 

Eu recomendo que você vá para a segunda abordagem para toda a sua aplicação.

Deixar bloco de captura em branco é a pior coisa a fazer. Se houver um erro, a melhor maneira de lidar com isso é:

  1. Logá-lo no arquivo \ database etc.
  2. Tente consertá-lo na hora (talvez tentando uma maneira alternativa de fazer essa operação)
  3. Se não podemos consertar isso, notifique o usuário de que há algum erro e, claro, aborte a operação

Para mim, lidar com exceção pode ser visto como regra de negócios. Obviamente, a primeira abordagem é inaceitável. O segundo é melhor e pode ser 100% correto se o contexto disser isso. Agora, por exemplo, você está desenvolvendo um complemento do Outlook. Se você adicionar exceção não tratada, o usuário do Outlook agora pode saber disso, já que o Outlook não irá se destruir devido a falha de um dos plugins. E você tem dificuldade em descobrir o que deu errado. Portanto, a segunda abordagem neste caso, para mim, é correta. Além de registrar a exceção, você pode decidir exibir a mensagem de erro para o usuário – considero isso como uma regra de negócios.

A melhor prática é lançar uma exceção quando o erro ocorrer. Porque ocorreu um erro e não deve estar oculto.

Mas na vida real você pode ter várias situações quando você quer esconder isso

  1. Você confia no componente de terceiros e deseja continuar o programa em caso de erro.
  2. Você tem um caso de negócios que precisa continuar em caso de erro

Você deve considerar estas Diretrizes de Design para Exceções

  • Lançamento de exceção
  • Usando tipos de exceção padrão
  • Exceções e Desempenho

https://docs.microsoft.com/pt-br/dotnet/standard/design-guidelines/exceptions

O problema sem argumentos é simplesmente comer a exceção e não tem utilidade. E se um erro fatal ocorrer? Não há como saber o que aconteceu se você usar o catch sem argumento.

Uma instrução catch deve capturar exceções mais específicas, como FileNotFoundException e, no final, você deve capturar Exception que pode capturar qualquer outra exceção e registrá-las.

Às vezes você precisa tratar exceções que não dizem nada para os usuários.

Meu caminho é:

  • Para capturar exceções não checadas no nível do aplicativo (ou seja, em global.asax) para exceções críticas (o aplicativo não pode ser útil). Essas exceções eu não estou pegando no lugar. Basta registrá-los no nível do aplicativo e deixar o sistema fazer seu trabalho.
  • Capture “on place” e mostre algumas informações úteis ao usuário (digite o número errado, não é possível analisar).
  • Capture o local e não faça nada em problemas marginais como “Vou verificar informações de atualização no segundo plano, mas o serviço não está em execução”.

Definitivamente não precisa ser a melhor prática. 😉

Com exceções, eu tente o seguinte:

Primeiro, eu pego tipos especiais de exceções como divisão por zero, operações de IO e assim por diante e escrevo código de acordo com isso. Por exemplo, uma divisão por zero, dependendo da proveniência dos valores, eu poderia alertar o usuário (por exemplo, uma calculadora simples em que um cálculo do meio (não os argumentos) chega em uma divisão por zero) ou tratar silenciosamente essa exceção, registrando e continue processando.

Então eu tento pegar as exceções restantes e registrá-las. Se possível, permitir a execução do código, caso contrário, alertar o usuário de que ocorreu um erro e pedir-lhe para enviar um relatório de erro.

No código, algo assim:

 try{ //Some code here } catch(DivideByZeroException dz){ AlerUserDivideByZerohappened(); } catch(Exception e){ treatGeneralException(e); } finally{ //if a IO operation here i close the hanging handlers for example }