Hoje, vamos ver em Ruby como combater o Code Smell de duplicação de códigos usando um design pattern conhecido como Template Method.
Identificando o Code Smell
Um cenário muito comum ao desenvolvermos software é nos depararmos com a seguinte situação: uma quantidade significativa de código que resolve um problema X, mas que precisa sofrer pequenas alterações para resolver também o problema Y, Z e “n” outros problemas muito parecidos entre si.
Vamos supor que você esteja trabalhando em um projeto que precisa gerar boletos bancários para diversos bancos.
Ao olhar o código do sistema em produção, você percebe que algum desenvolvedor no passado (que já saiu da empresa, afinal ninguém quer assumir a culpa) resolveu brilhantemente isolar o código para gerar boletos bancários para o Itaú em uma classe chamada BoletoItau – classe essa que parece com a seguinte:
class BoletoItau
def initialize(valor, vencimento)
@valor = valor
@vencimento = vencimento
end
def gerar_boleto
puts("Header do Boleto para o Banco Itau")
puts("Valor: #{@valor}")
puts("Vencimento: #{@vencimento}")
puts("Footer do Boleto: Esse boleto deve ser pago no Banco Itau mais próximo")
end
end
Parabéns para o desenvolvedor que resolveu isolar o código acima em uma classe. Mas, quando esse mesmo desenvolvedor teve que gerar um boleto para o banco Santander (que parece muito com o boleto do Itaú) ele fez algo muito, muito ruim: ele duplicou a classe BoletoItau, criando a BoletoSantander:
class BoletoSantander
def initialize(valor, vencimento)
@valor = valor
@vencimento = vencimento
end
def gerar_boleto
puts("Header do Boleto para o Banco Santander")
puts("Valor: #{@valor}")
puts("Vencimento: #{@vencimento}")
puts("Footer do Boleto: Esse boleto deve ser pago no Banco Santander mais próximo")
end
end
Agora você, que esta com a tarefa para criar boletos para o Banco do Brasil, se vê diante das opções: perpetuar esse legado de código duplicado, ou refatorar o código acima para algum design pattern existente.
Não se deixe seduzir pelo lado negro da força, gaste um tempinho e resolva permanentemente esse problema com o Template Method.
Identificando o que permanece igual
A chave central do Code Smell de código duplicado é identificar o que se repete entre os códigos semelhantes e isolá-los em uma classe que servirá de base para todas as subclasses. Uma definição formal do pattern Template seria:
A idéia geral do Pattern Template Method é criar uma classe base abstrata com um método que sirva de esqueleto. Esse método possui o processamento que pode variar, realizando chamadas a métodos abstratos, que serão fornecidos por subclasses concretas.
Fonte: Design Patterns in Ruby
No exemplo anterior, vemos que as principais diferenças entre as classes BoletoItau e BoletoSantander se encontra no método gerar_boleto.
Para transformar o método gerar_boleto em um Template Method, eu preciso antes transformar o código que ele executa em chamadas as métodos que serão sobrescritos por cada subclasse, que no meu exemplo, será cada Banco.
Uma proposta da classe Boleto seria:
class Boleto
def initialize(valor, vencimento)
@valor = valor
@vencimento = vencimento
end
def gerar_boleto
gerar_header
gerar_valor
gerar_vencimento
gerar_footer
end
def gerar_header
end
def gerar_valor
puts("Valor: #{@valor}")
end
def gerar_vencimento
puts("Vencimento: #{@vencimento}")
end
def gerar_footer
end
end
Com isso, a cada novo Banco, eu posso simplesmente estender a classe base Boleto e sobrescrever os métodos desejados. Os bancos poderiam ficar da seguinte forma:
# Banco Itaú
class BoletoItau < Boleto
def gerar_header
puts("Header do Boleto para o Banco Itau")
end
def gerar_footer
puts("Footer do Boleto : Esse boleto deve ser pago no Banco Itau mais proximo")
end
end
# Banco Santander
class BoletoSantander < Boleto
def gerar_header
puts("Header do Boleto para o Banco Santander")
end
def gerar_footer
puts("Footer do Boleto : Esse boleto deve ser pago no Banco Santander mais proximo")
end
end
# E finalmente o Banco do Brasil
class BoletoBancoBrasil < Boleto
def gerar_header
puts("Header do Boleto para o Banco do Brasil")
end
def gerar_footer
puts("Footer do Boleto : Esse boleto deve ser pago no Banco do Brasil mais proximo")
end
end
Don’t Repeat Yourself (DRY)
A metodologia Don’t Repeat Yourself (DRY) é uma filosofia de programação que busca evitar a duplicação de código.
A ideia principal do DRY é que cada pedaço de conhecimento na programação deve ser representado por uma única, isto é, um única “entidade autoritativa”.
A implementação do princípio DRY pode ajudar a reduzir a complexidade e aumentar a manutenibilidade do código, pois elimina a necessidade de manter várias cópias do mesmo código, que podem acabar divergindo umas das outras.
Portanto, se qualquer parte do código precisar ser modificada, apenas precisará ser feito em uma única lugar, em vez de em vários lugares.
Por exemplo, algumas técnicas comuns para implementar o princípio DRY incluem:
- Utilizar funções e procedimentos para encapsular lógicas comuns
- Utilizar herança e polimorfismo para compartilhar comportamento entre classes
- Utilizar bibliotecas e frameworks que forneçam funcionalidades comuns
- Utilizar código genérico para evitar a criação de código específico para cada caso
- Utilizar arquitetura modular para dividir seu código em módulos independentes e reutilizáveis
DRY é importante porque o código duplicado é uma fonte comum de problemas de manutenção e pode levar a bugs difíceis de encontrar.
Ao seguir o princípio DRY, você pode aumentar a confiabilidade do seu código para então torná-lo mais fácil de manter e evoluir no futuro.
Conclusão
O exemplo foi bastante reduzido para não tornar este post em uma dissertação acadêmica, mas adianto que existe um mar de técnicas que podem ser aplicadas para evitar o Code Smell de duplicação de código.
Caso queira uma recomendação de livro, indico o Design Patterns – Elements of Reusable Object-Oriented Software, que é um livro mais abrangente sobre o tema.
Caso queria algo mais prático, tem o excelente Refactoring to Patterns que é um livro mais prático sobre Patterns. Em Ruby, existe o Design Patterns in Ruby, que usei como base para este post – e possivelmente usarei como base para os próximos posts.
Por fim, deixo também uma sugestão do clássico Clean Code, do Robert C Martin.