Introduction
Jour de déménagement. Une pile de cartons attend dans le salon, chacun étiqueté « divers ». Six mois plus tard, retrouver la cafetière suppose d'en ouvrir quatre avant de tomber sur la bonne. Le déménagement s'est bien passé, l'étiquetage, lui, a juste déplacé le problème dans le temps.
Le code fait exactement la même chose. Une variable nommée data, une méthode nommée process(), une classe nommée Manager : ce sont des cartons « divers ». Rien n'empêche le code de fonctionner, mais chaque personne qui le lira plus tard devra ouvrir le carton pour savoir ce qu'il contient vraiment.
Pourquoi ça compte
Un nom est une promesse faite au lecteur : il dit ce qu'il y a dedans, pour que le lecteur n'ait pas besoin d'aller vérifier. Quand cette promesse est tenue, on peut lire une méthode comme une phrase et comprendre l'intention sans descendre dans l'implémentation. Quand elle ne l'est pas, chaque appel devient un aller-retour obligé vers la définition, juste pour savoir si process() valide, sauvegarde, ou envoie un e-mail.
Ce coût est invisible ligne par ligne. Il devient énorme à l'échelle d'un projet entier, relu des centaines de fois par des personnes qui n'étaient pas là quand le nom a été choisi.
Quatre avant/après, tirés de code réel
Une variable qui dit ce qu'elle contient, pas d'où elle vient
Avant
Map<String, Object> data = fetchReservations();data ne dit rien : ni ce que contiennent les valeurs, ni la nature des clés. Le type Object n'aide pas non plus, il faudra suivre la valeur jusqu'à son usage pour comprendre de quoi il s'agit.
Après
Map<String, Reservation> reservationsParUtilisateur = fetchReservations();Le nom porte maintenant l'information que le type seul ne donnait pas : ce que représente la clé. Plus besoin de deviner, la ligne se lit toute seule.
Un booléen qui pose une question, pas un statut caché
Avant
boolean flag = reservation.getStatus() == 2;Ici, deux informations sont perdues en route : ce que flag représente, et ce que signifie le statut 2. Comprendre cette ligne demande d'aller chercher la définition du statut ailleurs dans le code.
Après
boolean estAnnulable = reservation.dateDepart().isAfter(maintenant.plusHours(24));Un booléen se nomme comme une question à laquelle il répond par vrai ou faux. estAnnulable répond à « peut-on annuler cette réservation ? » sans qu'il soit nécessaire de lire la condition pour le savoir.
Une méthode nommée pour l'action qu'elle effectue
Avant
public void process(Reservation reservation) {
if (reservation.getStatus() == 2) {
reservation.setStatus(3);
emailService.send(reservation.getEmail());
}
}process est un nom qui convient à peu près à n'importe quelle méthode du projet, ce qui revient à ne rien dire de celle-ci en particulier.
Après
public void confirmerReservation(Reservation reservation) {
if (reservation.estEnAttente()) {
reservation.confirmer();
emailService.envoyerConfirmation(reservation);
}
}Le renommage a un effet secondaire utile : il oblige à remplacer les statuts numériques 2 et 3, tout aussi opaques que flag, par des méthodes qui portent leur signification. estEnAttente() et confirmer() disent ce qu'ils vérifient et ce qu'ils changent, sans renvoyer le lecteur vers une table de correspondance mentale entre chiffres et statuts.
Une classe qui dit ce qu'elle fait, pas qu'elle fait « des choses »
Manager, Helper, Handler, Service utilisés seuls sont des noms qui décrivent une fonction générique de « faire des trucs avec », jamais une responsabilité précise :
Avant
public class ReservationManager {
public void process(Reservation reservation) { /* ... */ }
public boolean isValid(Reservation reservation) { /* ... */ }
public void notify(Reservation reservation) { /* ... */ }
public void save(Reservation reservation) { /* ... */ }
}Cette classe a de bonnes chances de contenir trois responsabilités qui n'ont jamais été séparées, simplement parce que le nom ne forçait personne à les distinguer.
Après
public class ReservationValidator {
public boolean estValide(Reservation reservation) { /* ... */ }
}
public class ReservationNotifier {
public void notifierConfirmation(Reservation reservation) {/* ... */}
}Renommer et séparer, chaque classe avec une seule responsabilité, ne change rien au fonctionnement du programme. Mais le nom qui ne trouvait plus sa place était déjà le signal que la classe faisait trop de choses à la fois.
Une habitude plus qu'une règle
Bien nommer ne se limite pas à corriger les noms après coup, comme dans les exemples ci-dessus. Quelques réflexes suffisent à en faire une habitude durable :
- Lire le nom à voix haute et se demander s'il répond à la question que poserait quelqu'un qui découvre ce code pour la première fois.
- Se méfier des noms qui vont avec n'importe quoi :
Manager,Helper,Data,Infosont rarement faux, mais ils ne disent jamais rien de spécifique. - Profiter d'une revue de code qui demande « ça veut dire quoi ce nom ? » pour renommer immédiatement, plutôt que d'ajouter un commentaire qui traduit ce que le nom aurait dû dire directement.
- Nommer les booléens comme des questions, et les méthodes comme des verbes qui décrivent l'action réellement effectuée.
Ce que ça vaut vraiment
Bien nommer les choses ne rend pas le code plus rapide à exécuter. Ça le rend plus rapide à comprendre, pour la personne qui le lira dans six mois sans le contexte que l'auteur avait en tête au moment de l'écrire. C'est le même souci que celui qui traverse une bonne revue de code : ce code cessera vite d'appartenir à son auteur, et mérite d'être compris sans devoir rouvrir chaque carton pour vérifier ce qu'il y a dedans.
Retour au déménagement
Étiqueter correctement un carton prend quelques secondes de plus que d'écrire « divers ». Nommer correctement une variable, un booléen ou une méthode prend, de la même façon, quelques secondes de réflexion en plus au moment de l'écrire. Le temps qu'on économise ensuite, à chaque relecture, à chaque personne qui n'a pas besoin d'ouvrir le carton pour savoir ce qu'il contient, ne se compte plus en secondes.
Continuer la lecture