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.

Menu Refactor

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.

Renomeando método

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).

Pré-visualização de mudanças no código

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.

Extraindo método

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.

Encapsulando campo

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.

Removendo parâmetros de um método

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.

Reordenando os argumentos de um método

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 é.