Niedawno obejrzałem „Wszystkie małe rzeczy” z RailsConf 2014. Podczas tej rozmowy Sandi Metz refaktoryzuje funkcję, która zawiera dużą zagnieżdżoną instrukcję if:
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
Pierwszym krokiem jest podzielenie funkcji na kilka mniejszych:
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
Dla mnie interesujący był sposób pisania tych mniejszych funkcji. brie_tick
, na przykład, nie został napisany przez wyodrębnienie odpowiednich części oryginalnej tick
funkcji, ale od zera przez odniesienie do test_brie_*
testów jednostkowych. Po przejściu wszystkich tych testów jednostkowychbrie_tick
uznano zostały wykonane. Po wykonaniu wszystkich małych funkcji oryginalna tick
funkcja monolityczna została usunięta.
Niestety prezenter wydawał się nieświadomy, że takie podejście doprowadziło do tego, że trzy z czterech *_tick
funkcji były nieprawidłowe (a druga była pusta!). Istnieją przypadki skrajne, w których zachowanie *_tick
funkcji różni się od tick
funkcji oryginalnej . Na przykład, @days_remaining <= 0
w brie_tick
powinny być < 0
- tak brie_tick
nie działa prawidłowo, gdy nazywa się days_remaining == 1
iquality < 50
.
Co tu poszło nie tak? Czy to błąd testowania - ponieważ nie było testów dla tych konkretnych przypadków skrajnych? A może błąd refaktoryzacji - ponieważ kod powinien zostać przetworzony krok po kroku, a nie przepisany od nowa?