Recientemente vi "All the Little Things" de RailsConf 2014. Durante esta charla, Sandi Metz refactoriza una función que incluye una gran instrucción if anidada:
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
El primer paso es dividir la función en varias más pequeñas:
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
Lo que encontré interesante fue la forma en que se escribieron estas funciones más pequeñas. brie_tick
, por ejemplo, no se escribió extrayendo las partes relevantes de la tick
función original , sino desde cero al referirse a las test_brie_*
pruebas unitarias. Una vez que todas estas pruebas unitarias pasaron, brie_tick
se consideró hecho. Una vez que se hicieron todas las funciones pequeñas, tick
se eliminó la función monolítica original .
Desafortunadamente, el presentador parecía ignorar que este enfoque llevó a que tres de las cuatro *_tick
funciones estuvieran mal (¡y la otra estaba vacía!). Hay casos extremos en los que el comportamiento de las *_tick
funciones difiere del de la tick
función original . Por ejemplo, @days_remaining <= 0
in brie_tick
debe ser < 0
, por lo brie_tick
que no funciona correctamente cuando se llama con days_remaining == 1
y quality < 50
.
¿Qué ha salido mal aquí? ¿Es esto un fracaso de la prueba, porque no hubo pruebas para estos casos límite particulares? ¿O una falla de refactorización, porque el código debería haberse transformado paso a paso en lugar de reescribirse desde cero?