Desenvolvimento

29 mai, 2019

Refatoração em Ruby

100 visualizações
Publicidade

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?

http://www.desenvolvimentoagil.com.br/xp/praticas/refatoracao

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:

Os gists podem ser encontrados neste link.

Até a próxima!