Refatoração é onde melhoramos o nosso código. Neste artigo veremos duas técnicas para essa etapa com exemplos em Ruby.
O que é refatoração?
Refatoração se trata do processo de alterar um sistema de software de uma forma que ela não tenha seu comportamento externo alterado, mas tenha a sua estrutura interna melhorada.
Com a refatoração você pode ter um design de projeto ruim e refazê-lo de forma bem projetada.
Isso pode ser feito movendo um campo de uma classe para outra, extraindo código de um método para transformá-lo em outro método, alterando a ordem em que seu código é escrito numa espécie de hierarquia, dentre outras diversas técnicas. Neste artigo vamos ver algumas dicas de como realizar isso:
Renomear método
def valor (valor_base,porcentagem)
if porcentagem.nil? || porcentagem <10
valor_base
else
valor_base * (porcentagem/100)
end
end
Para deixar o código mais expressivo, vamos renomear este método de modo que fique mais fácil de identificar qual é o seu propósito:
def valor_final(valor_base,porcentagem)
if porcentagem.nil?||
porcentagem <10
valor_base
else
valor_base *(porcentagem/100)
end
end
Extrair método
Esta técnica pode ser utilizada quando precisamos quebrar um método que possui mais de uma responsabilidade.
O método abaixo Inativar_clientes é um exemplo disso:
def inativar_clientes
inativar clientes =
Clientes.all.select do |cliente|
cliente.ultima_compra > 1.month.ago && cliente.ativo?
end
clientes_para_inativar.each(&:deactivate)
AvisarClientePorWpp.inativados(inativar_clientes)
end
end
Vamos criar outro método para separar a responsabilidade de buscar quais clientes serão inativados:
def clientes_para_inativar
Clientes.all.select do |cliente|
cliente.ultima_compra >1.month.ago && cliente.ativo?
end
Após o método da busca ser separado, atualizamos o método que vai realmente inativar os clientes:
def inativar_clientes
clientes =clientes_para_inativar
clientes.each(&:deactivate)
AvisarClientePorWpp.inativados(clientes)
end
Mover método
Esta técnica pode ser utilizada quando se tem um método que usa mais informações de uma classe externa do que da sua própria classe.
Essa alteração reduz a complexidade do nosso código, pois ele vai acessar as informações da nova classe de forma direta. Vamos continuar com o exemplo anterior:
def inativar_clientes
clientes= clientes_para_inativar
clientes.each(&:deactivate)
AvisarClientePorWpp.inativados(clientes)
end
def clientes_para_inativar
Clientes.all.select do |cliente|
cliente.ultima_compra > 1.month.ago && cliente.ativo?
end
end
Mesmo já tendo sido melhorado, ainda temos muita relação com clientes no código, quando o nome de outro objeto é chamado várias vezes, talvez a responsabilidade esteja no lugar errado.
Para isso, devemos criar a classe Clientes
e mover o método clientes_para_inativar para dentro dela, transferindo sua responsabilidade.
class Clientes
def self.clientes_para_inativar
Clientes.all.select do |cliente|
cliente.ultima_compra > 1.month.ago && cliente.ativo?
end
end
end
Depois desta separação, a classe InativarClientesWorker ficaria assim:
class InativarClientesWorker
def inativar_clientes
clientes = clientes_para_inativar
clientes.each(&:deactivate)
AvisarClientePorWpp.inativados(clientes)
end
end
Mover campo é necessário quando temos um campo (atributo) que é mais usado em uma classe externa do que na sua própria classe.
Seu maior benefício é que, ao ser criado na nova classe, temos a garantia de que ele ficará protegido de modificações externas ou criamos um atributo de leitura e – se preciso – de escrita na classe de destino, e alteramos todos os locais em que ele é referenciado.
class ContaBancaria
attr_accessor : taxa_juros
def juros_por_dias(numero_de_dias, dias)
@taxa_juros * numero_de_dias * dias / 365;
end
end
Na classe ContaBancaria
só um método está usando a variável taxa_juros
, mas a classe ContaCorrente
utiliza bem mais essa variável. Dessa forma, é necessário mover essa variável para essa outra classe:
class ContaCorrente
attr_accessor : taxa_juros
end
class ContaBancaria
def juros_por_dias(numero_de_dias, dias)
@contacorrente.taxa_juros * numero_de_dias * dias / 365;
end
end
Extrair classe
É a união do Extrair método. É utilizada quando uma classe tem mais de uma responsabilidade. Então, para a construção dessa nova classe, utilizamos o Mover Método e Mover Campo.
Esse tipo de refatoração é necessário em classe como o exemplo a seguir:
class BaixarLogVendaWorker
attr_reader :host, :porta, :usuario, :senha
def self.requisita_servidor(arquivo)
Net::FTP.open(@host) do |ftp|
ftp.login(@usuario,@senha)
salvar_no_banco(ftp.gettextfile(arquivo)
end
end
def salvar_no_banco(arquivo)
LogVendas.ler_arquivo(arquivo)
end
end
Perceba que temos muitas responsabilidades numa mesma classe. Vamos separar a parte de baixar o log de vendas da parte que salva esse log no banco de dados. Primeiro vamos criar uma nova classe para essa responsabilidade e passar os respectivos métodos para ela.
class BaixarArquivoServidorFtp
attr_reader :host, :porta, :usuario, :senha
def self.requisita_servidor(arquivo)
Net::FTP.open (@host) do |ftp|
ftp.login(@usuario,@senha)
ftp.gettextfile(arquivo)
end
end
end
Agora substituímos o código antigo pela nova classe:
class BaixarLogVendaWorker
def self requisitar_servidor(arquivo)
arquivo_de_texto =BaixarArquivoServidorFtp.requisitar_servidor(arquivo)
salvar_no_banco(arquivo_de_texto)
end
def slavar_no_banco(arquivo)
LogVendas.ler_arquivo(arquivo)
end
end
Com as responsabilidades separadas e com os métodos simplificados, podemos ver nomes mais claros para as nossas classes.
A classe BaixarArquivoServidorFtp
é bem genérica para ser usada em outros locais – pode permanecer com o mesmo nome. Já a BaixarLogVendaWorker
pode ser renomeada para SalvarRegistroVendasWorker
.
No fim, as duas classes ficariam deste modo:
class SalvarRegistroVendasWorker
def salvar_no_banco ()
arquivo_de_texto = BaixarArquivoServidorFtp.requisitar_servidor(arquivo)
LogVendas.ler_arquivo(arquivo)
end
end
class BaixarArquivoServidorFtp
attr_reader :host :porta :usuario :senha
def self.requisitar_servidor(arquivo)
Net::FTP.open(@host) do |ftp|
ftp.login (usuarios: @usuario, senhas: @senha)
end
end
end
Estas foram as referências que eu utilizei para escrever este artigo:
- Refatorando com padrões de projeto
- Refatoração – Marcos Brizeno
- Refatoração: Aperfeiçoando o Projeto de Código Existente
- Refactoring homepage
- Refatorações em Ruby: Move Method e Move Field
- Catalog of Refactorings
- Desenvolvimento Ágil
Os gists podem ser encontrados neste link.
Até a próxima!