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
- FOWLER, Martin Fowler, Refatoração: Aperfeiçoando o Design de Códigos Existentes.
- https://refactoring.guru/