Durante o desenvolvimento de aplicações ou, mesmo, com um sistema já em operação, não é um fato incomum que ocorram erros. As prováveis causas disto podem ser as mais variadas possíveis: descuidos por parte de programadores envolvidos em um projeto, uma funcionalidade não concluída conforme o esperado ou ainda, falhas decorrentes de algum elemento externo ao próprio software (servidores de banco de dados inoperantes, problemas em uma rede corporativa, falha no acesso a Web Services etc.) são apenas alguns dos fatores que podem conduzir a situações inesperadas e, em muitos casos, indesejáveis.

Considerando as principais plataformas de desenvolvimento atuais (com destaque para .NET e Java), erros são representados através de objetos conhecidos como exceções. A gravação de dados relativos a exceções geradas por uma aplicação se revela como um instrumento de extrema importância, visto que a partir destas informações desenvolvedores terão meios para tentar reproduzir falhas que afetaram um sistema.

No .NET Framework informações sobre exceções estão associadas a objetos baseados no tipo Exception (namespace System). O comum é que existam diferentes classes que herdam dessa construção básica, com cada uma das mesmas estando normalmente vinculadas a um contexto bem específico.

Sobre a estrutura da classe Exception, esta última conta com algumas propriedades que detalham um erro gerado dentro de uma aplicação:

  • Message: mensagem que descreve o erro que levou ao lançamento da exceção que se está considerando;
  • InnerException: instância do tipo Exception que corresponde à falha original, estando associada a uma nova exceção. O preenchimento desta propriedade é opcional (logo, o valor desse elemento poderá ser “nul” em muitos casos);
  • StackTrace: string em que são listados os diferentes pontos pelos quais passou a aplicação antes de um erro. Graças a esta informação, é possível se rastrear a origem, bem como compreender melhor o que levou a esta situação anormal. Uma única ressalva deve ser feita quanto a disponibilizar essas informações a usuários, já que existirão tanto aqueles incapazes de entender o conteúdo desta propriedade, assim como outros que com conhecimentos mais avançados podem vir a explorar vulnerabilidades do sistema.

A seguir estão listados alguns exemplos de exceções bastante comuns em aplicações construídas sob o .NET Framework:

  • InvalidOperationException (namespace System): exceção que acontece quando a invocação de um método a partir de um objeto for inválida, normalmente devido a problemas com o estado atual em que este se encontra;
  • SqlException (namespace System.Data.SqlClient): geralmente lançada quando da ocorrência de erros em instruções enviadas a um banco de dados do SQL Server;
  • CommunicationException (namespace System.ServiceModel): representa erros em processos de comunicação envolvendo serviços, sendo bastante comum em soluções baseadas na tecnologia WCF (incluindo nisto aplicações-cliente que consomem funcionalidades de serviços deste tipo);
  • HttpUnhandledException (namespace System.Web): exceções deste tipo são lançadas quando erros não são tratados de modo apropriado dentro de aplicações Web;
  • ArithmeticException (namespace System): falha resultante de um erro durante o processamento de uma operação aritmética;
  • DivideByZeroException (namespace System): exceção derivada do tipo ArithmeticException, sendo disparada em casos que envolvem a tentativa de divisão de um número por zero;
  • OutOfMemoryException (namespace System): erro associado à falta de memória e que impossibilita a um programa de prosseguir com sua execução normal;
  • SecurityException (namespace System.Security): falha geralmente relacionada à detecção de problemas de segurança;
  • FileNotFoundException (namespace System.IO): corresponde a falhas ao se tentar realizar uma operação que acesse um arquivo;
  • DirectoryNotFoundException (namespace System.IO): exceção disparada quando um diretório que se está tentando acessar não existir.

A forma como estes problemas são registrados (técnica esta conhecida como "logging") é também bastante diversa, podendo envolver desde a persistência dos dados em tabelas de bases relacionais ou em mecanismos próprios de um sistema operacional (como o Event Viewer do Windows), passando até mesmo pela gravação em arquivos (no formato texto ou XML, por exemplo). Existem inclusive frameworks específicos que simplificam a implementação deste tipo de funcionalidade, sendo possível citar no caso do .NET Framework a geração de arquivos XML por meio do log4net (Log4net).

Independentemente da maneira como informações sobre exceções venham a ser gravadas, não será raro que programadores precisem se debruçar sobre o código-fonte, a fim de identificar em que situação uma falha poderá ocorrer. Muitas ferramentas visuais para desenvolvimento de software contam com mecanismos que facilitam tarefas desse gênero, sendo que a atividade relacionada ao uso de funcionalidades de execução de instruções e checagem de erros é conhecida como depuração ou, simplesmente, debug.

O Visual Studio oferece um amplo suporte para a depuração de soluções .NET. A partir do menu Debug está disponível uma série de opções para a execução passo a passo de trechos de uma aplicação, incluindo nisto as estruturas conhecidas como breakpoints.

Profissionais da área de software estão mais do que familiarizados com o poder e a flexibilidade que o uso de breakpoints oferece. Graças a esses recursos, é possível interromper o fluxo de execução de um sistema a partir de uma IDE de desenvolvimento, de maneira que se consiga inclusive executar instruções uma a uma, avaliando os dados e os resultados produzidos pelas mesmas. Trata-se, portanto, de um instrumento bastante importante para a simulação de situações que resultem em exceções.

Ainda sobre a interrupção no fluxo de execução de aplicações, o Visual Studio costuma indicar o ponto em que uma falha ocorreu, pausando assim o processamento atual. Quando isto acontece, a própria IDE permite a visualização da instância que corresponde à exceção gerada, facilitando assim o trabalho de análise a ser desempenhado por um desenvolvedor. Na Figura 1 é demonstrado um exemplo disto, em que a tentativa de se acessar uma base de dados resultou em um erro do tipo SqlException.

Visualizando informações sobre uma SqlException a partir do Visual Studio

Figura 1: Visualizando informações sobre uma SqlException a partir do Visual Studio

Soluções construídas sob a tecnologia ASP.NET contam com algumas características peculiares no que se refere à geração de exceções. Conforme já mencionado anteriormente, um erro do tipo HttpUnhandledException será gerado caso uma falha não venha a ser tratada em aplicações Web. Esse comportamento pode vir a causar algumas dificuldades na depuração de uma aplicação, principalmente se um programador não se atentar a detalhes como a propriedade InnerException de uma exceção.

Supondo uma aplicação em que o evento Application_Error do arquivo Global.asax será responsável pela gravação de informações sobre erros, utilizando para isto uma classe de nome LogHelper (Listagem 1). O método Application_Error será acionado sempre que uma exceção não for tratada adequadamente dentro desta aplicação Web. Por meio do método GetLastError do objeto Server é possível se obter a instância da exceção que corresponde a tal erro.

Listagem 1: Evento Application_Error


  ...
  
  void Application_Error(object sender, EventArgs e)
  {
      Exception ex = Server.GetLastError();
      LogHelper.RegistrarErro(ex);
  }
  
  ...
  

Caso uma falha desconhecida esteja acontecendo na aplicação que se está considerando como exemplo, uma possível forma de se rastrear tal problema seria incluir um breakpoint dentro do evento Application_Error. A partir disto, torna-se possível verificar detalhes de prováveis exceções disparadas em outros pontos do sistema.

Uma vez que uma exceção seja lançada, pode-se visualizar o conteúdo de sua propriedade Message posicionando o mouse sobre a variável que armazena a mesma. Conforme demonstrado na Figura 2, a mensagem exibida é vaga demais, apenas indicando a ocorrência de um erro do tipo HttpUnhandledException.

Visualizando a mensagem associada a uma exceção

Figura 2: Visualizando a mensagem associada a uma exceção

O botão “+” em que consta a mensagem vinculada a uma exceção permite verificar maiores detalhes sobre uma falha. Neste exemplo específico (Figura 3) nota-se que a propriedade InnerException foi preenchida com o erro original (uma exceção do tipo DivideByZeroException), o qual é decorrente de uma operação aritmética que resultou em divisão por zero.

Analisando o conteúdo da propriedade InnerException

Figura 3: Analisando o conteúdo da propriedade InnerException

O uso da propriedade InnerException é comum também em frameworks desenvolvidos por terceiros. Em tais casos, é provável que um erro inicial seja associado a um tipo de objeto mais genérico; nestas situações, apenas a consulta à propriedade InnerException poderá fornecer maiores informações na tentativa de se chegar à causa real de um problema.

Procurei com este artigo fornecer uma dica simples, mas que pode ser de grande utilidade ao se efetuar o debug de aplicações .NET. Espero que o conteúdo aqui apresentado possa auxiliá-lo em algum momento. Até uma próxima oportunidade!