Menu

Refatoração ao pé da letra

Refatoração é uma das técnicas que nos permite ser ágil e utilizar uma abordagem evolucionária para o design dos nossos projetos. É uma prática central no XP e um dos pilares do TDD. A refatoração contínua afasta  o mal da arquitetura complexa e mantém nossas codebases saudáveis. Refatoração é uma das poucas práticas que a maioria dos devs concorda que deve ser feita em algum ponto do projeto, de preferência de maneira contínua.

O problema é que, como todos os outros termos do jargão da computação, a definição de refatoração ficou confusa com o passar do tempo. Refatoração é muitas vezes usada como sinônimo para sua prima mais sombria, a Reescrita.  Dizemos que estamos "refatorando", quando estamos fazendo qualquer melhoria no projeto, seja ela em um método ou em metade do sistema.

Sessões de refatoração de dias ou semanas estão se tornando comum em projetos ágeis. Muitos times têm histórias para elas ou até mesmo uma parte fica dedicada no quadro.

Embora todas estas sessões sejam feitas com a melhor das intenções, sair loucamente rescrevendo todas as partes ruins do código é uma manobra arriscada. Quantas vezes já nos afundamos em horas e horas de refatoração, para no final ter um resultado mais confuso que o código anterior e ter que jogar tudo fora?

Toda essas "refatorações" passam longe da definição original do termo e raramente são questionadas, afinal todos sabemos que refatorar é uma coisa boa.

Vamos dar um passo para trás e relembrar como as refatorações surgiram e o que perdemos ao longo do caminho.

As refatorações originais

Embora a prática já existisse antes, Refatorações foram apresentadas para a maioria dos desenvolvedores pelo livro Refatoração, Aperfeiçoando o Projeto de Código Existente escrito por Martin Fowler.

O livro apresenta práticas de codificação de alguns “SmallTalkers”  lendários, como Kent Beck e Ward Cunningham e apresenta a seguinte definição:

"Refatoração é uma técnica disciplinada para reestruturar um corpo de código já existente, alterando sua estrutura interna sem alterar seu comportamento".

No seu cerne estão uma série de pequenas transformações, que mantêm o comportamento já existente. Cada transformação (chamada de "refatoração") faz pouco, mas, uma sequência de transformações, pode gerar uma re-estruturação significativa. Como cada refatoração é pequena, a chance de dar errado é menor.

O funcionamento do sistema é mantido após cada pequena refatoração, reduzindo as chances de que algum sistema seja seriamente afetado durante a reestruturação.

As refatorações originais são apresentadas em formato de padrões de projeto.

Elas têm um nome, como “Extrair Método” e “Trocar Temp por Query”, uma explicação de quando usá-las e uma lista de passos a serem seguidos para a aplicação. Você pode navegar o catálogo de refatorações em refactoring.com (em inglês).

A ideia é aplicar as refatorações de maneira consciente, uma depois da outra, até que o design da sua classe ou do método esteja bom o suficiente para você avançar.  Palavras como “disciplina” e “pequenas” estão no centro da definição do termo.

Quando você sai loucamente re-escrevendo metade do seu projeto, não está refatorando, mesmo que esteja tentando chegar a um melhor design. Outro ponto importante é que o sistema deve continuar funcionando o tempo todo. Os testes devem ficar verdes!  O fluxo do TDD era pra ser Vermelho -> Verde -> Refatorar e não Vermelho -> Verde -> Vermelho -> Vermelho -> O que eu to fazendo!? -> Rollback.

Formas de Refatorar no Verde

Vamos ver como refatorar código de maneira disciplinada mantendo os testes sempre verdes.

Ao invés de usarmos uma já manjada classe Usuário, vamos trabalhar sobre um exemplo real, a classe Request do projeto Pacto. Não se preocupe em entender exatamente o que a classe faz, basta prestar atenção na estrutura do código e como ela muda ao longo das refatorações.

Esta classe tem vários probleminhas, mas o principal é o método instantiate.  O método força a codebase do Pacto a lidar com uma classe externa, Faraday::Response.  Esta classe externa é muito simples e podemos implementar toda a interface necessária na própria classe “Pacto::Response”reduzindo a dependência em uma biblioteca externa.

Passo 0 - Verificar os testes atuais

Antes de começarmos, um passo muito importante é verificar se o código em questão está coberto por testes. Um jeito rápido de fazer isto é, simplesmente, fazer a refatoração desejada; no nosso caso remover o método instantiate  e ver se alguma coisa quebra.

Nenhum teste ficar vermelho não é um bom sinal! Isto quer dizer que você não tem como saber se as refatorações realmente não impactaram a codebase. Não ter nenhuma rede de segurança para lhe proteger, só o "eu tenho certeza que isso não vai quebrar nada" não conta.  Uma frase famosa diz "refatoração sem testes é, simplesmente, fazer bagunça".

Se este for o caso, implemente bons testes para o comportamento atual antes de executar as refatorações. Para nossa sorte, o projeto Pacto tem uma boa suíte de testes e uma boa mensagem de erro.

Passo 1 - Mover Método

O teste anterior nos diz que precisamos de um método bodyno objeto retornado por instantiate. Atualmente, a definição de body é usada na inicialização do Faraday::Response. Vamos aplicar uma versão modificada da refatoração Mover Método para passar o método do default_env para a classe Response.

Executamos os testes para ter certeza que nada foi afetado. Como só alteramos métodos privados e mantivemos a estrutura original, todos os testes continuam verdes.

Passo 2 - Alterar Faraday::Response para self

O passo anterior é a única coisa que necessitamos para nos livrar do Faraday::Response. Então, alteramos o método instantiate para que ele retorne “self”.

Este deve ser, provavelmente, o método mais inútil já escrito, mas, lembre, queremos manter os testes sempre verdes. Não sabemos quantas classes dependem deste método instantiate, então nós deixamos ele viver mais um pouco para garantir que não quebraremos nada e podemos continuar refatorando somente a classe Response, sem se preocupar com outras partes da codebase.

Passo 3 - Remover o método default_env

Agora temos um método privado que não é mais utilizado. É só deletar certo?

Não precisa nem rodar os testes! Bem,  em Ruby, métodos privados ainda podem ser chamados com send, uma prática horrenda, mas que pode acontecer na sua codebase. Por mais que uma mudança pareça inocente, rode seus testes. Não custa nada e cria bons hábitos.

Para nossa sorte, a codebase do Pacto não é cheia de maus exemplos de meta programação, então nossos testes continuam verdes após a remoção do método default_env.

Passo 4 - Remover o campo @definition

Outro pedaço de código não utilizado, mesmas regras do passo anterior

Passo 5 - Remover condicionais excessivas

No método Response#body temos condicionais que podem ser facilmente trocadas por um valor default para o atributo schema. Usando nosso truque "deleta e vê se alguma coisa quebra", descobrimos que essas condicionais não estão cobertas por nenhum teste e primeiramente adicionamos os testes necessários:

Então, trocamos as condicionais por um valor default para schema:

Passo 6 - Remover o método instantiate

Finalmente estamos satisfeitos com a estrutura interna da classe Response, podemos passar para as mudanças mais perigosas que afetam outros componentes no sistema.  Qualquer um que esteja usando o resultado de instantiate pode agora, simplesmente, usar a instância da classe Response.

Para este passo deletamos o método e consertamos todos as falhas. Neste caso, apenas um outro componente depende da classe Response e rapidamente os testes estão verdes novamente. Para casos onde vários componentes dependem do método a ser alterado, é mais prudente alterar cada dependência, uma por uma, e depois sim realizar a alteração.

Ainda tem algumas coisas que poderiam ser melhoradas nesta classe, como por exemplo transformá-la em uma Struct, mas o código já está bom o suficiente. Saber onde parar refatorações é tão importante quanto saber executá-las. Devemos sempre balancear o estado atual do código com o custo de continuar trabalhando sobre a mesma funcionalidade.

Conclusão

Você pode estar pensando que todos esses passos são puro drama de desenvolvedor, que tudo isso poderia ter sido feito de uma vez só sem nenhum problema. Bem, se você analisar somente nosso exemplo simples, isso é verdade, mas enfrentamos situações muito piores no dia a dia dos projetos.

Usando uma aproximação metódica para a refatoração tivemos uma sessão divertida de codificação e bem curta também. Todos os commits foram curtos e precisos. O mais legal é que foi possível “pushar” todos eles para produção, já que os testes estiveram sempre verdes.

A próxima vez que encontrar uma classe horrível pedindo para ser alterada, segure a mão e não saia deletando todo o código, tente fazer as coisas de uma forma disciplinada. Discuta com seus colegas toda vez que perceber alguém criando um branch só para "refatoração", afinal, se o sistema for mantido o tempo todo funcionando, para quê um branch?

Um bom lugar para aprender mais sobre refatorações é o livro do Martin Fowler, que possui uma versão mais recente em Ruby, apesar do código Java da versão original ser bem fácil de entender. Outro recurso legal é a c2 wiki, onde você pode ver grandes nomes discutindo a prática, na época em que ela foi introduzida.