Di recente ho visto "All the Little Things" da RailsConf 2014. Durante questo discorso, Sandi Metz ha riformulato una funzione che include una grande dichiarazione if nidificata:
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
Il primo passo è suddividere la funzione in più piccole:
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
Ciò che ho trovato interessante è il modo in cui sono state scritte queste funzioni più piccole. brie_tick
, ad esempio, non è stato scritto estraendo le parti pertinenti della tick
funzione originale , ma da zero facendo riferimento ai test_brie_*
test unitari. Una volta superati tutti questi test unitari, è brie_tick
stato considerato fatto. Una volta eseguite tutte le piccole funzioni, la tick
funzione monolitica originale è stata eliminata.
Sfortunatamente, il presentatore sembrava inconsapevole del fatto che questo approccio ha portato a tre delle quattro *_tick
funzioni sbagliate (e l'altra era vuota!). Esistono casi limite in cui il comportamento delle *_tick
funzioni differisce da quello della tick
funzione originale . Ad esempio, @days_remaining <= 0
in brie_tick
dovrebbe essere < 0
- quindi brie_tick
non funziona correttamente quando viene chiamato con days_remaining == 1
e quality < 50
.
Cosa è andato storto qui? È un fallimento del test - perché non ci sono stati test per questi casi limite particolari? O un fallimento del refactoring - perché il codice avrebbe dovuto essere trasformato passo dopo passo anziché riscritto da zero?