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 tickfunzione originale , ma da zero facendo riferimento ai test_brie_*test unitari. Una volta superati tutti questi test unitari, è brie_tickstato considerato fatto. Una volta eseguite tutte le piccole funzioni, la tickfunzione monolitica originale è stata eliminata.
Sfortunatamente, il presentatore sembrava inconsapevole del fatto che questo approccio ha portato a tre delle quattro *_tickfunzioni sbagliate (e l'altra era vuota!). Esistono casi limite in cui il comportamento delle *_tickfunzioni differisce da quello della tickfunzione originale . Ad esempio, @days_remaining <= 0in brie_tickdovrebbe essere < 0- quindi brie_ticknon funziona correttamente quando viene chiamato con days_remaining == 1e 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?