Desenvolvimento - C#

Exceções - Uma visão geral

Todo mundo já sabe que uma exceção é uma barbeiragem ou do programador ou do usuário e, as vezes, é alguma bronca no ambiente onde sua aplicação está executando...

por Renato Guimarães



Todo mundo já sabe que uma exceção é uma barbeiragem ou do programador ou do usuário e, as vezes, é alguma bronca no ambiente onde sua aplicação está executando. Você pode, muito bem, pensar nas várias coisas que podem dar errado na sua aplicação. Quando sua aplicação estiver rodando podem acontecer dois tipos de erros: evitáveis e inevitáveis. Para os evitáveis podemos considerar a divisão por 0; e para os inevitáveis considere uma situação onde sua aplicação solicita um caminho de um arquivo e quando sua aplicação tenta acessar o arquivo verifica que o mesmo não é válido. Para os erros inevitáveis você deve preparar sua aplicação para tratá-los de forma adequada. Sendo assim, não escreva milhares de linha de código para tratar um erro de divisão por 0 ou erro por acessar um elemento inválido de um array. Caso sua aplicação deixe esses tipos de erros acontecerem pode sentar agora mesmo na sua cadeira e corrigir esse bug. A aplicação não deve ir para o usuário com esses tipos de bugs.

Um manipulador de exceção é um trecho de código que manipula erros inevitáveis. Seu programa levantará(ou lançará) um objeto de exceção quando for procurar o arquivo (inválido) que o sacana do usuário informou. Quer dizer que será criado um objeto com o erro (no caso do exemplo um FileNotFoundException) e depois o código que lida com esse erro será ativado. Se não for encontrado um bloco de código que trate aquele determinado erro, a execução do programa retorna ao chamador do método e verifica se lá tem o tratamento para aquele erro. Esta procura continua até que o erro seja manipulado ou o método Main() seja ativado. Se esse erro não for manipulado em lugar algum (inclusive o método Main()), o erro é apanhado pelo runtime do .NET. Isso não é muito bom porque o usuário receberá uma bela mensagem avisando que deu pau na sua aplicação.

Para que isso não aconteça, vamos aprender a antecipar, identificar, e manipular esses tipos de problemas efetivamente na nossa aplicação.

Lançando e apanhando exceções

Lançar uma exceção é quando você cria um novo objeto de erro devido a um determinada condição. Por exemplo, uma divisão por zero, o CLR gera um objeto System.DivideByZeroException para ser lançado e depois procura por um handler(manipulador) que apanhe aquela exceção. No exemplo abaixo não existe um handler que apanhe o error, então o CLR exibirá um caixa de diálogo ao usuário do programa, e depois terminará o programa.

public class DivisaoPorZero{
  public static void Main(){
    int a = 0;
    int b = 10; 
    int z = b / a; 
    System.Console.WriteLine("Devido ao erro nunca executará essa linha"); 
  }
}

Para esse bloco será emitido uma caixa de erro com a mensagem:

Application has generated an exception that could not be handled.

ou

An unhandled exception of type "System.DivideByZeroException" ocurred in...

Existem três tipos de blocos de código que você pode usar para testar, apanhar, e manipular erros em tempo de execução:

- try Diz ao CLR que estamos executando um bloco que pode ocorrer um erro

- catch Diz ao CLR o que fazer se acontecer um determinado tipo de exceção

- finally É o último trecho de código a ser executado, acontecendo ou não um erro.

Vamos dá uma geral nas situações de um tratamento de exceção:

  1. A execução do programa entra num bloco try. Cada linha de código é executada. Se nenhum error ocorrer, o controle é tranferido para o bloco finally (item 4). Se nenhum bloco finally existe, a execução continua na instrução que está após último bloco catch.

  2. Se um erro ocorrer, o CLR cria um objeto que representa o erro e transfere o controle para o catch. Cada bloco catch é examinado, na ordem que foram escritos. O primeiro bloco catch que lidar com aquele erro é executado e o controle, em seguida, é transferido para o bloco finally.

  3. Se não for encontrado um bloco catch que manipule o error, o CLR passa o objeto do error para o chamador do método. O processo 2 continua no chamador do método. Novamente, se nenhum bloco catch é encontrado, o erro é passado de volta até alcançar o método Maind(). Somente no método Main(), o error ou é apanhado ou o programa terminará prematuramente e exibirá uma caixa de diálogo informando o erro ocorrido.

  4. O bloco finally é executado. Perceba que não existe condição para ele executar. Isso porque é garantido (acontecendo ou não erro) que o bloco finally executará.

    O bloco Try...catch

    Qualquer suspeita de erro dentro de um bloco de código, coloque-o dentro de um try...catch. Com isso você estará dizendo ao compilador para tentar executar o código e, caso aconteça algum erro, saia do bloco e verifique se existe algum bloco catch que está tratando esse erro. Vamos melhorar o exemplo da divisão por zero.

    public class DivisaoPorZero{
      public static void Main(){
       try{
          int a = 0;
          int b = 10; 
          int z = b / a; 
          System.Console.WriteLine("Devido ao erro nunca executará essa linha"); 
       }catch (Systesm.Exception e){
         System.Console.WriteLine("Aconteceu um erro!");
         // escreva algum código para manipular o erro
       }
       System.Console.WriteLine("Agora esse bloco de código consiguirá executar até essa linha");
      }
    }
    

    Aqui nós dissemos ao CLR para tentar executar o código dentro do try. Claro, sabíamos que a divisão por zero causará um erro. O CLR criará um objeto para representar o erro. Perceba que tratamos erro da classe System.Exception. Essa classe é pai de todas as classes de erro. Sendo assim, podemos ficar tranquilo, pois qualquer erro será tratado por esse bloco. Perceba que nosso exemplo agora vai executar até o fim.

    No exemplo acima só colocamos um bloco catch, mas nada impede de colocarmos vários blocos catch. Cada um tratando um determinado tipo de exceção. Um erro é um objeto de uma determinada classe. Sendo assim, você pode criar suas próprias exceções normalmente. Qualquer classe de exceção que você crie vai, diretamente ou indiretamente, herdar da classe System.Exception.

    Um cuidado que você deve ter na construção dos blocos catch é colocar as exceções da mais específica a mais genérica. Abaixo temos o exemplo acima com mais um bloco catch que nunca será executado. Isto porque a exceção mais genérica está sendo tratada primeiro.

     
    public class DivisaoPorZero{
      public static void Main(){
       try{
          int a = 0;
          int b = 10; 
          int z = b / a; 
          System.Console.WriteLine("Devido ao erro nunca executará essa linha"); 
       }catch (Systesm.Exception e){
         System.Console.WriteLine("Aconteceu um erro!");
         // escreva algum código para manipular o erro
       }catch (System.DivideByZeroException dze){
         //algum código para manipular o erro.            
       }
       System.Console.WriteLine("Agora esse bloco de código consiguirá executar até essa linha");
      }
    }
    
    OBS: Como toda exceção é uma classe que herda de System.Exception, o código do bloco catch do System.Exception sempre será executado. Para resolver esse problema troque o bloco catch de System.DivideByZeroException pelo bloco do System.Exception. Tenha cuidado na ordem dos blocos catch.

    O bloco finally é a parte que sempre será executada, havendo ou não uma exceção. Normalmente, colocamos esse código quando estamos escrevendo algum bloco de código que abre conexão com banco de dados ou faz uma operação de leitura e escrita. Isto porque queremos liberar os recursos alocados havendo ou não uma exceção.

    public class DivisaoPorZero{
      public static void Main(){
       try{
          int a = 0;
          int b = 10; 
          int z = b / a; 
          System.Console.WriteLine("Devido ao erro nunca executará essa linha"); 
       }catch (Systesm.Exception e){
         System.Console.WriteLine("Aconteceu um erro!");
         // escreva algum código para manipular o erro
       }catch (System.DivideByZeroException dze){
         //algum código para manipular o erro.            
       }finally{
         //hvendo ou não uma exceção esse bloco sempre será executado
       }
       System.Console.WriteLine("Agora esse bloco de código consiguirá executar até essa linha");
      }
    }
    

    Existirão momentos que você terá a necessidade lançar alguma determinada exceção devido a alguma condição. Talvez quando estiver projetando as regras de negócio de sua aplicação você descobrirá um tipo de erro que pode acontecer com certa frequência, por exemplo, "SaldoInsuficienteException" ou "ClienteNaoCadastradoException". Poucas coisas devem ser feitas para que se crie suas próprias exceções:

    • Herde da classe System.Exception. Você também pode criar suas exceções a partir da classes System.ApplicationException.
    • Sobrescreva o construtor da classe base. Em particular, você pode querer atribuir a mensagem de erro adequada.
    • Dê um nome descritivo a sua classe. Por exemplo, se você está criando uma exceção para dizer que um cliente não foi cadastrado, então ClienteNaoCadastradoException. Fazendo assim você está seguindo um bom padrão de codificação

    Abaixo segue um exemplo de uma classe onde sobrescrevemos o construtor da classe base. O exemplo é bem simples, mas é só pra dar idéia do que você pode fazer:

    public class ClienteNaoCadastradoException : ApplicationException{
                public ClienteNaoCadastradoException (String message) : base ("Cliente não cadastrado."){
                }
    }
    

    Para lançarmos uma exceção, só precisamos utilizar a palavra throw. No código abaixo temos um exemplo de como utilizar o throw. Para isso, vamos criar dois tipos de exceções. O nosso exemplo pedirá para o usuário entrar com um número entre 12 e 42. Uma vez ou outra o usuário está totalmente distraído e informa um número fora desse intervalo. Sendo assim, você manda uma exceção para dar um susto no "demente" e ele prestar mais atenção.

    OBS: O exemplo abaixo poderia ser tratado de forma mais simples, ou seja, não era preciso levantar exceção, mas é só para demonstrar a utilização da palavra throw.

    using System; 
    public class TestarValorEntrada{
    	public static void Main(){
        	try{
            	int x; 
                Console.Write("Digite um número entre 12 e 42(presta atenção no intervalo dos valores): ");
                x = Int32.Parse(Console.ReadLine());
                if (x > 42)
                	throw new MaiorQue42Exception();
                else if (x < 12) 
                	throw new MenorQue12Exception();
    		}catch(MaiorQue42Exception m){
            	Console.WriteLine("Animal! Você digitou um número maior que 42");
            }catch(MenorQue12Exception md){
            	Console.WriteLine("Animal! Você digitou um número menor que 12");
            }catch(Exception ex){ //Caso aconteça uma exceção não esperada, esse bloco irá tratá-la
            	WriteLine(ex.Message);       
    		}
    	}
    }
    
    class MaiorQue42Exception : Exception {
       //algum código 
    }
    
    class MenorQue12Exception : Exception {
       //algum código 
    }
    

    Exceções do Framework .NET

    A galera do .NET já fez um grande favor e já definiu um conjunto de exceções para facilitar a nossa vida. Essas classes estão dentro da Base Class Library. Vamos dar uma olhada na mãe (todo mundo herda dela) de todas as exceções - System.Exception.

    System.Exception - É a classe mãe de todas as exceções. Todas as exceções do framework ou as implementadas pelo desenvolvedor devem herdar direta ou indiretamente de System.Exception. Então, vamos dar uma olhada em algumas propriedades e métodos dessa classe.

    - Message retorna uma mensagem que descreve a exceção
    - Source ler ou escreve o nome do objeto que causou a exceção
    - StackTrace permite você mostrar a pilha de execucação no momento da exceção.
    - TargetSite retorna o método que lançou a exceção
    - InnerException obtém a instância Exception que causou a exceção corrente.

    Em um pequeno exemplo de divisão por 0 não dá para mostrar o quão é importante o tratamento de exceções dentro de uma aplicação. Imagine sua aplicação que acessa dados na rede, ler e escreve arquivos, utiliza servidor de e-mail, manipula dados dos usuários, etc. Podemos ver um cenário onde várias coisas podem dar errado e sua aplicação precisa estar preparada para esses inesperados acontecimentos. Fazendo assim, você terá uma aplicação robusta e show de bola.

    Abraço a todos e aguardo vocês no próximo artigo....

Renato Guimarães

Renato Guimarães - Bacharel em Sistemas de Informação e trabalha com tecnologia da informação há mais de 15 anos.