Flay é uma ferramenta útil para cercear repetições de código em programas Ruby. Analisando o código semanticamente, o Flay é capaz de pegar repetições em casos que geralmente passam despercebidos no melhor dos códigos.
Em algumas casos, a repetição será inevitável e mesmo necessária para deixar o código mais legível. Geralmente, entretanto, repetição significa código que ainda não está DRY o suficiente.
Instalação
Para instalar o Flay, basta rodar o comando gem:
~$ sudo gem install flay
Esse comando instala a gem em si e um executável que pode ser usado na linha de comando.
Um exemplo
Para demonstrar o uso da ferramenta, vamos rodar o Flay contra o código mais recente do Active Record:
O resultado (um trecho apenas), depois de alguns minutos de trabalho com a CPU a 100%, é o seguinte:
Matches found in :defn (mass = 290)
./test/cases/associations/join_model_test.rb:237
./test/cases/associations/join_model_test.rb:262
./test/cases/associations/join_model_test.rb:271
./test/cases/associations/join_model_test.rb:623
./test/cases/associations/join_model_test.rb:650
Matches found in :defn (mass = 270)
./test/cases/validations_test.rb:929
./test/cases/validations_test.rb:937
./test/cases/validations_test.rb:945
./test/cases/validations_test.rb:953
./test/cases/validations_test.rb:961
./test/cases/validations_test.rb:969
Matches found in :defn (mass = 236)
./test/cases/validations_i18n_test.rb:491
./test/cases/validations_i18n_test.rb:510
./test/cases/validations_i18n_test.rb:529
./test/cases/validations_i18n_test.rb:549
Matches found in :scope (mass = 204)
./test/migrations/duplicate/3_innocent_jointable.rb:12
./test/migrations/interleaved/pass_1/3_innocent_jointable.rb:12
./test/migrations/interleaved/pass_2/3_innocent_jointable.rb:12
./test/migrations/interleaved/pass_3/3_innocent_jointable.rb:12
./test/migrations/missing/4_innocent_jointable.rb:12
./test/migrations/valid/3_innocent_jointable.rb:12
Matches found in :call (mass = 172)
./test/models/author.rb:64
./test/models/project.rb:17
Matches found in :defn (mass = 171)
./test/cases/validations_test.rb:977
./test/cases/validations_test.rb:991
./test/cases/validations_test.rb:1079
Note especialmente código que está sendo repetido em arquivos diferentes. No caso acima, como o código está em arquivos de teste, é provável que a repetição seja desejável para não poluir o escopo dos mesmos.
Vamos tomar um outra caso na biblioteca em si:
Matches found in :if (mass = 165)
./lib/active_record/associations/has_and_belongs_to_many_association.rb:82
./lib/active_record/associations/has_many_association.rb:100
./lib/active_record/associations/has_many_through_association.rb:194
O código repetido em questão é o seguinte:
if @reflection.options[:counter_sql]
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
elsif @reflection.options[:finder_sql]
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
else
@counter_sql = @finder_sql
end
Vendo os arquivos, é possível observar que o código é o mesmo. Considerando que os três arquivos representam classes com um pai em comum seria possível reduzir a repetição movendo o código para um método a classe pai.
Esse tipo de repetição tende a sumir com refatoramentos e o código acima provavelmente sumirá por um processo similar (alguém se habilita para um patch?).
Conclusão
Como é possível perceber, o Flay representa mais uma forma de observar o seu código em busca de coisas que podem ser melhoradas. É claro que isso não deve ser converter em uma obsessão de remover toda e qualquer repetição, o que demoraria mais tempo do que codificar coisas novas.
Como de usual, sugestões, críticas e dúvidas são bem-vindos.

Æ!!
Bem legal essa ferramenta Ronaldo!
Que bom que voltou a postar! E o melhor! Ótimos conteúdos!
Há braços
[...] Flay, eliminando repetições em código Ruby [...]