Saya baru-baru ini menonton "All the Little Things" dari RailsConf 2014. Selama pembicaraan ini, Sandi Metz refactor fungsi yang mencakup pernyataan if-bersarang besar:
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
Langkah pertama adalah memecah fungsi menjadi beberapa yang lebih kecil:
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
Apa yang saya temukan menarik adalah cara fungsi-fungsi kecil ini ditulis. brie_tick
, misalnya, tidak ditulis dengan mengekstraksi bagian yang relevan dari tick
fungsi asli , tetapi dari awal dengan mengacu pada test_brie_*
unit test. Setelah semua tes unit ini berlalu, brie_tick
dianggap sudah selesai. Setelah semua fungsi kecil selesai, monolitik aslitick
fungsi dihapus.
Sayangnya, presenter sepertinya tidak menyadari bahwa pendekatan ini menyebabkan tiga dari empat *_tick
fungsi salah (dan yang lainnya kosong!). Ada kasus tepi di mana perilaku *_tick
fungsi berbeda dari tick
fungsi aslinya . Misalnya, @days_remaining <= 0
dalam brie_tick
seharusnya < 0
- jadi brie_tick
tidak berfungsi dengan benar ketika dipanggil dengan days_remaining == 1
dan quality < 50
.
Apa yang salah di sini? Apakah ini kegagalan pengujian - karena tidak ada tes untuk kasus tepi khusus ini? Atau kegagalan refactoring - karena kode seharusnya ditransformasi selangkah demi selangkah daripada ditulis ulang dari awal?