quinta-feira, 27 de março de 2008

Erros acontecem

Mais cedo ou mais tarde nossa aplicação terá um comportamento técnico inesperado, uma situação até então não conhecida, ou simplesmente um bug ou erro. Talvez o pior deles seja achar que não erramos, dessa forma não teremos atuado para contorná-los ou ter coletado informações suficientes para que quando um erro aconteça, seja apenas uma vez e possamos descobrir seu motivo e resolve-lo.

Problemas acontecem durante o processo de desenvolvimento de software e profissionais trabalham no sentido codificar de forma defensiva, logando e manipulando erros inesperados.

Com o tempo e experiência a tendência é desenvolvermos produtos mais maduros, no sentido de ter menos bugs, mas também melhorando nosso processo de manipulação de erros. Pois é assim, por mais que você tenha feito sua parte, o disco rígido pode estar cheio, a conexão pode cair no meio de um processo. Até mesmo um inesperado inseto pode atrapalhar nossa vida...

Exception

As exceções nos programas do .net são tratadas todas de forma uniforme, independente de linguagem. Ou seja, as exceções que ocorrerem em nossas implementações ou em assemblies .net de terceiros, serão tratados da mesma maneira.

Podemos definir exceção como um comportamento ou estado esperado de forma implícita pelo sistema que não é atendido. Um exemplo seria ler um arquivo em disco que não existe, outro exemplo seria programarmos uma exceção para informar um comportamento ou estado inesperado.

As exceções são classes System.Exception ou descendentes desta e a .Net Framework já disponibiliza várias exceções que podem ser utilizadas. É interessante pensarmos no desenvolvimento de nossos sistemas em criarmos exceções específicas do nosso sistema, e estas devem descender de System.ApplicationException. Apesar da Classe ApplicationException descender de Exception, ela não acrescenta nenhum comportamento específico, apenas é uma prática sugerida A imagem 1 demonstra a hierarquia de exceções.


Imagem 1 - Hierarquia de Excecoes.gif

Dentre as propriedades mais utilizadas da classe Exception estão a propriedade Message, que tem as mensagem da exceção e a propriedade InnerException, que pode conter a exceção que ocasionou a exceção atual, isto será exemplificado no próximo parágrafo.

As exceções devem ser detectadas pelo nosso sistema, se a exceção for detectada por um método de nosso código e for possível recuperar desta, ou seja, atuar para resolvê-la, isto deve ser feito. Caso não seja possível a recuperação e tenha sido feito a detecção da exceção, podemos gerar informações do contexto atual, efetuarmos a liberação de recursos utilizados e repassarmos uma exceção para o método chamador. Veja na Imagem 2 este fluxo.

Imagem 2 - Tratamento de uma exceção

Em última instância, ou seja, caso uma exceção não seja manipulada e recuperada em nenhuma parte do programa, podemos logar a exceção, notificar usuários, informar o usuário do erro e devemos também liberar os recursos utilizados. Veja este esquema representado na Imagem 3.

Imagem 3 - Tratando exceções em última instância

Veremos agora como a .Net Framework, o C# e o ASP.NET facilitam a implementação deste tratamento.

Detectando, recuperando, liberando recursos ou repassando exceções

O C# implementa o detectar, recuperar, liberar e repassar exceções através das declarações try-catch-finally e throw. Por causa desta implementação ser um recurso do C# é utilizado para o desenvolvimento de aplicações WebForms ou WinForms da mesma maneira. Para mostrarmos uma aplicação, suponha que precisemos consultar no banco de dado AdventureWorks o total de contatos cadastrados na tabela Person.Contact, veja a imagem 4:

Imagem 4 - Try Finally.jpg

O bloco de código try é executado até o seu final ou até que uma exeção seja levantada. No bloco de código try devemos colocar todo e somente o código que pode levantar a exceção que queremos manipular, ou neste caso, garantir que o recurso seja liberado. Devemos evitar colocar códigos desnecessários no bloco try para liberarmos os recursos assim que não precisemos mais utilizá-los. O bloco finally é sempre executado, independente se o bloco try foi executado com sucesso ou alguma execeção foi levantada.

Uma outra sintaxe para utilizarmos algum recurso garantindo que este seja liberado após sua utilização é a declaração using. A Imagem 5 tem a mesma funcionalidade que o da imagem 4. A declaração using, permite utilizar-se de classes que implementam a interface IDisposable para que este recurso seja fechado após sua utilização.


Imagem 5 - Declaração Using.jpg

Podemos também querer recuperar de um exceção ou adicionar informações de um contexto do método ou parte do código. Vejamos na imagem 6



Imagem 6 - Try Catch Finally.jpg

O bloco catch é executado quando uma exceção é levantada pelo bloco try. Outro ponto importante é que somente o primeiro catch, que é do tipo da exceção levantada ou de algum dos tipos ancestrais da exceção levantada, é executado. Então devemos colocar as exceções mais genéricas por último. Depois da execução do bloco catch o bloco finally é executado se existir.

Caso queria ver mais sobre o tratamento de exceções, confira uma entrevista do Anders Hejlsberg, onde explica porque da opção de não implementar no C# o conceito de Checked e Unchecked exceptions, conceito este, existente no Java: http://www.artima.com/intv/handcuffsP.html


Referências:

http://msdn2.microsoft.com/en-us/library/ms954599.aspx

http://msdn2.microsoft.com/en-us/library/0yd65esw(VS.80).aspx
http://msdn2.microsoft.com/en-us/library/dszsf989(VS.80).aspx
http://msdn2.microsoft.com/en-us/library/dszsf989(VS.80).aspx
http://msdn2.microsoft.com/en-us/library/1ah5wsex(VS.80).aspx
http://weblogs.asp.net/jasonsalas/archive/2005/02/08/368811.aspx