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_tick
Zum Beispiel wurde nicht durch Extrahieren der relevanten Teile der ursprünglichen tick
Funktion geschrieben, sondern von Grund auf unter Bezugnahme auf die test_brie_*
Komponententests. Nachdem alle diese Unit-Tests bestanden waren, brie_tick
wurde dies als erledigt angesehen. Nachdem alle kleinen Funktionen ausgeführt wurden, wurde die ursprüngliche monolithische tick
Funktion gelöscht.
Leider schien der Moderator nicht zu wissen, dass dieser Ansatz dazu führte, dass drei der vier *_tick
Funktionen falsch waren (und die andere leer war!). Es gibt Randfälle, in denen sich das Verhalten der *_tick
Funktionen von dem der ursprünglichen tick
Funktion unterscheidet. Zum Beispiel @days_remaining <= 0
in brie_tick
sollte < 0
- so brie_tick
funktioniert nicht richtig , wenn sie aufgerufen mit days_remaining == 1
und 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?