ABC App – 04 Exceções

Ia deixar para escrever sobre o assunto depois, mas pra começar bem a semana vou usar o comentário do Tucaz e fazer um Refactoring no código e tratar Execeptions, ou Exceções!

exceção
ex.ce.ção
sf (lat exceptione) 1 Ato ou efeito de excetuar. 2 Desvio de regra, de lei, de princípio ou de ordem. 3 A coisa excetuada; aquilo que se desvia da regra. 4 Prerrogativa, privilégio. 5 Pessoa cujo modo de pensar ou de proceder se afasta do comum e usual. 6 Dir Alegação jurídica, constituindo defesa indireta (difere da contestação, que é defesa direta), pela qual o réu pretende baldar a ação intentada. E. declinatória, Dir: a que visa a declinar a competência do juiz ou tribunal ao qual foi apresentada a demanda. E. dilatória, Dir: a que pretende apenas demorar a demanda. E. peremptória, Dir: a que de todo e definitivamente afasta a demanda.

Conforme a definição acima do Michaelis, exceção é um desvio da regra. Na classe Customer da camada de DAL, quando fazemos acesso ao banco de dados para buscar um ou mais usuários, a EntLib abre uma conexão e executa o comando Select, essa é a regra, mas e quando o banco de dados não esta disponível? Ou se alguém renomeou ou apagou a tabela que esta na nossa query? Isso são exceções, e se não tratarmos, o sistema para de funcionar! Para testar é só desligar o banco de dados (parando o serviço SQL Server Browser) e rodar o nosso software.

Existem várias correntes de pensamento sobre Exceptions

Devemos tratar Exceptions como conexões a banco de dados, mas se um usuário do sistema cadastrar um número inválido de CPF devemos lançar uma Exeception? Devemos criar nossas próprias Execeptions para o nosso sistema? Onde devemos tratar? Muitas perguntas, algumas vão ficar sem respostas no momento, o objetivo desse post será fazer o Refactoring, e o primeiro será na conexão do banco.

Tratamento de Exceptions

No .Net é usado a tríade Try-Catch-Finally, o código passível de erro e que pode vir a lançar uma exceção é colocado no bloco do Try, no Catch é onde se pegam as execeções, e o Finally serve para finalizar alguma operação, limpar alguma variável,  e, muitas vezes,  ele não é usado, mas no nosso primeiro exemplo já faremos uso dele.
Uma regra que devemos sempre atentar é: nunca, NUNCA, colocar um método inteiro dentro de um bloco Try, não há necessidade de colocar criação de variáveis! Teste somente o que pode dar um erro. Além disso,  o código fica mais elegante.

No código da classe Customer da camada de DAL o que pode dar erro? O mais óbvio seria quando o DataReader estivesse sendo carregado, se nessa hora o banco de dados cair o nosso software vai pro espaço! Como no método AdaptToList ainda usamos o DataReader (ele fica aberto para popularmos o objeto Customer)  vamos colocar a linha de ExecuteReader e a de chamada do AdaptToLista dentro do Try. Para fazer isso,  vamos usar o recurso de Refactoring da IDE :  selecione as duas linhas, clique CTRL, e no menu de contexto escolha Surrond With…

Fazendo um Surrond With no código
Fazendo um Surrond With no código

Escolha a opção “tryf”, conforme figura abaixo, repare que este é um atalho para um Snippet, então quando estiver escrevendo código usando este atalho economiza tempo!

Escolha "tryf"
Escolha “tryf”

O código estará dentro do bloco Try, veja outras alterações no código abaixo e em seguida o por quê

[code lang=”csharp”]
public Domain.Customer GetCustomerById(int customerId)
{
IDataReader dr = null;
IList<Domain.Customer> lstCustomers = null;

string sql = “select customerid, firstname, middlename, lastname, companyname, emailaddress, phone, modifieddate from saleslt.customer where customerid = @customerid”;

DbCommand cmd = db.GetSqlStringCommand(sql);

db.AddInParameter(cmd, “customerid”, DbType.Int32, customerId);

try
{
dr = db.ExecuteReader(cmd);
lstCustomers = AdaptToList(dr);
}
catch (System.Data.SqlClient.SqlException exsql)
{
throw new Exception(exsql.Message);
}
catch {
}

finally
{
if (!dr.IsClosed)
dr.Close();
}

return ((lstCustomers != null) || (lstCustomers[0] != null)) ? lstCustomers[0] : null;
}
}
[/code]

Listagem 01

Movi a declaração do DataReader para o início do método, pois podemos deixar dentro do Try, já que o Finally não iria enxergar esse objeto e depois é só mover a linha que faz a verificação se o DataReader estiver aberto para dentro do Finally, ocorrendo ou não um erro o Finally sempre é executado e daí ele ficará responsável por fechar o DataReader.
O Tucaz também fez um comentário sobre setar o CommandBehavior para CloseConnection, o que faria a conexão com o banco ser fechada após fecharmos o DataReader, mas a EntLib cuida disso pra gente! Veja o código fonte, e essa é a parte legal de usar essa biblioteca da MS, pois com o acesso ao fonte vemos as boas práticas sendo aplicadas, aquelas que se encontram no Guia de Acesso a Dados.
O Catch vazio vai pegar todo o tipo de exceção, então é bom especializarmos o Catch, por isso eu também coloquei para pegar a SQLException.

Aplique esse Refactoring no método GetCustomers() também.

OK… E vamos fazer isso também quando é criado o Database na classe BaseDAL, pois se o banco de dados estiver fora do ar vai dar um erro. É só executar os mesmos passos em cima da única linha que temos no único construtor da classe. E vai ficar assim:

[code lang=”csharp”]
public abstract class BaseDAL
{
public EntLib.Database db { get; set; }

public BaseDAL()
{
try
{
db = EntLib.DatabaseFactory.CreateDatabase(“Connection String”);
}
catch (Exception ex)
{
throw ex;
}
finally
{

}
}
}
[/code]

Listagem 02
E agora temos que exibir esses erros lá na nossa interface de usuário, aqui vai o código de como vai ficar:

[code lang=”csharp”]

namespace ABCApp.Console
{
class Program
{
static void Main(string[] args)
{

ABCApp.Domain.Customer c;

ABCApp.DAL.Customer dalCustomer = new ABCApp.DAL.Customer();

try
{
c = dalCustomer.GetCustomerById(1);
System.Console.WriteLine(c.CustomerId.ToString() + ” – ” + c.FirstName.ToString() + ” ” + c.LastName.ToString());
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
}

System.Console.ReadKey();

try
{
IList<Domain.Customer> lstCustomer = dalCustomer.GetCustomers();

foreach (Domain.Customer customer in lstCustomer)
{
System.Console.WriteLine(customer.CustomerId.ToString() + ” – ” + customer.FirstName.ToString() + ” ” + customer.LastName.ToString());
}
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
}

System.Console.ReadKey();
}
}
}
[/code]

Vamos testar? Para isso desligue o seu SQL Server! Vá no menu Iniciar > Microsoft SQL Server 2008 > Configuration Tools > SQL Server Configuration Manager, entre na ferramenta.  Pare o serviço SQL Server Browser, daí não deve ser possível conectar na base. Rodando a aplicação devem ser impressas mensagens de erro onde deveriam aparecer dados!

Bom… Isso foi só para começarmos a colocar um controle sobre os locais onde podem ocorrer Exceptions, esse não é o mundo perfeito, e nem será a última vez que irei falar sobre o tema. Mas por que não ler alguns links sobre isso? Aí vai:

http://unplugged.giggio.net/unplugged/post/Como-tratar-erros.aspx

http://msdn.microsoft.com/en-us/library/dd203116.aspx (Esse é sobre o bloco de Exceptions da EntLib, mais pra frente vamos usar ele)

http://www.developerfusion.com/article/5250/exceptions-and-performance-in-net/ (discute a questão de performance ao lançar exceções)

http://yoda.arachsys.com/csharp/exceptions2.html (também sobre performance)

http://www.artima.com/interfacedesign/AbnormalConditions.html

http://apparch.codeplex.com/ (aqui tem um vídeo sobre o tema)

Fico por aqui… Na quinta-feira post sobre objetos anêmicos…

O código deste post esta no Change Set 36605.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *