Introdução
Muitos programadores, principalmente quando ainda estão na fase inicial do aprendizado sobre programação, escrevem códigos que, quando vão ver posteriormente, nem eles mesmos entendem o porquê de terem escrito o código daquela forma. Falta de identação e organização estética em geral, códigos demasiadamente extensos para resolver uma tarefa simples, repetição de código desnecessariamente.
Esse tipo de prática é comum e se acentua quando o código é desenvolvido “às pressas”, ou seja, quando o responsável não dispõe do tempo necessário para dar atenção a “detalhes” como os citados acima. Outro fator que contribui para que o código “cheire mal” (do inglês bad smell), termo bem humorado introduzido por Kent Beck, um dos criadores da Programação Extrema (Xtreme Programing), é a falta de conhecimento técnico sobre as ferramentas que se está utilizando e não entendimento pleno do cenário real para o qual se desenvolve o código.
Em geral não é interessante manter esse tipo de problema no código, então nesse momento entra a refatoração. Neste artigo serão apresentados os principais conceitos sobre refatoração de código, com exemplos práticos na linguagem C#. Além disso, serão apresentados alguns recursos do IDE Visual Studio que auxiliam e tornam mais prática a refatoração.
Refatoração
A refatoração consiste em aprimorar a estrutura interna do código sem, contudo, alterar seu resultado externo. Essa melhoria deve ser tanto estética quanto organizacional e muitas vezes requer a reescrita total de certos blocos de código.
Apesar de parecer um pouco “retrabalho” e contrariar o princípio “em time que está vencendo, não se mexe”, ou seja, “se o código está funcionando, não precisa ser alterado”, os benefícios da refatoração são facilmente observados logo após sua execução.
Alguns sinais que podem indicar a necessidade de refatoração são:
- Código duplicado (mesmo bloco de código repetido várias vezes);
- Métodos e classes muito extensos (nesse casso também podem estar sendo contrariados princípios como o SRP - Single Responsibilty Principle, ou Princípio da Responsabilidade Única);
- Lista de parâmetros muito extensa (o que provavelmente indica um método muito longo que deve estar fazendo mais do que deveria);
- Má identação (código esteticamente desorganizado, o que dificulta a compreensão para quem o lê posteriormente).
Exemplos práticos
A partir deste ponto serão apresentados exemplos práticos de refatoração, considerando alguns dos pontos citados. Inicialmente será apresentado um código com problemas e, em seguida, o mesmo código refatorado.
Código repetido e métodos muito longos
Listagem 1: Exemplo de código repetido
public class Produto
{
public string Descricao;
public decimal Preco;
public decimal Estoque;
public void Salvar()
{
if (Descricao.Length > 50)
{
Console.WriteLine("A descrição deve ter no máximo 50 caracteres.");
return;
}
if (Preco <= 0)
{
Console.WriteLine("O preço deve ser maior que zero.");
return;
}
if (Estoque < 0)
{
Console.WriteLine("O estoque não pode ser negativo");
return;
}
//Inserir produto no banco de dados
}
public void Atualizar()
{
if (Descricao.Length > 50)
{
Console.WriteLine("A descrição deve ter no máximo 50 caracteres.");
return;
}
if (Preco <= 0)
{
Console.WriteLine("O preço deve ser maior que zero.");
return;
}
if (Estoque < 0)
{
Console.WriteLine("O estoque não pode ser negativo");
return;
}
//Alterar produto no banco de dados
}
}
No código acima temos uma classe Produto com dois métodos, Salvar a Atualizar. Em ambos os métodos, antes de se efetuar a operação principal (inserção e atualização do produto na base de dados), são feitas verificações sobre suas propriedades, para garantir que elas seguem certas regras.
Nesse exemplo a classe possui apenas dois métodos, porém, em um sistema real possivelmente haveriam outras operações a serem implementadas e a classe possuiria mais atributos a serem verificados.
Então, refatorando esse código, poderíamos colocar toda essa verificação em um método e, sempre que fosse preciso avaliar os atributos, bastaria chamar esse método. Isso é feito na listagem a seguir, onde foi criado o método ValidarAtributos.
Listagem 2: Removendo código repetido usando método
public class Produto
{
public string Descricao;
public decimal Preco;
public decimal Estoque;
private bool ValidarAtributos()
{
if (Descricao.Length > 50)
{
Console.WriteLine("A descrição deve ter no máximo 50 caracteres.");
return false;
}
if (Preco <= 0)
{
Console.WriteLine("O preço deve ser maior que zero.");
return false;
}
if (Estoque < 0)
{
Console.WriteLine("O estoque não pode ser negativo");
return false;
}
return true;
}
public void Salvar()
{
if(ValidarAtributos())
{
//Inserir produto no banco de dados
}
}
public void Atualizar()
{
if (ValidarAtributos())
{
//Alterar produto no banco de dados
}
}
}
Caso a validação de um dos atributos falhe, o método ValidarAtributos retorna falso e, usando esse resultado, os métodos Salvar e Atualizar podem manter o foco no seu real objetivo.
Essa modificação na estrutura do código garante, pelo menos, dois benefícios facilmente observáveis:
- Evita erros humanos como a não validação de um dos atributos em algum ponto, por esquecimento ou por cópia incompleta do código previamente desenvolvido, pois toda validação estará centralizada em um único método.
- Evita repetição de trabalho futuramente. Por exemplo, caso a validação de um campo mude ou outro campo seja inserido, não será preciso fazer a mesma alteração em vários pontos do código, apenas no método de validação.
Observando bem, vemos que além do código repetido, também foi melhorada a questão do método muito longo, pois as operações foram divididas em partes com funções específicas.
Má identação
A má identação (organização das linhas de código “hierarquicamente”, ou seja, seguindo níveis de tabulação e espaçamento bem definidos que deixem claro o fluxo do código), além de deixar o código “feio”, é um dos principais agravantes para a dificuldade de compreensão do código posteriormente à escrita.
A listagem a seguir apresenta um exemplo de código totalmente desorganizado, sem identação nenhuma. Em seguida o mesmo código é organizado e apresentado com uma estética completamente diferente.
Listagem 3: Código sem identação
public class Pedido
{
public int Numero; public DateTime Data;
public void IniciarPedido()
{
Console.WriteLine("Informe o número do pedido:");
int num = Convert.ToInt32(Console.ReadLine());
if (num <= 0) { Console.WriteLine("Número inválido"); return; }
Numero = num; Data = DateTime.Today;
}
}
De fato todo código poderia ser escrito em uma única linha, poiso Visual Studio desconsidera os espaços em branco no código e o separador de expressões é o ponto-e-vírgula (;). Porém, essa não é nem de longe uma prática aconselhável, pois o próprio autor do código, muito provavelmente, terá dificuldade para fazer manutenção futuramente.
O próprio IDE Visual Studio ajuda a manter o código organizado, identando automaticamente expressões assim que são concluídas. Aproveitando esses recursos, podemos alterar o código anterior, deixando-o com a seguinte aparência:
Listagem 4: Código identado
public class Pedido
{
public int Numero;
public DateTime Data;
public void IniciarPedido()
{
Console.WriteLine("Informe o número do pedido:");
int num = Convert.ToInt32(Console.ReadLine());
if (num <= 0)
{
Console.WriteLine("Número inválido");
return;
}
Numero = num;
Data = DateTime.Today;
}
}
Agora está muito mais fácil compreender o código e a ordem em que é executado.
Recursos do Visual Studio para Refatoração
O poderoso IDE Visual Studio fornece nativamente alguns recursos para auxiliar o processo de refatoração do código, tornando algumas tarefas bastante simples. Selecionando um trecho de código, em uma classe, por exemplo, e clicando com a direita sobre ele, haverá a opção “Refactor” no menu apresentado, conforme a figura a seguir.
Figura 1: Menu Refactor
Obviamente cada item do submenu é aplicável a uma situação diferente, a seguir cada uma dessas opções é explicada individualmente.
Rename (F2)
A opção Rename, como o nome sugere, deve ser usada pare renomear um identificador, como um atributo, método ou classe. Tomando o exemplo da Listagem 2, posicionando o cursor sobre ou selecionando o nome do método ValidarAtributos e usando a opção Rename, podemos alterar o nome do método na sua declaração, bem como as referências a ele no restante do código.
A figura a seguir mostra a janela que se abre quando utilizamos essa opção, usando o exemplo citado.
Figura 2: Renomeando método
Mantendo a opção “Preview reference changes” marcada, em seguida é apresentada uma janela com os pontos do código que fazem referência a esse elemento (no caso, o método).
Figura 3: Pré-visualização de mudanças no código
Clicando sobre cada referência na parte superior é possível ver o trecho de código abaixo. É possível ainda escolher onde o nome do método deve ser alterado, marcando e desmarcando as referências, como visto acima.
Extract Method...(CTRL+R, M)
Essa opção permite extrair um método de um bloco de código selecionado, ou seja, selecionando um trecho de código, é possível torna-lo um método. Podemos tomar como exemplo a Listagem 1. Selecionando o trecho do método Salvar onde é feita a validação dos atributos da classe e usando a opção Extract Method, a seguinte janela é apresentada.
Figura 4: Extraindo método
Deve-se então informar o nome do método e clicar em OK. O bloco de código selecionado será passado para o corpo do novo método e, onde antes estava esse código, será feita a chamada ao método criado.
Encapsulate Field...(CTRL+R, E)
O encapsulamento é uma das bases da Programação Orientada a Objetos e no Visual Studio não poderia ser diferente. Sabendo que o .NET Framework é composto por centenas de classes e que C# é uma linguagem orientada a objetos, o IDE facilita o encapsulamento de campos de uma classe, criando automaticamente os métodos get e set e as propriedades (públicas) que encapsulam cada atributo (privados).
Para testar essa funcionalidade, utilizemos o seguinte trecho de código, onde temos três atributos que deveriam ser privados, mas se encontram públicos devido a falta de encapsulamento (exemplo da classe Produto, vista anteriormente).
Listagem 5: Campos não encapsulados
public string _descricao;
public decimal _preco;
public decimal _estoque;
Os campos são os mesmo usados anteriormente na classe Produto, porém, os nomes dos atributos foram modificados, usando um caractere underscore (_) no início do nome, cuja primeira letra foi alterada para minúscula. Esse é um padrão muito utilizado para esse fim (encapsulamento). Enquanto isso, as propriedades que encapsulam esses campos devem ter o nome escrito normalmente (como estava nas Listagem 1 e 2).
Posicionando o cursor sobre um dos campos e usando a opção Encapsulate Field, a seguinte janela é apresentada.
Figura 5: Encapsulando campo
Deixando marcada a opção “Preview reference changes”, ocorrerá o mesmo que na opção Rename, mostrando os trechos do código que fazem referência ao atributo e que agora usarão a propriedade que o encapsula.
As opções “Search in comments” e “Search in strings”, que também aparecem no menu Rename servem para substituir o valor alterado também nos comentários e strings ao longo do código.
Clicando em OK e repetindo o procedimento para os demais atributos, temos agora os campos encapsulados, conforme o código a seguir.
Listagem 6: Campos encapsulados
private string _descricao;
public string Descricao
{
get { return _descricao; }
set { _descricao = value; }
}
private decimal _preco;
public decimal Preco
{
get { return _preco; }
set { _preco = value; }
}
private decimal _estoque;
public decimal Estoque
{
get { return _estoque; }
set { _estoque = value; }
}
Agora o encapsulamento está feito, os atributos que estavam públicos se tornaram privados e foram criadas propriedades que os encapsulam.
Extract Interface... (CTRL+R, I)
Interfaces também são largamente utilizadas para padronizar classes. Então, caso se tenha uma classe e em algum momento seja observado que todos ou alguns de seus campos possam ser usados também por outras classes, pode-se extrair uma interface dela usando o menu Extract Interface.
Por exemplo, usando a classe Produto das primeiras listagens, podemos posicionar o cursos do mouse sobre o nome da classe e usar esse menu. Será apresentada uma janela com os dados da interface a ser extraída, conforme a figura a seguir.
Figura 6: Opções da interface extraída
Selecionando, por exemplo, todos os membros da classe Produto e clicando em OK, será gerado um novo arquivo (com o nome definido no campo “New file name”) contendo a interface cujo nome foi definido no primeiro campo da tela apresentada.
O código da interface é o seguinte:
Listagem 7: Interface extraída
interface IProduto
{
void Atualizar();
string Descricao { get; set; }
decimal Estoque { get; set; }
decimal Preco { get; set; }
void Salvar();
}
A classe Produto passa automaticamente a implementar essa interface.
Remove Parameters...(CTRL+R, V)
Quando se tem um método com vários parâmetros e em algum momento é preciso remover um deles, pode-se utilizar o menu Remove Parameters para alterar a assinatura do método e as referências a ele.
Quando se remove um ou mais parâmetros de um método, todas as chamadas a ele são alteradas, adequando-se à nova assinatura.
Por exemplo, consideremos o seguinte método:
Listagem 8: Método com vários parâmetros
public void IniciarPedido(int numero, DateTime data)
{
}
Em determinado momento surgiu a necessidade de remover o segundo parâmetro, por exemplo. Então, usando o menu Remove Parameters, a seguinte tela é apresentada.
Figura 7: Removendo parâmetros de um método
É possível remover um parâmetro clicando no botão Remove e, caso deseje desfazer a operação, basta usar o botão Restore e o parâmetro volta ao método. O botão Restore só funcionará antes de ser pressionado o botão OK, ou seja, caso a operação de remoção seja finalizada, não é possível voltar o parâmetro usando essa opção.
A opção “Preview reference changes” tem a mesma função que nos menus anteriores, permitindo visualizar previamente as chamadas ao método modificado e como elas ficarão após a remoção dos argumentos.
Reorder Parameters...(CTRL+R, O)
Este último subitem do menu Refactor permite alterar a ordem dos parâmetros de um método, modificando também as referências feitas e ele (chamadas).
Podemos usar o mesmo método da Listagem 9 como exemplo. Ao acionar o comando Reorder Parameters, a seguinte janela é apresentada.
Figura 8: Reordenando os argumentos de um método
Para alterar a posição de um argumento, basta selecioná-lo e usar as setas do lado direito da tela. Em seguida, deve-se clicar no botão OK para finalizar o processo.
Conclusão
Como vimos, a refatoração é um processo de fundamental importância no desenvolvimento de software. Aperfeiçoar a estrutura interna do código traz benefícios tanto a curto quanto a longo prazo, pois facilita a compreensão e manutenção do sistema.
Quando não se tem tempo bastante para implementar ou alterar uma funcionalidade, ou não se tem o correto e total entendimento da situação e requisitos que levaram a tal necessidade, é comum haver certa deterioração do código, ou seja, este vai tende a ficar cada vez mais “poluído” e desorganizado. Se desde o momento da concepção do sistema forem adotadas práticas como a refatoração de código, o impacto dessa deterioração será menor ao longo do tempo.
Vimos ainda exemplos práticos de refatoração na linguagem C# e os recursos que o IDE Visual Studio fornece para auxiliar esse processo, o que, entre outras coisas, o torna essa poderosa ferramenta de desenvolvimento que é.