Нещодавно я переглядав "Всі дрібниці" від 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
.
Що тут пішло не так? Це невдача тестування - бо не було тестів для цих конкретних кращих випадків? Або невдача рефакторингу - тому що код повинен був перетворюватися поетапно, а не переписуватись з нуля?