Нещодавно я переглядав "Всі дрібниці" від RailsConf 2014. Під час цієї розмови Санді Мец відновлює функцію, яка включає велику вкладену операцію 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
Перший крок - розбити функцію на кілька менших:
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
Цікавим було те, як були написані ці менші функції. brie_tickНаприклад, написано не шляхом вилучення відповідних частин вихідної tickфункції, а з нуля, посилаючись на test_brie_*одиничні тести. Після того, як всі ці одиничні тести пройшли, brie_tickвважали виконаними. Як тільки всі дрібні функції були виконані, початкову монолітну tickфункцію видалили.
На жаль, ведучий здався невідомим, що такий підхід призвів до того, що три з чотирьох *_tickфункцій були неправильними (а інша була порожньою!). Є крайні випадки, коли поведінка *_tickфункцій відрізняється від поведінки вихідної tickфункції. Наприклад, @days_remaining <= 0в brie_tickповинен бути < 0- значить brie_tick, не працює правильно, коли викликається з days_remaining == 1і quality < 50.
Що тут пішло не так? Це невдача тестування - бо не було тестів для цих конкретних кращих випадків? Або невдача рефакторингу - тому що код повинен був перетворюватися поетапно, а не переписуватись з нуля?