Refatoração com Ruby

A refatoração é o processo de reestruturação de um sistema de software com o objetivo de aprimorar o design do código sem causar alterações no comportamento externo. Por que refatorar? A decisão de realizar a refatoração de um código existente pode surgir de diferentes contextos e necessidades específicas. No entanto,…

A refatoração é o processo de reestruturação de um sistema de software com o objetivo de aprimorar o design do código sem causar alterações no comportamento externo.

Por que refatorar?

A decisão de realizar a refatoração de um código existente pode surgir de diferentes contextos e necessidades específicas. No entanto, de maneira geral, podemos destacar como os seguintes motivos:

  • Adicionar um novo recurso – ajuda a garantir que o código esteja estruturado de maneira limpa e seja mais fácil de estender.
  • Realizar alguma adaptação – facilita implementações futuras em determinados contextos.
  • Sugestão durante revisão de código – permite identificar oportunidades de refatoração que podem melhorar a qualidade do software.
  • Reduzir a dívida técnica – possibilita aprimorar códigos que não possuem a implementação ideal devido a decisões de design e entregas aceleradas.
  • Sentir a necessidade – ao constatar que um código está ficando desorganizado ou difícil de manter, mesmo que não haja um problema específico no momento.

Vantagens da refatoração

Os benefícios da refatoração podem ser percebidos em curto, médio e longo prazo, desde a resolução rápida de um problema até a facilitação na compreensão de um projeto, especialmente para novos membros ou desenvolvedores que não estão familiarizados com determinados componentes do software. Alguns dos ganhos que podemos obter ao realizar refatorações incluem: 

  • Melhora na legibilidade – aumenta a clareza do código, facilitando a compreensão e colaboração entre desenvolvedores.
  • Consistência/padrão de codificação – possibilita que o código esteja alinhado com os padrões estabelecidos, promovendo uma base uniforme.
  • Diminuição de redundância – eliminando duplicações é possível melhorar a eficiência e evitar inconsistências.
  • Simplificação de estruturas complexas – minimizar a complexidade e o acoplamento entre componentes facilita futuras modificações.
  • Melhora na coesão – atribui responsabilidades bem definidas aos componentes do software.
  • Otimização do desempenho – identifica e ajusta partes do código que impactam negativamente no desempenho do sistema e consequentemente na experiência do usuário.
  • Simplificação de introdução de novas features – prepara o código para incorporar novas funcionalidades de maneira eficiente e sem grandes complicações.
  • Facilitação na escrita de testes – aprimora a testabilidade do código, tornando mais fácil a implementação de testes unitários e de integração.
  • Prevenção de bugs – mitiga potenciais fontes de erros antes que eles ocorram.
  • Facilitação na manutenção e correção de bugs – pode diminuir o tempo e esforço empregado na manutenção de alguma funcionalidade ou detecção e resolução de problemas.
  • Prevenção contra a degradação do projeto – previne a deterioração da qualidade do código ao longo do tempo.
  • Contribuição com a experiência do desenvolvedor – melhora o ambiente de desenvolvimento, tornando-o mais agradável e produtivo para o time.

Desafios da refatoração

Embora a refatoração ofereça uma série de benefícios, sua prática também apresenta desafios que demandam atenção, pois podem dificultar ou até mesmo inviabilizar o processo. Dentre as adversidades que merecem destaque, encontram-se:

  • Ausência de testes que garantam o funcionamento do código-fonte original – a falta de testes compromete a segurança e confiabilidade da refatoração.
  • Tempo e recursos limitados – é necessário encontrar um equilíbrio entre a qualidade do trabalho habitual e os recursos disponíveis, considerando que a refatoração pode demandar tempo adicional para ser realizada de maneira eficaz.
  • Falta de compreensão do código – dificulta a identificação de trechos passíveis de refatoração.
  • Necessidade de mais tempo para refatorar do que para reescrita total do código – é essencial entender e justificar o investimento de tempo que será empregado em uma possível refatoração.
  • Existência de problemas de lógica – a compreensão da lógica correta é fundamental para evitar a introdução de novos bugs durante a refatoração.
  • Medo de mudança – o receio e a resistência à mudança por parte do time podem ser prejudiciais ao ciclo de vida do projeto.
  • Desconhecimento de técnicas de refatoração – pode limitar a eficácia do processo, sendo necessário investimento em capacitação, boas práticas e pesquisa de ferramentas úteis.

Diante deste contexto, exploraremos algumas técnicas de refatoração com exemplos de como aplicá-las em Ruby. Os exemplos abrangem as técnicas mais básicas de refatoração e algumas delas podem até parecerem familiares, ainda que de maneira implícita. Vale ressaltar também que há diversas outras técnicas de refatoração que não serão abordadas neste artigo, mas podem ser encontradas nos materiais referenciados.

  • Técnicas de simplificação de métodos:

Remove Parameter – remover parâmetros não utilizados na estrutura interna do método.

# Antes

def nome_completo(nome:, sobrenome:, nome_do_meio:)
  "#{nome} #{sobrenome}"
end
# Depois

def nome_completo(nome:, sobrenome:)
  "#{nome} #{sobrenome}"
end

Rename Method – modificar o nome do método que não deixa explícito exatamente do que se trata.

# Antes

def documento_sem_pontuacao(documento:)
  documento.gsub(/\D/, "")
end
# Depois

def cpf_sem_pontuacao(cpf:)
  cpf.gsub(/\D/, "")
end

Preserve Whole Object – passar o objeto inteiro como um único parâmetro quando um método estiver recebendo múltiplos parâmetros de um mesmo objeto.

# Antes

def enviar_encomenda(nome: cliente.nome, endereco: cliente.endereco, telefone: cliente.telefone)
  # ...
end
# Depois

def enviar_encomenda(cliente: cliente)
  #
end

Extract Method – mover um bloco de código para um método separado e nomeá-lo de forma que seu propósito seja evidente.

# Antes

class Pessoa
  def initialize(nome:, endereco:)
    @nome = nome
    @endereco = endereco
  end

  def atualizar_nome_e_endereco(novo_nome:, novo_endereco:)
    @nome = novo_nome
    @endereco = novo_endereco
  end
end
# Depois

class Pessoa
  def initialize(nome:, endereco:)
    @nome = nome
    @endereco = endereco
  end

  def atualizar_nome(novo_nome:)
    @nome = novo_nome
  end

  def atualizar_endereco(novo_endereco:)
    @endereco = novo_endereco
  end
end

Inline Method – é o oposto do Extract Method, o objetivo é incorporar o conteúdo de um método em seu chamador, pois a implementação é simples o suficiente e não adiciona um nível significativo de abstração.

# Antes

class Calculadora
  def initialize(numero:)
    @numero = numero
  end

  def dobrar
    dobro
  end

  def dobro
    @numero * 2
  end
end
# Depois

class Calculadora
  def initialize(numero:)
    @numero = numero
  end

  def dobro
    @numero * 2
  end
end

Extract Variable introduzir uma nova variável para armazenar um valor intermediário, melhorando a clareza das expressões.

# Antes

class Produto
  def initialize(quantidade:, preco_unitario:, desconto:)
    @quantidade = quantidade
    @preco_unitario = preco_unitario
    @desconto = desconto
  end

  def aplicar_desconto!(percentual:)
    @desconto = @quantidade * @preco_unitario * (percentual.to_f / 100)
  end
end
# Depois

class Produto
  def initialize(quantidade:, preco_unitario:, desconto:)
    @quantidade = quantidade
    @preco_unitario = preco_unitario
    @desconto = desconto
  end

  def aplicar_desconto!(percentual:)
    percentual = percentual.to_f / 100

    @desconto = @quantidade * @preco_unitario * percentual
  end
end

Substitute Algorithm – substituir um algoritmo por outro mais eficiente ou legível.

# Antes

class Calculadora
  def somar(numeros:)
    total = 0

    numeros.each do |numero|
      total += numero
    end

    total
  end
end
# Depois

class Calculadora
  def somar(numeros:)
    numeros.sum
  end
end
  • Técnicas de simplificação de estruturas condicionais:

Consolidate Conditional Expression – combinar cláusulas condicionais em uma única expressão condicional mais concisa.

# Antes

class Produto
  DESCONTO_PERCENTUAL = "percentual".freeze
  DESCONTO_EM_REAIS = "valor_real".freeze

  def initialize(quantidade:, preco_unitario:, desconto:, tipo_desconto:)
    @quantidade = quantidade
    @preco_unitario = preco_unitario
    @desconto = desconto
    @tipo_desconto = tipo_desconto
  end

  def valor_desconto
    if @tipo_desconto.present?
      if @tipo_desconto == DESCONTO_PERCENTUAL
        desconto = @desconto.to_f / 100
        
        @quantidade * @preco_unitario * desconto
      else
        @desconto
      end
    end
  end
end
# Depois

class Produto
  DESCONTO_PERCENTUAL = "percentual".freeze
  DESCONTO_EM_REAIS = "valor_real".freeze

  def initialize(quantidade:, preco_unitario:, desconto:, tipo_desconto:)
    @quantidade = quantidade
    @preco_unitario = preco_unitario
    @desconto = desconto
    @tipo_desconto = tipo_desconto
  end

  def valor_desconto
    return calcula_desconto_percentual if @tipo_desconto.present? && desconto_percentual?

    @desconto
  end

  private

  def calcula_desconto_percentual
    desconto = @desconto.to_f / 100
    @quantidade * @preco_unitario * desconto
  end

  def desconto_percentual?
    @tipo_desconto == DESCONTO_PERCENTUAL
  end
end

Consolidate Duplicate Conditional Fragments – eliminar código duplicado dentro de diferentes ramos de uma estrutura condicional.

# Antes

class Pedido
  STATUS_PENDENTE = "pendente".freeze
  STATUS_APROVADO = "aprovado".freeze
  STATUS_REJEITADO = "rejeitado".freeze

  def initialize(status:)
    @status = status
  end

  def mensagem_por_status
    case @status
    when STATUS_PENDENTE
      mensagem = "Há pendências no pedido"
      "#{@status.upcase}: #{mensagem}"
    when STATUS_APROVADO
      mensagem = "O pedido foi aprovado"
      "#{@status.upcase}: #{mensagem}"
    when STATUS_REJEITADO
      mensagem = "O pedido foi rejeitado"
      "#{@status.upcase}: #{mensagem}"
    end
  end
end
# Depois

class Pedido
  STATUS_PENDENTE = "pendente".freeze
  STATUS_APROVADO = "aprovado".freeze
  STATUS_REJEITADO = "rejeitado".freeze

  def initialize(status:)
    @status = status
  end

  def mensagem_por_status
    statuses = {
      STATUS_PENDENTE => "Há pendências no pedido",
      STATUS_APROVADO => "O pedido foi aprovado",
      STATUS_REJEITADO => "O pedido foi rejeitado"
    }

    "#{@status.upcase}: #{statuses[@status]}"
  end
end

Hide Method – tornar um método privado quando ele não é chamado por outras classes ou protegido quando é chamado apenas dentro de sua hierarquia de classes.

  • Técnicas de refatorações entre classes:

Extract Class  – adicionar novas classes quando uma classe tem duas ou mais responsabilidades distintas.

# Antes

class Pessoa
  def initialize(nome:, idade:, rua:, cep:, cidade:, estado:)
    @nome = nome
    @idade = idade
    @rua = rua
    @cep = cep
    @cidade = cidade
    @estado = estado
  end

  def atualizar_endereco(dados_endereco)
    @rua = dados_endereco[:rua]
    @cep = dados_endereco[:cep]
    @cidade = dados_endereco[:cidade]
    @estado = dados_endereco[:estado]
  end
end
# Depois

class Pessoa
  def initialize(nome:, idade:, endereco:)
    @nome = nome
    @idade = idade
    @endereco = endereco
  end

  def atualizar_endereco(dados_endereco)
    @endereco.atualizar_endereco(dados_endereco)
  end
end

class Endereco
  def initialize(rua:, cep:, cidade:, estado:)
    @rua = rua
    @cep = cep
    @cidade = cidade
    @estado = estado
  end

  def atualizar_endereco(dados_endereco)
    @rua = dados_endereco[:rua]
    @cep = dados_endereco[:cep]
    @cidade = dados_endereco[:cidade]
    @estado = dados_endereco[:estado]
  end
end

Move Method – mover um método de uma classe para outra quando ele aparentar pertencer mais à classe de destino.

Move Field – mover um atributo de uma classe para outra quando ele aparentar pertencer mais à classe de destino.

Conclusão

A prática da refatoração oferece inúmeros ganhos, desde a melhoria na legibilidade e consistência do código até a sua manutenibilidade. Embora possamos enfrentar desafios ao longo do processo, a superação dessas barreiras resulta em sistemas mais robustos e eficientes. Ao integrar a refatoração como uma abordagem contínua, fortalecemos a qualidade do software e promovemos o sucesso a longo prazo.

Referências

0 Comentário