Dans l'article sur le Comment Driven Refactoring, j'ai parlé des avantages du CDR et de pourquoi cette manière de refactorer fonctionne aussi bien.
Maintenant, on va passer à la pratique !
On va suivre pas-à-pas le déroulement d'un kata de Comment Driven Refactoring, de la mise en place du code jusqu'au résultat final.
Au fil de l'article, je te laisserais régulièrement l'occasion de chercher une réponse avant de te montrer le résultat que j'ai obtenu. Si tu le souhaites, tu peux voir ça comme un kata intéractif. N'hésite pas à suivre cet article avec ton IDE préféré ouvert sur le côté. 😉
L'exemple que j'utilise est en PHP version 7.4 (la meilleure ^^).
Tu es prêt ? C'est parti !
Mise en place
Avant de se lancer dans le CDR, il nous faudrait déjà un morceau de code à refactorer.
On va créer ce code.
Pour les besoins de la démo, on voudrait utiliser un code ni trop propre, ni trop sale. On veut plutôt du code représentatif de ce qu'on pourrait retrouver dans un logiciel quelconque.
Ma méthode pour créer ce genre de code est la suivante : je pars d'une base de quelques lignes (2 à 5 en général), et je la complète en improvisant.
La base
Voici le code de base qu'on va utiliser pour notre session :
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Comme tu peux le voir, il ne s'y passe pas grand chose : on crée un utilisateur et on lui assigne une date de naissance.
Simple. Basique.
Les fioritures
Le seul problème avec ce code, c'est qu'il plante :
PHP Fatal error: Uncaught Error: Class "User" not found
Eh oui, c'est normal : la classe User
n'existe pas !
On va donc la créer. On en profite aussi pour ajouter quelques règles métier supplémentaires.
Ne t'en fais pas si le code n'est pas très clair. C'est voulu. On arrange ça juste après. 😉
class User
{
protected $birthDate;
public function setBirthDate($date)
{
$d = DateTime::createFromFormat('Y-m-d', $date);
if (!$d) {
throw new Exception('Bad date format');
}
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
$this->birthDate = $date;
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Pour résumer : quand on lui assigne une date de naissance, la classe User vérifie que la date est valide et qu'elle est bien dans le passé.
Ce sont des règles arbitraires, mais on va partir du principe qu'elles ont du sens pour notre exemple.
L'important, c'est que maintenant qu'on a ajouté la classe User, le script s'exécute correctement.
Le but de notre refacto est aussi un peu plus clair : on va améliorer la classe User.
On a maintenant tout ce qu'il faut pour se lancer, on peut y aller.
Étape 1 : ajouter des commentaires
Commençons notre session de Comment Driven Refactoring.
La première étape du CDR consiste à ajouter des commentaires dans le code pour l'expliciter.
La règle d'or de cette étape, c'est :
Le code final sera aussi bon que les commentaires qu'il contient.
Quand tu pratiques le Comment Driven Refactoring, il est normal de passer du temps sur cette étape : c'est là que tout se joue.
N'aie pas peur de choisir avec soin ce que diront les commentaires et veille à ce que les commentaires couvrent le maximum de code.
À ton tour
Quels commentaires tu ajouterais au code qu'on a pris comme exemple ? Quelles sont les clarifications que tu lui apporterais ?
N'hésite pas à te poser quelques secondes pour imaginer ce que ça donnerait. 👍
J'ai donc tenté d'apporter un maximum d'informations à la classe User
sous forme de commentaires.
Voilà ce que ça donne :
/**
* A User
*/
class User
{
/**
* @var string Date of birth format Y-m-d
*/
protected $birthDate;
/**
* @param string $date Birth date
* @return void
* @throws Exception
*/
public function setBirthDate($date)
{
// validate birth date
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
// store birth date
$this->birthDate = $date;
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Là, c'est le moment où tu peux te poser un peu pour t'imprégner du code, car tout y est indiqué. Aucune autre information ne sera ajoutée ni retirée dans le code après cette étape.
C'est bon ? Tout est clair pour toi ?
Alors on a terminé la première étape !
Étape 2 : retirer des commentaires
Notre code regorge maintenant d'informations sous forme de commentaires. Transformons-les en code.
Ici, la règle du jeu, c'est de défoncer joyeusement supprimer les commentaires. 😈
C'est maintenant notre seul et unique but. Pas de pitié !
Règle d'or de l'étape 2 : on ne retire que ce qui est déjà clair dans le code.
Ça veut dire que les commentaires qui apportent des informations supplémentaires au code sont intouchables.
On va donc (souvent) devoir faire en sorte que le code change, afin que les commentaires deviennent redondants et qu'on puisse les supprimer.
Les commentaires inutiles
On va procéder pas à pas en augmentant progressivement le niveau de difficulté.
Tout d'abord, attaquons-nous au premier commentaire du code :
/**
* A User
*/
class User
Ce commentaire nous indique que la classe User
représente ... un User.
Non, sans blague ? Merci pour la précision ! 😁

En tout cas, c'est une bonne nouvelle pour nous.
On sait déjà que la classe User
est un User : c'est littéralement écrit dessus.
Ce commentaire n'apporte pas d'information supplémentaire au code, donc on peut l'enlever.
/**
* A User
*/
class User
{
/**
* @var string Date of birth format Y-m-d
*/
protected $birthDate;
/**
* @param string $date Birth date
* @return void
* @throws Exception
*/
public function setBirthDate($date)
{
// validate birth date
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
// store birth date
$this->birthDate = $date;
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Et on n'a même pas eu à toucher au code en lui-même !
Ça, c'est fait.
On passe à la suite.
À ton tour
De la même manière que ce qu'on vient de faire, est-ce que tu vois un autre commentaire qu'on pourrait retirer sans rien changer d'autre ? Un commentaire qui explicite quelque chose qui est déjà évident.
Tu l'as trouvé ?
Voyons ça :
class User
{
/**
* @var string Date of birth format Y-m-d
*/
protected $birthDate;
/**
* @param string $date Birth date
* @return void
* @throws Exception
*/
public function setBirthDate($date)
{
// validate birth date
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
// store birth date
$this->birthDate = $date;
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
En effet, la présence du birthDate =
dans le code laisse peu de place à l'interprétation : on va stocker la date de naissance.
Le code nous dit même où on va le stocker (dans $this
), ce que le commentaire ne fait pas.
Le commentaire store birth date
est donc inutile : on peut le retirer.
Et un de plus en moins !
On passe au niveau supérieur ?
La PHPDoc
Intéressons-nous à la documentation.
À partir de maintenant, toutes les modifications qu'on devra apporter aux commentaires nécessiteront qu'on modifie aussi le code.
Est-ce que la PHPDoc compte comme un commentaire ? Est-ce qu'on doit aussi chercher à la supprimer ?
Oui. Techniquement, la PHPDoc c'est du commentaire, donc ça compte. Même si c'est un type spécial de commentaire.
On peut donc explorer cette voie.
Pour te donner un exemple, si on type le retour de la méthode setBirthDate
, on n'aura plus besoin de préciser @return void
dans la PHPDoc :
class User
{
/**
* @var string Date of birth format Y-m-d
*/
protected $birthDate;
/**
* @param string $date Birth date
* @return void
* @throws Exception
*/
public function setBirthDate($date): void
{
// validate birth date
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
$this->birthDate = $date;
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Cela nous a permis de supprimer une ligne entière de commentaire !
À ton tour
Est-ce que tu vois une autre ligne de PHPDoc qu'on pourrait retirer en entier de cette manière ?
La réponse arrive juste après.
class User
{
/**
* @var string Date of birth format Y-m-d
*/
protected $birthDate;
/**
* @param string $date Birth date
* @throws Exception
*/
public function setBirthDate(string $date): void
{
// validate birth date
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
$this->birthDate = $date;
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Oui, c'était le paramètre de setBirthDate
. Voyons pourquoi.
Que nous disait le commentaire ?
@param
: "c'est un paramètre". Merci, mais on le savait déjà.$date
: son nom. C'est déjà écrit dans le code.Birth date
: ce qu'il représente (une date de naissance). Le code nous le disait déjà, vu que la méthode est un setter pour la date de naissance et que le paramètre s'appelle "date".
La seule info présente dans le commentaire mais pas dans le code, c'était string
: le type du paramètre. Et maintenant qu'on l'a déclaré directement dans le code, on est bon là-dessus aussi.
En ajoutant le type du paramètre, le dernier élément de cette ligne de commentaire devient totalement redondant. Alors on peut supprimer toute la ligne.
Allez, on fait ça encore une fois ?
Cette fois, on va se concentrer sur le premier commentaire : string Date of birth format Y-m-d
.
C'est l'occasion de préciser qu'on n'est pas obligé de retirer TOUT le commentaire d'un coup. Si le commentaire a toujours du sens au final, on peut juste enlever ce qui est redondant.
Voilà à quoi ça peut ressembler :
class User
{
/**
* @var string Date of birth format Y-m-d
*/
protected string $birthDate;
/**
* @throws Exception
*/
public function setBirthDate(string $date): void
{
// validate birth date
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
$this->birthDate = $date;
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Ici, on a indiqué le type de $birthDate
, ce qui nous a permis de retirer tout le commentaire, sauf ce qui parle du format de la date.
On finira par retirer ce qu'il en reste, tu verras. Mais pas maintenant. 😉
Laissons la PHPDoc de côté et augmentons encore de niveau.
L'intérieur de la méthode
L'intérieur de la méthode setBirthDate comporte trois commentaires. Pour le moment, héhé.
On va procéder dans l'ordre, et chercher à supprimer le commentaire validate birth date
.
À ton tour
Comment ferais-tu pour que les informations présentes dans ce commentaire deviennent explicites dans le code ?
class User
{
/**
* format Y-m-d
*/
protected string $birthDate;
/**
* @throws Exception
*/
public function setBirthDate(string $date): void
{
// validate birth date
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
$this->validateBirthDate($date);
$this->birthDate = $date;
}
/**
* @throws Exception
*/
protected function validateBirthDate(string $date): void
{
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
On a extrait une méthode à partir du commentaire : validate birth date
est devenu validateBirthDate()
.
Et à l'intérieur de cette méthode, on a mis toutes les lignes qui étaient concernées par le commentaire.
Résultat : le commentaire a disparu !
En plus, la méthode setBirthDate
ne possède plus de commentaires. Ça veut dire qu'on est sur la bonne voie ! 💪
On passe au commentaire suivant :
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
À ton tour
Comment tu ferais pour éliminer ce commentaire ?
class User
{
/**
* format Y-m-d
*/
protected string $birthDate;
public function setBirthDate(string $date): void
{
$this->validateBirthDate($date);
$this->birthDate = $date;
}
/**
* @throws Exception
*/
protected function validateBirthDate(string $date): void
{
$d = DateTime::createFromFormat('Y-m-d', $date);
// assert is valid date
if (!$d) {
throw new Exception('Bad date format');
}
$this->assertIsValidDate($d);
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
}
/**
* @param DateTime|false $d
* @throws Exception
*/
protected function assertIsValidDate($d): void
{
if (!$d) {
throw new Exception('Bad date format');
}
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Eh oui, on procède exactement comme à l'étape d'avant : le commentaire assert is valid date
devient la méthode assertIsValidDate
.
Note que son paramètre $d
est soit un DateTime
, soit égal à false
, au cas où la date n'a pas pu être calculée correctement à la ligne d'avant (mauvais format ou autre). Pour ne pas perdre d'information, on a dû le préciser dans la PHPDoc de assertIsValidDate
.
Hmmm ... cette refacto a supprimé une ligne de commentaires, mais elle en a créé deux autres.
T'es sûr de ton coup ?
Oui, t'inquiète. On s'en sort toujours avec le Comment Driven Refactoring.
L'important, c'est de supprimer au moins une partie d'un commentaire existant à chaque fois.
Si on crée d'autres commentaires au passage, c'est pas grave.
Tu verras qu'on finira par les supprimer aussi. 😉
À ton tour
Bon, ça fait deux fois qu'on transforme un commentaire en fonction, je suppose que tu sais déjà comment on va traiter le commentaire assert date is in the past
.
class User
{
/**
* format Y-m-d
*/
protected string $birthDate;
public function setBirthDate(string $date): void
{
$this->validateBirthDate($date);
$this->birthDate = $date;
}
/**
* @throws Exception
*/
protected function validateBirthDate(string $date): void
{
$d = DateTime::createFromFormat('Y-m-d', $date);
$this->assertIsValidDate($d);
// assert date is in the past
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
$this->assertIsInThePast($d);
}
/**
* @param DateTime|false $d
* @throws Exception
*/
protected function assertIsValidDate($d): void
{
if (!$d) {
throw new Exception('Bad date format');
}
}
/**
* @throws Exception
*/
protected function assertIsInThePast(DateTime $d): void
{
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
}
}
$date = '1989-12-16';
$user = new User();
$user->setBirthDate($date);
Le commentaire assert date is in the past
a été changé en méthode assertIsInThePast
.
Et pas besoin du mot "date" dans son nom, vu qu'on sait déjà qu'il prend une date en paramètre (un objet DateTime
).
Le commentaire peut donc disparaitre.
Mais ... le code est encore plus gros que tout à l'heure !
C'était mieux avant !
T'as vu le bazar qu'on a mis dedans ? 😱
Peut-être, mais nous, on s'intéresse juste aux commentaires. Le code, c'est un détail pour le moment.
D'ailleurs, je vois même quelque chose d'encourageant à ce niveau : la méthode validateBirthdate
ne possède plus de commentaires.
C'est très bon signe, on progresse. 💪
Dernière ligne droite
La meilleure partie, celle où la magie opère !
Jusqu'à présent, nos modifications n'ont pas amélioré notre code tant que ça.
La classe User a même beaucoup gagné en taille. Et je ne parle même pas du commentaire DateTime|false
, qui me pique un peu les yeux !
Il est temps d'arranger ça. On passe aux choses sérieuses !

Tu es prêt ?
Alors, on y va !
Tout à l'heure, on a laissé ce commentaire tout en haut du code :
/**
* format Y-m-d
*/
protected string $birthDate;
Je l'avais annoncé, il va passer à la casserole. Et ça va se faire maintenant !
À ton tour
Comment tu ferais pour éliminer ce commentaire ?
class User
{
/**
* format Y-m-d
*/
protected stringDateTime $birthDate;
public function setBirthDate(stringDateTime $date): void
{
$this->validateBirthDate($date);
$this->birthDate = $date;
}
protected function validateBirthDate(stringDateTime $date): void
{
$d = DateTime::createFromFormat('Y-m-d', $date);
$this->assertIsValidDate($d$date);
$this->assertIsInThePast($d$date);
}
/**
* @param DateTime|false $d
* @throws Exception
*/
protected function assertIsValidDate($d): void
{
if (!$d) {
throw new Exception('Bad date format');
}
}
/**
* @throws Exception
*/
protected function assertIsInThePast(DateTime $d): void
{
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
}
}
$date = '1989-12-16';
$d = DateTime::createFromFormat('Y-m-d', $date);
$user = new User();
$user->setBirthDate($date$d);
Tu l'avais trouvé ? Si c'est le cas, félicitations ! 👍
L'objet DateTime
en PHP n'a pas de format fixe pour la date. En transformant le string
en DateTime
, le commentaire n'a plus de raisons de rester là. Adieu !
Ça nous a aussi forcé à adapter setBirthDate
et validateBirthDate
. Maintenant, on instancie le DateTime
avant de créer notre utilisateur.
D'ailleurs, à propos de ces modifications, tu ne vois pas quelque chose de bizarre dans le code ?
À ton tour
Tu as trouvé ce qui cloche suite à notre dernière modification ?
Eh oui, la méthode assertIsValidDate
prend maintenant toujours un DateTime
en entrée. Son paramètre ne sera donc jamais égal à false
et la méthode ne lèvera jamais d'exception.
class User
{
protected DateTime $birthDate;
public function setBirthDate(DateTime $date): void
{
$this->validateBirthDate($date);
$this->birthDate = $date;
}
protected function validateBirthDate(DateTime $date): void
{
$this->assertIsValidDate($date);
$this->assertIsInThePast($date);
}
/**
* @param DateTime|false $d
* @throws Exception
*/
protected function assertIsValidDate($d): void
{
if (!$d) {
throw new Exception('Bad date format');
}
}
/**
* @throws Exception
*/
protected function assertIsInThePast(DateTime $d): void
{
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
}
}
$date = '1989-12-16';
$d = DateTime::createFromFormat('Y-m-d', $date);
$user = new User();
$user->setBirthDate($d);
La méthode n'a donc plus de raisons d'être. On peut la supprimer, ainsi que les deux commentaires qui lui sont rattachés.
Aparté : la gestion d'erreurs
Avec notre nouveau code, si on utilise une mauvaise date :
$date = 'not a valid date';
$d = DateTime::createFromFormat('Y-m-d', $date);
$user = new User();
$user->setBirthDate($d);
On aura l'erreur suivante :
PHP Fatal error: Uncaught TypeError: User::setBirthDate(): Argument #1 ($date) must be of type DateTime, bool given
Attention : c'est un objet Error
qui est généré dans ce cas, pas une Exception
, comme c'était le cas avant.
L'erreur ne sera donc pas interceptée avec :
try {
// le code
} catch (Exception $e) {
// récupération de l'erreur
}
Mais les deux implémentent Throwable
. On peut donc l'attrapper comme ça :
try {
// le code
} catch (Throwable $t) {
// récupération de l'erreur
}
Ici, on va partir du principe que le code gère correctement les Error
, et donc que cette différence ne change rien au comportement du code.
Notre code ressemble maintenant à ça :
class User
{
protected DateTime $birthDate;
public function setBirthDate(DateTime $date): void
{
$this->validateBirthDate($date);
$this->birthDate = $date;
}
protected function validateBirthDate(DateTime $date): void
{
$this->assertIsInThePast($date);
}
/**
* @throws Exception
*/
protected function assertIsInThePast(DateTime $d): void
{
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
}
}
$date = '1989-12-16';
$d = DateTime::createFromFormat('Y-m-d', $date);
$user = new User();
$user->setBirthDate($d);
Il ne reste plus qu'un seul commentaire dans la classe User
.
Encore une dernière modification et on aura terminé !
À ton tour
Comment pourrait-on faire pour retirer le dernier commentaire de la classe User
?
Allez, découvrons la solution :
class BirthDate extends DateTime
{
public function __construct($datetime = "now", ?DateTimeZone $timezone = null)
{
parent::__construct($datetime, $timezone);
$this->assertIsInThePast();
}
/**
* @throws Exception
*/
protected function assertIsInThePast(): void
{
if ($this->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
}
}
class User
{
protected DateTimeBirthDate $birthDate;
public function setBirthDate(DateTimeBirthDate $date): void
{
$this->validateBirthDate($date);
$this->birthDate = $date;
}
protected function validateBirthDate(DateTime $date): void
{
$this->assertIsInThePast($date);
}
/**
* @throws Exception
*/
protected function assertIsInThePast(DateTime $d): void
{
if ($d->getTimestamp() > time()) {
throw new Exception('Date must be in the past');
}
}
}
$date = '1989-12-16';
$d = DateTimeBirthDate::createFromFormat('Y-m-d', $date);
$user = new User();
$user->setBirthDate($d);
On a amélioré le DateTime qu'on utilisait jusqu'à présent en créant un objet qui effectue nativement les assertions dont on a besoin.
Voici comment ça s'est fait :
- On a étendu la classe
DateTime
pour créerBirthDate
. - On a déplacé le contenu de
validateBirthDate
dans le constructeur de BirthDate. - On a déplacé la méthode
assertIsInThePast
dans la classe BirthDate.
En créant une classe qui représente une date qui est forcément dans le passé, l'assertion et le commentaire qui lui est attaché ont été déplacés dans la classe BirthDate
.
Techniquement, on a retiré un commentaire de la classe User
. Ça compte !
Et maintenant ? On fait quoi ?
Notre but était de retirer les commentaires de la classe User, et il n'y en a plus.
La refacto est donc terminée !

Si tu as tenu jusqu'ici, bien joué !
Comparaison finale
Prenons un peu d'altitude sur ce qu'on vient de faire, comparons le code avant et après refacto :
class User
{
protected BirthDate $birthDate;
public function setBirthDate(BirthDate $date): void
{
$d = DateTime::createFromFormat('Y-m-d', $date);
if (!$d) {
throw new Exception('Bad date format');
}
if ($d->getTimestamp() > $time) {
throw new Exception('Date must be in the past');
}
$this->birthDate = $date;
}
}
Alors, il est magnifique ou il est pas magnifique, ce code ? 😁

Plus sérieusement, voici quelques-unes des choses qu'on peut remarquer :
- Le code est plus lisible. Avant, il y avait 9 lignes possédant des informations importantes à la compréhension du code, et les informations étaient peu précises (pas de typage etc). Maintenant, la classe possède seulement 4 lignes contenant des informations importantes, et tout est précisément décrit.
- Le code est plus simple. La classe User est réduite à une seule instruction ! Si on veut que le code fasse quelque chose, c'est le minimum possible. On n'aurait pas pu faire mieux que ça.
- Le code est plus clair. On peut facilement résumer l'intégralité de la classe en quelques mots : "c'est un utilisateur à qui on peut assigner une date de naissance". Elle ne fait rien de plus et rien de moins.
- Le code est plus robuste. La classe User ne lève plus d'exceptions et son comportement est prévisible à 100%.
En bref, le code s'est nettement amélioré.
On n'y a pas pensé !
Le Comment Driven Refactoring nous demande juste de penser aux commentaires. Faisons un petit tour d'horizon des choses auxquelles on n'a pas eu besoin de réfléchir.
- Primitive Obsession. Notre code utilisait un
string
pour représenter une date de naissance, ce qui est un code smell. En convertissant lestring
enDateTime
, puis enBirthDate
, on a corrigé ce problème. - Fail Fast. Si le code doit planter, qu'il plante le plus tôt possible. Suite à nos modifications, le code ne peut plus lever d'exceptions/erreurs après la création du
BirthDate
. C'est propre ! - Single Responsability Principle. User faisait trop de choses : d'une part il représentait un utilisateur, d'autre part il faisait de la gestion d'erreurs sur les dates. En créant BirthDate, on a permis à User de ne gérer que ce qui concerne un utilisateur et à BirthDate de ne s'occuper que de la validation des dates.
- Single Level of Abstraction. Quand toutes les informations présentes dans un même bloc ont le même niveau d'abstraction, le code devient plus compréhensible. C'est ce qu'on a fait en extrayant les méthodes
assert*
. - Facade. Les commentaires "validate birth date", "assert is valid date" et "assert date is in the past" faisaient référence à ce qu'il se passait dans plusieurs lignes de code. En regroupant ces lignes dans un bloc (ici, une méthode) qui ne nous laisse voir que sa signature, on a appliqué le design pattern Facade.
Je pourrais continuer comme ça pendant longtemps.
En faisant une refacto classique, on aurait dû se prendre la tête réfléchir à ces choses pour aboutir à ce résultat.
Ici, on n'a pas eu besoin de s'encombrer le cerveau avec tous ces "détails" et avec ces grands principes pour progresser dans la refacto. Les grands principes sont venus d'eux-même dans le code.
On a juste cherché à retirer les commentaires du code, en suivant une seule règle simple ! Rien d'autre.
Conclusions
J'espère que ce kata t'a plu et que tu vois mieux comment fonctionne le CDR en pratique.
Comme promis, je suis entré dans les détails. Mais j'ai aussi survolé pas mal de sujets.
Contacte-moi si tu veux en discuter. 😉
Une dernière chose dont je n'ai pas parlé explicitement mais qui me paraît importante, c'est le côté ludique du CDR :
A chaque fois qu'on trouve un commentaire à ajouter, à améliorer ou à retirer, notre cerveau reçoit une petite dose de récompense chimique. C'est comme quand on fait du Test Driven Development et que les tests passent du rouge au vert.
Notre cerveau interprète ça comme une victoire. Et cette victoire, il l'obtient régulièrement et facilement. C'est parfait pour rester satisfait et motivé dans notre tâche.
Oui, le Comment Driven Refactoring, c'est fun !