le problème : le kata diamond
J’utilise la description de Seb Rose, simple et efficace.
Étant donnée une lettre, afficher une diamant commençant par ‘A’ avec la lettre donnée au point le plus large.
Par exemple : print-diamond ‘C’ affiche :
A
B B
C C
B B
A
J’ai entendu beaucoup de chose au sujet de ce kata. Récemment1, il était le sujet principal d’un workshop de 2 heures lors de l’excellente conférence NCrafts. Bien après la bataille initiale2, à mon tour de m’y coller
ma solution
l’idée
À chaque fois qu’on me dit que quelque chose est difficile voir impossible, j’ignore cette information et tente le coup comme je le ferais si on ne m’avait rien dit. Donc j’applique l’approche TDD habituelle : rouge, vert, refactor. Et c’est tout.
Écrire un test, le faire passer, nettoyer le code. Une étape à la fois.
le premier test
Commençons donc par le premier test.
Le test est rouge, il est temps de le faire passer au vert.
Yep, c’est une implémentation totalement stupide. Mais ça marche :)
Et relativement propre en lui même. Ajoutons donc un autre test.
a second test
Faisons le passer aussi simplement que possible.
Et c’est vert. C’est le moment de refactorer.
Ok, il y a deux fois la même ligne “ A “ dans le diamant ‘B’, mais je n’arrive pas à lui donner un sens. Je n’ai probablement pas assez de données.
Donc je garde les choses comme ça pour l’instant et ajoute un troisième test.
et le troisième
Comme j’essaie d’être cohérent, ma façon de le faire passer vert ne sera pas différente des précédentes :)
Yep, encore une fois, c’est une implémentation simpliste. À ce moment, je ne me soucis pas d’être intelligent, ni de faire les choses proprement. À ce moment, je veux juste être sûr que le test que je viens d’écrire est bien celui que j’avais l’intention d’écrire.
En le faisant passer au vert avec une solution simple, il y a peu de risques que j’introduise une erreur dans l’implémentation. Ainsi quand je vois le test passer de rouge à vert, je peux avoir confiance dans mon test.
C’est ma façon de tester mes tests.
Bon, maintenant, j’ai peut-être assez de matière pour vois si je peux faire apparaître des choses intéressantes.
le temps de commencer à refactorer.
Il doit y avoir une logique dans la distribution des lettres et des espaces. Comme je ne la comprends pas encore, je vais artificiellement les séparer. Oui, je suis sur le point de créer volontairement et artificiellement de la duplication.
Maintenant, je vois que la première et la dernière ligne suivent le motif z spaces - A - z spaces
.
Si un diamant ‘A’ a une taille de 1, ‘B’ de 2, ‘C’ de 3 et ainsi de suite, alors z = (size - 1)
.
Parfait. Ajoutons cette logique dans le code.
Je remarque aussi que les autres lignes suivent un autre motif : x spaces - a char - y spaces - same char - x spaces
.
Faisons le apparaître dans le code.
Il y a indéniablement un truc qui lie tout ça. Mais je ne le vois pas encore.
a fourth test
Ajoutons le diamant ‘D’. Je ne vous montre pas le test, c’est le même que les précédent mais avec un diamant ‘D’. Faisons le passer en ajoutant cela à la solution actuelle.
À partir de maintenant, je ne montrerais que le refactoring qui a lieu sur le diamant ‘D’.
Tous ces entiers doivent avoir une sorte de relation. Essayons de les réarranger.
Introduisons width
: c’est la largeur d’un diamant.
Après avoir dessiné quelques diamant sur une feuille de papier, j’en conclus que la largeur est size * 2 - 1
.
Bien, je crois que je l’ai. Faisons apparaître une notion d’étage (floor
)
Impeccable, toutes les lignes se ressemble maintenant. On peut lui donner un nom.
C’est mieux, mais ça n’est pas encore fini.
Il y a un motif dans ces appels. Voyons voir ce qui arrive si on renumérote les étages de façon à ce qu’ils soit en séquence de +size
à -size
.
On dirait bien qu’il y a une boucle de cachée là dedans. Ce qui nous empêche de l’introduire est le caractère en paramètre de l’appel à diamondWall()
.
Mais on peut facilement s’en débarrasser puisque le caractère utilisé à un étage peut être calculé à partir de la taille du diamant et l’étage actuel.
Et finalement,
réflexions
Ma première tentative sur ce kata m’a pris environ deux heures dans un train3.
Comme à mon habitude, j’ai fait le refactoring avec de toutes petites étapes de façon à conserver mes tests verts tout le temps J’ai essayé de laisser l’algorithme émerger du refactoring et non l’inverse.
Bien sur, il n’émerge pas de lui même. Avec ce type de problème, j’ai dans l’idée qu’il va y avoir une forme de boucle dans la solution. Mes refactorings ont donc pour but de faire apparaître cette boucle en introduisant volontairement de la duplication pour que chacune des lignes se ressemblent et pouvoir ensuite unifier les indices.
En ne faisant qu’un changement à la fois, les tests sont un outils très efficaces pour m’aider à trouver les erreurs que je fais4.
Le code au complet est disponible sur mon github. Regardez dans les branches :)5
ensuite
Une fois rendu là, si c’était du code de production, je donnerais probablement un nom à size -2
et -(size - 2)
. Peut-être ‘grenier’ et ‘cave’ pour rester dans la métaphore du bâtiment size
devrait alors probablement devenir height
(hauteur).
Je n’aime pas trop l’utilisation de Character
un peu partout alors que cela représente en fait un type de diamant. J’introduirais alors une classe DiamondKind
pour contentir ce Character
ainsi que size
et width
. Peut-être d’autres opérations sur Character
.
Aussi une classe Floor
?6.
Le diamant ‘A’ deviendrait sûrement une constante.
image : By kubotake CC BY 2.0, via Wikimedia Commons. During a solar eclipse, in french, the third contact is also called “Effet diamant” or diamond effect.
-
Bon, c’était récent quand j’ai commencé à écrire la version originale de cet article. ↩
-
démarré je pense par cet article Recycling test in TDD de Seb Rose. ↩
-
et j’en ai fait un paquet. Les décalages +1/-1 sont mon enfer personnel :) ↩
-
dans la branch 2015.05.23-step_by_step, j’ai fait un commit pour chaque étape avec des explications sur l’objectif de cette étape. Pour le principal, c’est ce que vous retrouvez dans ce billet. ↩
-
mais actuellement l’utilisation de
floor
est un détail d’implémentation. Je ne suis pas sur que ce soit une bonne idée de lui donner sa propre classe qui deviendrait publique. ↩
Poursuivre la discussion
Envie de réagir à l'article ? Il suffit de me laisser un message via la page de contact, sur mastodon @avernois@piaille.fr ou un billet chez vous.
Vous pouvez aussi proposer des modifications via le source de cet article sur gitlab.