Arquivo

Archive for the ‘Qualidade de Código’ Category

Layer Supertype Pattern

superman logoUm bom código deve evitar ao máximo a replicação de suas partes, favorecendo a manutenção. Nesse post, vamos apresentar um artifício para combater esse problema.

O padrão Layer Supertype tem como objetivo definir uma classe que agrupa operações que são semelhantes em todas as classes de uma camada. Vamos ao exemplo:

public class Teacher
{
    private string name;
    private int Age;

    public int Id { get; set; }

    public void Insert()
    {
       this.IsValid();
       //código de persistência comum a todas as classes
    }

    public void Update()
    {
        this.IsValid();
        //código de persistência comum a todas as classes
    }

    private bool IsValid()
    {
       //código de validação específico para a entidade Teacher
    }
}

public class Lesson
{
    private int level;

    public int Id { get; set; }

    public void Insert()
    {
       this.IsValid();
       //código de persistência comum a todas as classes
    }

    public void Update()
    {
       this.IsValid();
       //código de persistência comum a todas as classes
    }

    private bool IsValid()
    {
      //código de validação específico para a entidade Lesson
    }
}

Note que as classes possuem os mesmos métodos Insert, Update e IsValid, além da propriedade Id. Utilizando o padrão Layer Supertype, criaremos uma classe base que vai agrupar essas operações e será herdada pelas demais classes da camada.

O código fica assim:

public class BaseEntity
{
    public int Id { get; set; }

    public void Insert()
    {
       this.IsValid();
       //código de persistência comum a todas as classes
    }

    public void Update()
    {
       this.IsValid();
       //código de persistência comum a todas as classes
    }

    protected abstract bool IsValid();
}

public class Teacher : BaseEntity
{
   private string name;
   private int Age;

   protected override bool IsValid()
   {
      //...
   }
}

public class Lesson : BaseEntity
{
    private int level;

    protected override bool IsValid()
    {
      //...
    }
}

Com isso, evitamos duplicação de códgio, centralizando a lógica de negócio que estaria repetida por diversas classes em uma única classe. O código ficou mais limpo e mais fácil de manter.

Boas Práticas no uso de Exceções

AtrExceção Null Referenceavés da manipulação de exceção podemos tratar situações indesejadas que podem acontecer durante a execução de um código. Nesse post, vamos discutir sobre algumas práticas para melhor utilizar esse recurso em nossos códigos.

Primeiramente, exceções não devem ser usadas para controlar o fluxo da aplicação, como se fosse uma estrutura if-then-else. Veja o exemplo abaixo:

IManager manager;
try
{
manager = businessManagerRepository.GetManager(managerId);
//Lógica para o Gerente de Negócio
}
catch(BusinessManagerNotFoundException ex)
{
manager = projectManagerRepository.GetManager(managerId);
//Lógica para o Gerente de Projetos
}

A estrutura try-catch não deve ser usada como meio de escolha do caminho a ser tomado segundo o levantamento de uma exceção.

Outra boa prática é diferenciar as exceções de sistemas das exceções de negócio. Ao serem lançadas exceções de negócio devem permitir que o usuário refaça a operação. Geralmente elas são exceções personalizadas, inerentes ao domínio da aplicação.

No caso de exceções de sistema, é importante adicioná-la ao log e verificar se ela foi gerada por uma falha no processo de comunicação com algum serviço, como o banco de dados por exemplo. Esse tipo de exceção pode gerar um estado inconsistente da aplicação. Dessa forma, é necessário voltar ao estado correto (ou até mesmo ao estado inicial) garantindo que as operações futuras não sofram alterações de comportamento devido ao erro ocorrido.

Nos dois casos, sempre exiba uma mensagem amigável ao usuário. Mensagens como “Ocorreu um erro durante o cadastro do cliente” são muito melhores que “Null Reference exception on line 25”.

Outra técnica importante é apenas capturar exceções são “esperadas”. Veja o exemplo:

try
{
this.SendMessageToCustomer(message);
}
catch(Exception ex)
{
//tratamento da exceção
//...
errorMessage = "Não foi possível enviar a mensagem";
}

O código captura uma exceção do tipo Exception. Esse é um tipo muito genérico e pode acabar encobrindo de forma indesejada algum erro na aplicação (principalmente se um log não estiver sendo utilizado). No exemplo acima, pode ter ocorrido um erro no formato da mensagem, ou na conexão com o servidor de email ou outra falha qualquer. Em ambos os casos, não será possível rastrear a verdadeira causa do levantamento da exceção.

O melhor nesse caso é capturar uma exceção mais especializada. Se nenhuma exceção já existente servir para esse caso, crie uma que possua essa responsabilidade. O código ficaria assim:

try
{
this.SendMessageToCustomer(message);
}
catch(CanNotConnectToEmailServerException ex)
{
//tratamento da exceção
//...
errorMessage = "Não foi possível enviar a mensagem. Ocorreu um erro na conexão com o Servidor de Email.";
}

Lembre-se que o framework já possui exceções para diversas situações diferentes. Utilize-as sempre que puder, para não reinventar a roda.

Números Mágicos

Um dos principais fatores que tornam um código ruim e difícil de ler é o aparecimento dos Números Mágicos.

Os Números Mágicos são um dos problemas mais antigos da programação. São valores contantes que representam valores próprios do domínio da aplicação. Eles geralmente não são óbvios, ou seja, não explicam o que fazem ou o motivo pelo qual estão sendo usados. Veja um exemplo:


public void MoverPartícula(double tempo)
{
this.posicao += 10.5 * tempo;
}

O método acima movimenta uma partículá utilizando a equação do movimento uniforme S = So + V x T. Dessa forma, o valor 10.5 representa a velocidade da partícula. Note que essa informação não está explícita no código, sendo necessário alguma explicação a respeito desse valor.

Números MágicosEncontramos o primeiro problema nos Números Mágicos: eles não deixam claro o que são (nem o que representam).

Outro problema ocorre quando um Número Mágico aparece em vários locais do código. Caso seja necessário modificar o valor, será preciso alterar todos os locais em que ele aparce. E esse procedimento pode ser trabalhoso, se ele aparecer 50 lugares diferentes, por exemplo.

Aí está mais um problema: Números Mágicos deixam o código difícil de ser mantido/alterado.

Para solucionar esses problemas, o melhor a se fazer é criar uma variável (ou atributo) que encapsule esse número.


private double readonly VELOCIDADE_PARTICULA = 10.5;

Alteramos agora os locais onde valor aparece, para utilizar a variável. O método MoverPartícula fica assim:


public void MoverPartícula(double tempo)
{
this.posicao += VELOCIDADE_PARTICULA * tempo;
}

Agora, qualquer alteração no valor da velocidade será feita apenas em um lugar (alterando-se o atributo) e o código ficou mais fácil de ser entendido, devido ao nome explicativo que representa totalmente o valor encapsulado. Criamos, assim, um código limpo.

Princípio da Responsabilidade Única (SRP)

Como já tinha falado no post sobre Princípios S.O.L.I.D., o Princípio da Responsabilidade Única (SRP) estabelece que uma classe, método ou variável deve fazer apenas uma coisa e deve ter apenas uma razão para mudar. Esse talvez seja o princípio mais fundamental no desenvolvimento de um código limpo. Todos os outros princípios se baseam nele ou estão sobre ele fundamentados.

Vamos mostrar como utilizar esse princípio. Antes disso, contudo, veja o exemplo de classe abaixo (que não respeita o SRP).

public class Controlador
{
	public List<Conta> contas;
	public List<Cliente> clientes;

	public void DepositarQuantiaEmConta(Guid contaId, double quantia)
	{...}

	public void SacarQuantiaEmConta(Guid contaId, double quantia)
	{...}

	public Conta ProcurarContaPeloId(Guid contaId)
	{...}

	public void CriarNovaConta(Cliente cliente)
	{...}

	public void ExcluirConta(Guid contaId)
	{...}

	public void TransferirQuantia(Conta contaOrigem, Conta contaDestino)
	{...}

	public void CadastrarCliente(Cliente cliente)
	{...}

	public void ExcluirCliente(long clienteId)
	{...}

	public void ProcurarClientePeloNome(string nomeCliente)
	{...}
}

É fácil notar que a classe tem mais de uma responsabilidade. Ela trada de operações independentes relacionadas com as entidades Cliente e Conta. O próprio nome da classe não deixa claro a sua funcionalidade.

Ao ver o nome da classe não sabemos inicialmente ao que ela se refere. “Controlador de quê?” É a primeira pergunta que fazemos. É melhor evitar esses nomes extremamente genéricos. Defina bem o nome e certamente será mais fácil delegar apenas uma responsabilidade.

Além disso, a maioria dos códigos que não respeitam o SRP recaem sobre o antipadrão The Blob. Antipadrão? O que é isso!?

Antipadrões

Um Antipadrão de Desenvolvimento de Software descreve uma má prática que torna o código ruim, ilegível ou difícil de manter (e de ser alterado). Antipadrões (Antipatterns) são a contra-parte dos Padrões de Projeto (Design Patterns).

the blob imageO código acima está fortemente similar ao antipadrão The Blob. Isso porque temos uma única classe que controla todo o sistema. Essa “Classe-Monstro” engloba toas as reponsabilidades (e ações) que do sistema. Dessa forma, caso seja necessário adicionar mais uma funcionalidade ao código, a classe vai aumentar ainda mais e em alguns meses teremos uma “Mega Classe” que será bastante difícil de encontrar, alterar ou remover qualquer coisa. Mesmo se essa coisa for simples!

Aplicando o SRP

Para tornar nosso código mais limpo, vamos aplicar o conceito definido pelo SRP. Para isso, vamos extrair cada responsabilidade da nossa “Classe-Monstro” em várias classes pequenas que comportem cada uma delas. As pequenas classes devem  fazer apenas uma coisa, respeitanto o SRP.

Responsável pelas operações sobre a entidade Cliente:

public class RepositorioCliente
{
	public void CadastrarCliente(Cliente cliente)
	{...}

	public void CadastrarCliente(Cliente cliente)
	{...}

	public void ExcluirCliente(long clienteId)
	{...}

	public void ProcurarClientePeloNome(string nomeCliente)
	{...}
}

Classe responsável pelas operações sobre a entidade Conta:

public class RepositorioConta
{
	public Conta ProcurarContaPeloId(Guid contaId)
	{...}

	public void CriarNovaConta(Cliente cliente)
	{...}

	public void ExcluirConta(Guid contaId)
	{...}
}

A entidade Conta engloba as operações de Sacar e Depositar quantia:

public class Conta
{
	private Guid id;
	private double saldo;

	public void Depositar(double quantia)
	{...}

	public void Sacar(double quantia)
	{...}
}

Não faz muito sentido inserir a operação de transferência de quantia na classe Conta. Ela se encaixa melhor em uma entidade separada, dedicada exclusivamente a ela.

public class ContaService
{
	public void TransferirQuantia(Conta contaOrigem, Conta contaDestino)
	{...}
}

Note que o código ficou mais simples de ser entendido. E agora, caso seja necessário alterar alguma funcionalidade, modificaremos apenas a classe que a engloba, sem alterarmos todo o código. O sistema ficou mais extensível e menos emaranhado.

Até o próximo post.

Princípios S.O.L.I.D.

Canivete suíçoS.O.L.I.D. é um conjunto de boas práticas de projetos Orientado a Objetos. Essas práticas foram elencadas por Robert Martin (o “Tio Bob”) em seu livro Agile Principles, Patterns, and Practices in C#. A aplicação desses princípios tem como objetivo a construção de software mais flexível, legível, fácil de entender e de modificar.

O termo S.O.L.I.D. é um acrônimo formado pelas iniciais de cada princípio. E quais são esses princípios? Pois bem, nesse post vamos fazer um pequeno resumo sobre eles:

Single Responsibility Principle (SRP)

Esse princípio estabelece que todo objeto deve ter apenas uma razão para mudar e somente uma responsabilidade. Objetos concisos aumentam a capacidade de manutenção do sistema e o deixam mais legível. Para saber mais leia esse post.

Open-Close Principle (OCP)

Establece que classes devem ser abertas para extensões e fechadas para modificações. Deve ser simples adicionar novas funcionalidades e extender as antigas sem, contudo, alterar seu comportamento interno. Esse princípio também tenta evitar a quebra de classes em outras que dependam dela.

Liskov Substitution Principle (LSP)

Afirma que deve-se ser capaz de utilizar qualquer classe derivada no lugar da classe pai e obter o comportamento semelhante sem que seja necessária alguma modificação. O LSP acessegura que a classe derivada não deve afetar o comportamento da classe pai, estando assim de acordo com o OCP.

Interface Segregation Principle (ISP)

Estabelece que classes que utilizam a mesma interface implementem apenas métodos que sejam realmente necessários. Dessa forma é melhor dividir os métodos em grupos de responsabilidade, assinando (atribuindo) interfaces para estes grupos, evitando uma interface muito grande e métodos que não serão usados.

Dependency Inversion Principle (DIP)

Objetiva isolar classes de implementações concretas, expondo classes abstratas ou interfaces. Esse princípio promove a prática de “codificar para interface ao invés de “codificar para implementação”, aumentando a flexibilidade e desacoplando o sistema.

Se quiser saber mais sobre esses princípios dê uma olhada neste artigo do Uncle Bob, onde ele descreve cada um deles.

Parâmetros de Funções

Série de Maclaurin

O número ideal de parâmetros de uma função é zero. Grandes quantidades de parâmetros tornam mais difícil a manutenção do código. Por isso, caso parâmetros sejam realmente necessários, a melhor escolha é evitar uma quantidade maior que dois.

Devem ser evitados também parâmetros booleanos que modificam o comportamento da função. Por exemplo, no código abaixo a função “DebitarJuros” recebe um parâmetro que indica se o Juros aplicado é Especial:

public void DebitarJuros(bool jurosEspecial)
{
    if(jurosEspecial)
    {
        this.Saldo -= JUROS_ESPECIAL;
    }
    else
    {
       this.Saldo -= JUROS_COMUM;
    }
}

Nesse caso, a maneira mais adequada seria criar dois métodos, um para cada tipo de Juros. Dessa forma, eliminamos o parâmetro e deixamos o código mais fácil de entender. Mesmo sem ver a implementação dos métodos, claramente percebemos que eles aplicam juros diferentes:

public void DebitarJurosEspecial() {...}

public void DebitarJurosComum() {...}

Em geral, quando uma função tem mais de três parâmetros é possível agrupá-los em uma classe, tornando-a mais fácil de ser ententida e alterada:

public void InsereUsuarioNoBancoDeDados(string nome, string login, string senha, DateTime dataNascimento)
{...}

public void AtualizaUsuarioNoBancoDeDados(int id, string nome, string login, string senha, DateTime dataNascimento)
{...}

Os métodos acima recebem as informações a respeito de um usuário para que possam ser inseridas ou atualizadas no Banco de Dados. Note que se, devido a alguma alteração no Banco de Dados, for necessário adicionar mais uma informação sobre o usuário (como o estado civil, por exemplo) precisaremos alterar os dois métodos, adicionando mais um parâmetro:


public void InsereUsuarioNoBancoDeDados(string nome, string login, string senha, DateTime dataNascimento, string estadoCivil)
{...}

public void AtualizaUsuarioNoBancoDeDados(int id, string nome, string login, string senha, DateTime dataNascimento, string estadocivil)
{...}

Note que se agruparmos as informações de um usuário em uma classe, qualquer alteração nessas informações afetará apenas essa classe.

public class Usuario
{
public string Nome { get; set; }
public string Login { get; set; }
public string Senha { get; set; }
public DateTime DataNascimento { get; set; }
}

E os métodos receberão um objeto Usuário como parâmetro:

public void InsereUsuarioNoBancoDeDados(Usuario usuario)
{...}

public void AtualizaUsuarioNoBancoDeDados(Usuario usuario)
{...}

Como vimos, diminuir o número de parâmetro em nossas funções além de deixar o código mais fácil de ser entendido, ajuda quando ocorrerem modificações, tornado mais simples a refatoração.

Comentários de Código

Comentário ruim no códigoGeralmente, quando estamos escrevendo nossos códigos temos a preocupação (ou a necessidade) de explicar certas partes que podem não estar tão claras. Utilizamos, então, comentários para esclarecer o propósito daquela função, variável ou classe.

Muito embora essa prática alcance seu objetivo imediato, ela esconde uma verdade importante: se o código precisa ser explicado, então ele não está bem escrito o bastante; ele é um código ruim.

Além disso, comentários são muitas vezes traiçoeiros. O código muda bastante e na maioria das vezes o programador acaba esquecendo de refletir essas mudanças em seus comentários.

Exemplo 1:


   //Retorna o cliente passando o ID como parâmetro
   public Cliente GetCliente(string CPF) {...}

Note que o programador esqueceu de alterar o nome do parâmetro de ID para CPF em seu comentário, trazendo uma informação errada para o seu código.

Outro uso negativo dos comentários é a redundância de informação, como mostra o exemplo abaixo:

Exemplo 2:


public class Cliente
{
    //Data de Nascimento
    private DateTime dataNascimento;

    //Nome do Cliente
    private string nome;
}

Neste caso, a informação dos comentários não agrega valor algum ao entendimento do código. A melhor escolha seria não utilizá-los.

Na maioria das vezes, um comentário pode ser substituído refatorando (e melhorando) o código. Nos casos em que o comentário for realmente necessário, ele deverá ser curto e direto.

Exemplo 3:

   /* O método ExecuteUpdateAsAdministrator não é Thread Safe */
   public void ExecuteUpdateAsAdministrator() {...}

O exemplo 3 mostra uma boa utilização de comentário, alertando o programador sobre uma consequência do uso do código.

Assim, tente diminuir o uso dos comentários. Refaça o código e altere o nome das entidades. Seu código ficará muito mais fácil de ser entendido.