J'ai récemment regardé "Toutes les petites choses" de RailsConf 2014. Au cours de cette conférence, Sandi Metz refactorise une fonction qui comprend une grande instruction if imbriquée:
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
La première étape consiste à diviser la fonction en plusieurs plus petites:
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
Ce que j'ai trouvé intéressant, c'est la façon dont ces petites fonctions ont été écrites. brie_tick, par exemple, n'a pas été écrit en extrayant les parties pertinentes de la tickfonction d' origine , mais à partir de zéro en se référant aux test_brie_*tests unitaires. Une fois tous ces tests unitaires réussis, il a brie_tickété considéré comme terminé. Une fois toutes les petites fonctions terminées, la tickfonction monolithique d'origine a été supprimée.
Malheureusement, le présentateur ne semblait pas conscient que cette approche conduisait à trois des quatre *_tickfonctions étant erronées (et l'autre était vide!). Il existe des cas marginaux dans lesquels le comportement des *_tickfonctions diffère de celui de la tickfonction d' origine . Par exemple, @days_remaining <= 0in brie_tickdevrait être < 0- donc brie_tickne fonctionne pas correctement lorsqu'il est appelé avec days_remaining == 1et quality < 50.
Qu'est-ce qui a mal tourné ici? Est-ce un échec des tests - car il n'y avait pas de tests pour ces cas de bord particuliers? Ou un échec de refactoring - parce que le code aurait dû être transformé étape par étape plutôt que réécrit à partir de zéro?