Ich habe kürzlich "All the Little Things" von RailsConf 2014 gesehen. Während dieses Vortrags überarbeitet Sandi Metz eine Funktion, die eine große verschachtelte if-Anweisung enthält:
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
Der erste Schritt besteht darin, die Funktion in mehrere kleinere zu unterteilen:
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
Was ich interessant fand, war die Art und Weise, wie diese kleineren Funktionen geschrieben wurden. brie_tickZum Beispiel wurde nicht durch Extrahieren der relevanten Teile der ursprünglichen tickFunktion geschrieben, sondern von Grund auf unter Bezugnahme auf die test_brie_*Komponententests. Nachdem alle diese Unit-Tests bestanden waren, brie_tickwurde dies als erledigt angesehen. Nachdem alle kleinen Funktionen ausgeführt wurden, wurde die ursprüngliche monolithische tickFunktion gelöscht.
Leider schien der Moderator nicht zu wissen, dass dieser Ansatz dazu führte, dass drei der vier *_tickFunktionen falsch waren (und die andere leer war!). Es gibt Randfälle, in denen sich das Verhalten der *_tickFunktionen von dem der ursprünglichen tickFunktion unterscheidet. Zum Beispiel @days_remaining <= 0in brie_ticksollte < 0- so brie_tickfunktioniert nicht richtig , wenn sie aufgerufen mit days_remaining == 1und quality < 50.
Was ist hier falsch gelaufen? Ist dies ein Testfehler - weil es für diese speziellen Randfälle keine Tests gab? Oder ein Fehler beim Refactoring - weil der Code Schritt für Schritt hätte transformiert und nicht von Grund auf neu geschrieben werden müssen?