Refatoração não é mais novidade, e o livro "Refactoring: improving the design of existing code", de Martin Fowler (1999), já possui mais de 20 anos de existência.
Mesmo assim, o assunto é relevante e pertinente tanto para novos profissionais como para quem nunca parou para entender o que é refatoração.
"Refatoração é o processo de alterar o código fonte de uma maneira que não altere seu comportamento externo e ainda melhore a sua estrutura interna. É uma técnica disciplinada de limpar e organizar o código, e por consequência minimizar a chance de introduzir novos bugs." – Martin Fowler (1999)
Alguns pontos importantes ao pensar a refatoração são:
Melhorar o design existente: Melhorar o design existente é uma definição muito importante, pois é bem comum pensar que qualquer coisa que não seja adição de funcionalidade é refatoração. Correções não devem fazer parte do processo de refatoração; lembre-se de que o foco é melhorar o design! Por isso é melhor reduzir a quantidade de mudanças, o que nos leva à segunda parte da definição.
Aplicar mudanças em pequenos passos: O conceito de aplicar mudanças em pequenos passos não é muito objetivo, afinal, o que são "pequenos passos"? O objetivo principal é reduzir a quantidade de mudanças, pois menos mudanças é igual a menos problemas! Se você está alterando os parâmetros de um método e percebe que algo na lógica poderia ser melhor, tome nota e volte para fazer a mudança depois.
Evitar deixar o sistema quebrado: Essa terceira parte da definição é sobre evitar deixar o sistema quebrado, garantindo que as melhorias não acabem afetando funcionalidades existentes. Para isso, é importante ter uma boa suíte de testes, tanto com relação à cobertura quanto à facilidade e velocidade de execução. Além de executar frequentemente os testes da classe em mudança, também é importante garantir que a integração com o resto do sistema continue funcionando.
Cria-se um novo método com um novo nome que chama o método com o nome anterior.
Realiza os casos de teste utilizando o método de novo nome no lugar do método de nome antigo.
Faz a substituição efetiva do nome do método.
//método para calcular o preço final
public long calcula(long precoBase, long bonusPorcentagem) {
if (bonusPorcentagem == null || bonusPorcentagem 10) {
return precoBase; }
else {
return precoBase * (bonusPorcentagem/100); }
}
public long precoFinal(long precoBase, long bonusPocentagem) { return calcula(precoBase, bonusPocentagem);
}
//método para calcular o preço final
public long precoFinal(long precoBase, long bonusPorcentagem) {
if (bonusPorcentagem == null || bonusPorcentagem 10) {
return precoBase; }
else {
return precoBase * (bonusPorcentagem/100); }
}
Extrair método é a técnica utilizada para dividir um método que possui mais de uma responsabilidade.
Identificar a responsabilidade que será extraída do método.
Criar um novo método para a responsabilidade que será extraída a partir da duplicação do código existente do método original.
Manter, de acordo com as necessidades os parâmetros e variáveis locais. Ex.: Podemos apenas copiar as variáveis locais, passá-las como argumento ou extraí-las para seu próprio método.
Excluir o que não faz parte da responsabilidade do novo método.
Realizar casos de teste específicos para o novo método. Geralmente, quando um método possui mais de uma responsabilidade, ele também vai ter vários testes unitários. Ao dividi-lo, vale a pena atualizar os testes para garantir que o novo método se comporte da mesma forma que o anterior.
Remover a responsabilidade do método original, substituindo pelo novo método.
public class DesativarUsuariosWorker {
...
public void desativarUsuarios() {
RepositorioUsuarios repositorio = new RepositorioUsuarios();
List Usuarios usuarios = repositorio.all().stream().filter(usuario-usuario.semLoginRecente() && usuario.estaAtivo()).collect(Collectors.toList());
usuarios.forEach(usuario - repositorio.desativar(usuario));
NotificadorViaEmail.usuariosDesativados(usuarios);
}
...
}
public class DesativarUsuariosWorker {
...
public void desativarUsuarios() {
RepositorioUsuarios repositorio = new RepositorioUsuarios();
List Usuarios usuarios = usuariosParaDesativar();
usuarios.forEach(usuario -repositorio.desativar(usuario));
NotificadorViaEmail.usuariosDesativados(usuarios);
}
...
}
public class DesativarUsuariosWorker {
...
private List Usuarios usuariosParaDesativar() {
RepositorioUsuarios repositorio = new RepositorioUsuarios();
return repositorio.all().stream().filter(usuario-usuario.semLoginRecente() && usuario.estaAtivo()).collect(Collectors.toList());
}
...
}
Mover Método é a técnica utilizada para mover parte de um método que utiliza mais informações de outra classe do que da sua própria classe. Mover método reduz a complexidade do código, pois ele vai ter acesso a todas as informações locais da nova classe em vez de ficar perguntando antes de tomar ações.
Duplicar o código do método original, sem alterar nenhuma funcionalidade, copiando a lógica da classe original para a nova classe.
Duplicar os testes unitários do método para garantir que ele continue funcionando como esperado. Após duplicar o método, basta substituir sua chamada e executar todos os testes novamente.
Remover o método da classe original.
public class DesativarUsuariosWorker {
...
public void desativarUsuarios() {
RepositorioUsuarios repositorio = new RepositorioUsuarios();
List Usuarios usuarios = usuariosParaDesativar();
usuarios.forEach(usuario - repositorio.desativar(usuario));
NotificadorViaEmail.usuariosDesativados(usuarios);
}
private List Usuarios usuariosParaDesativar() {
RepositorioUsuarios repositorio = new RepositorioUsuarios();
return repositorio.all().stream().filter(usuario -usuario.semLoginRecente() && usuario.estaAtivo()).collect(Collectors.toList());
}
...
}
public class RepositorioUsuarios {
...
public List Usuarios usuariosParaDesativar() {
return all().stream().filter(usuario -usuario.semLoginRecente() && usuario.estaAtivo()).collect(Collectors.toList());
}
...
}
public class DesativarUsuariosWorker {
public void desativarUsuarios() {
RepositorioUsuarios repositorio = new RepositorioUsuarios();
List Usuarios usuarios = repositorio.usuariosParaDesativar();
usuarios.forEach(usuario -repositorio.desativar(usuario)); NotificadorViaEmail.usuariosDesativados(usuarios);
}
}
Problema: Um atributo é mais usado em outra classe do que em sua própria classe.
Solução: Crie um atributo em uma nova classe e redirecione todos os usuários do atributo antigo para ele.
Decidir em qual classe deixar o atributo pode ser difícil. Aqui está nossa regra prática: coloque um atributo no mesmo lugar que os métodos que o utilizam (ou então onde está a maioria desses métodos). Essa regra ajudará em outros casos em que um campo está simplesmente localizado no lugar errado.
Se o atributo for público, a refatoração será muito mais fácil se você tornar o atributo privado e fornecer métodos de acesso públicos (encapsulamento).
Crie o mesmo atributo com métodos de acesso na classe destinatária.
Substitua todas as referências ao atributo antigo por chamadas apropriadas para métodos na classe de destinatário.
Exclua o atributo na classe original.
As classes sempre começam claras e fáceis de entender. Eles fazem seu trabalho e cuidam de seus próprios negócios, por assim dizer, sem se intrometer no trabalho de outras classes. Mas, à medida que o programa se expande, um método é adicionado e depois um campo... e, eventualmente, algumas classes estão executando mais responsabilidades do que jamais imaginaram.
Extrair Classe pode ser entendido como uma evolução da técnica Extrair Método, pois usamos essa técnica quando uma classe possui mais de uma responsabilidade.
Em vez disso, crie uma nova classe e coloque nela os atributos e métodos responsáveis pela responsabilidade extra. Para construir a nova classe, utilizadas as técnicas Mover Campo e Mover Método.
Por que refatorar: Essa técnica de refatoração ajudará a manter a adesão ao Princípio de Responsabilidade Única. O código de suas classes será mais claro e compreensível.
Antes de começar, decida como exatamente você deseja dividir as responsabilidades da classe.
Crie uma nova classe para conter a funcionalidade que será separada.
Crie um relacionamento entre a classe antiga e a classe nova. Idealmente, esta relação é unidirecional; isso permite reutilizar a segunda classe sem problemas. No entanto, se você acha que um relacionamento de mão dupla é necessário, isso sempre pode ser estabelecido.
Use as técnicas Mover campo e Mover método para cada atributo e método que você decidiu mover para a nova classe. Para métodos, comece com os privados para reduzir o risco de cometer um grande número de erros. Tente realocar um pouco de cada vez e testar os resultados após cada movimento, a fim de evitar um acúmulo de correção de erros no final.
Depois de terminar de mover, dê mais uma olhada nas classes resultantes. A classe antiga com responsabilidades alteradas pode ser renomeada para maior clareza. Verifique novamente para ver se você pode se livrar de relacionamentos de classe bidirecionais, se houver algum.
Pense também na acessibilidade externa para a nova classe. Você pode ocultar totalmente a classe do cliente, tornando-a privada, gerenciando-a por meio dos campos da classe antiga. Como alternativa, você pode torná-la público, permitindo que o cliente altere os valores diretamente. Sua decisão aqui depende de quão seguro é para o comportamento da classe antiga quando mudanças diretas inesperadas são feitas nos valores da nova classe.
A abstração de generalização tem seu próprio grupo de técnicas de refatoração, principalmente associadas à:
movimentação de funcionalidades ao longo da hierarquia de herança de classe;
criação de novas classes e interfaces; e
substituição de herança por delegação ou vice-versa.
Problema: As subclasses cresceram e se desenvolveram independentemente umas das outras, causando campos e métodos idênticos (ou quase idênticos). As subclasses têm métodos com funcionamento semelhante.
Solução: Tornar os métodos idênticos e depois movê-los para uma superclasse relevante.
Por que refatorar: Diminuir código duplicado. Se você precisar fazer alterações em um método, é melhor fazê-lo em um único local do que procurar todas as duplicatas do método nas subclasses.
Essa técnica de refatoração também pode ser usada se, por algum motivo, uma subclasse redefine um método de superclasse, mas executa essencialmente o mesmo trabalho.
Investigue métodos semelhantes nas subclasses. Se não forem idênticos, formate-os para que correspondam.
Se os métodos usarem um conjunto diferente de parâmetros, coloque os parâmetros na forma que deseja ver na superclasse.
Copie o método para a superclasse. Aqui você pode descobrir que o código do método usa campos e métodos que existem apenas em subclasses e, portanto, não estão disponíveis na superclasse. Resolva essas questões utilizando como na técnica Extrair Método.
Remova os métodos das subclasses.
Verifique os locais em que o método é chamado. Em alguns lugares, você pode substituir o uso de uma subclasse pela superclasse.
Problema: Suas subclasses implementam algoritmos que contêm etapas semelhantes na mesma ordem.
Solução: Mova a estrutura do algoritmo e as etapas idênticas para uma superclasse e deixe a implementação das diferentes etapas nas subclasses.
Por que refatorar: As subclasses são desenvolvidas em paralelo, às vezes por pessoas diferentes, o que leva a duplicidade de código, erros e dificuldades na manutenção do código, pois cada alteração deve ser feita em todas as subclasses.
Esta técnica consiste na aplicação do padrão de projeto Template Method.
Divida o algoritmo nas subclasses em suas partes constituintes descritas em métodos separados. A técnica Extrair Método pode ajudar com isso.
Os métodos resultantes que são idênticos para todas as subclasses podem ser movidos para uma superclasse por meio da técnica Mover Método.
Os métodos variantes podem receber nomes consistentes via técnica Renomear Método.
Mova as assinaturas de métodos não semelhantes para uma superclasse como abstratas. Deixe suas implementações nas subclasses.
E, finalmente, puxe o método principal do algoritmo para a superclasse. Agora ele deve funcionar com as etapas do método descritas na superclasse, tanto reais quanto abstratas.
Para saber mais sobre técnicas de refatoração e conhecer um portfólio mais completo de técnicas, recomendo o conteúdo "Refactoring Guru: Técnicas de Refatoração Avançadas".