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 tick
fonction 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 tick
fonction monolithique d'origine a été supprimée.
Malheureusement, le présentateur ne semblait pas conscient que cette approche conduisait à trois des quatre *_tick
fonctions étant erronées (et l'autre était vide!). Il existe des cas marginaux dans lesquels le comportement des *_tick
fonctions diffère de celui de la tick
fonction d' origine . Par exemple, @days_remaining <= 0
in brie_tick
devrait être < 0
- donc brie_tick
ne fonctionne pas correctement lorsqu'il est appelé avec days_remaining == 1
et 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?