Eu assisti recentemente "All the Little Things" do RailsConf 2014. Durante essa palestra, Sandi Metz refatora uma função que inclui uma declaração if aninhada grande:
def tick
if @name != 'Aged Brie' && @name != 'Backstage passes to a TAFKAL80ETC concert'
if @quality > 0
if @name != 'Sulfuras, Hand of Ragnaros'
@quality -= 1
end
end
else
...
end
...
end
O primeiro passo é dividir a função em várias menores:
def tick
case name
when 'Aged Brie'
return brie_tick
...
end
end
def brie_tick
@days_remaining -= 1
return if quality >= 50
@quality += 1
@quality += 1 if @days_remaining <= 0
end
O que achei interessante foi a maneira como essas funções menores foram escritas. brie_tick
, por exemplo, não foi escrito extraindo as partes relevantes da tick
função original , mas a partir do zero, consultando os test_brie_*
testes de unidade. Depois que todos esses testes de unidade foram aprovados, brie_tick
foi considerado feito. Depois que todas as pequenas funções foram concluídas, a tick
função monolítica original foi excluída.
Infelizmente, o apresentador parecia não ter consciência de que essa abordagem fazia com que três das quatro *_tick
funções estivessem erradas (e a outra estava vazia!). Existem casos extremos nos quais o comportamento das *_tick
funções difere do da tick
função original . Por exemplo, @days_remaining <= 0
em brie_tick
deve ser < 0
- portanto brie_tick
, não funciona corretamente quando chamado com days_remaining == 1
e quality < 50
.
O que deu errado aqui? Isso é uma falha no teste - porque não houve testes para esses casos específicos? Ou uma falha na refatoração - porque o código deveria ter sido transformado passo a passo em vez de reescrito do zero?