Clean Code
Chapitre 2 / 14 · 15 min de lecture

Des noms qui ont du sens

Choisir des noms qui révèlent l'intention, évitent la désinformation et rendent le code lisible sans le moindre commentaire.

Nous nommons sans cesse. Des variables, des fonctions, des arguments, des classes, des packages, des fichiers, des dossiers. Le nommage est l'activité la plus fréquente du métier — et l'une des plus négligées. Pourtant, choisir un bon nom prend du temps mais en fait gagner bien davantage. C'est un investissement qui se rentabilise à chaque relecture, à chaque modification, à chaque nouvelle personne qui rejoint le projet.

Ce chapitre rassemble les règles que Robert C. Martin propose pour bien nommer. Aucune n'est compliquée. Mais appliquées ensemble, elles transforment un code opaque en un texte qui se lit comme une prose. Le livre illustre tout cela en Java ; nous traduisons ici les exemples en TypeScript idiomatique, dans un univers e-commerce et métier.

Révéler l'intention

La première règle, et la plus importante : le nom d'une variable, d'une fonction ou d'une classe doit répondre aux grandes questions. Pourquoi cette chose existe-t-elle ? Que fait-elle ? Comment l'utilise-t-on ? Si un nom a besoin d'un commentaire pour être compris, c'est que le nom ne révèle pas son intention.

// ❌ Avant : que mesure-t-on ? dans quelle unité ?
let d: number; // temps écoulé en jours

Le nom d n'évoque rien. Il ne dit ni qu'on parle de temps écoulé, ni qu'on compte en jours. Choisissez un nom qui précise ce qui est mesuré et dans quelle unité.

// ✅ Après : le nom porte toute l'information
let elapsedTimeInDays: number;
let daysSinceCreation: number;
let daysSinceModification: number;
let fileAgeInDays: number;

L'effet sur la lisibilité est spectaculaire. Voici un fragment qui « marche » mais dont l'intention est entièrement implicite :

// ❌ Quel est le but de ce code ?
function getThem(theList: number[][]): number[][] {
	const list1: number[][] = [];
	for (const x of theList) {
		if (x[0] === 4) list1.push(x);
	}
	return list1;
}

Le problème n'est pas la complexité — il n'y a ni expression alambiquée ni polymorphisme. Le problème est l'implicité : le contexte n'est pas exprimé dans le code. Pour le comprendre, il faut connaître les réponses à des questions absentes :

  1. Que contient theList ?
  2. Que signifie l'indice 0 d'un élément ?
  3. Que signifie la valeur 4 ?
  4. Comment utiliser la liste retournée ?

Imaginons un jeu de démineur. Le plateau est une liste de cellules ; l'indice 0 porte un statut, et le statut 4 signifie « marquée d'un drapeau ». Rien qu'en nommant ces concepts, le code change de nature :

// ✅ Le même code, rendu explicite par les noms
const STATUS_VALUE = 0;
const FLAGGED = 4;

function getFlaggedCells(gameBoard: number[][]): number[][] {
	const flaggedCells: number[][] = [];
	for (const cell of gameBoard) {
		if (cell[STATUS_VALUE] === FLAGGED) {
			flaggedCells.push(cell);
		}
	}
	return flaggedCells;
}

La simplicité n'a pas bougé : mêmes opérateurs, mêmes constantes, même niveau d'imbrication. Mais le code est devenu lisible. On peut aller plus loin en introduisant une vraie classe Cell qui masque les nombres magiques derrière une méthode isFlagged() :

// ✅ Encore mieux : un type dédié cache les détails
function getFlaggedCells(gameBoard: Cell[]): Cell[] {
	return gameBoard.filter((cell) => cell.isFlagged());
}

Astuce

Si vous devez écrire un commentaire pour expliquer ce qu'une variable représente, demandez-vous d'abord si un meilleur nom ne rendrait pas ce commentaire inutile.

Éviter la désinformation

Nous devons fuir les fausses pistes qui brouillent le sens. Évitez les mots dont le sens établi diffère de votre intention. hp, aix ou sco sont de mauvais noms : ce sont des plateformes Unix. Même si vous codez une hypoténuse et que hp semble une abréviation pratique, c'est de la désinformation.

Surtout, n'appelez pas accountList un regroupement de comptes qui n'est pas une List. Le mot « list » a un sens précis pour un programmeur. Si le conteneur n'est pas réellement une liste, vous induisez le lecteur en erreur. accountGroup, bunchOfAccounts, ou simplement accounts, conviendront mieux — et, comme nous le verrons, mieux vaut de toute façon ne pas encoder le type du conteneur dans le nom.

// ❌ Désinformation : ce n'est pas un Array
const accountList: Set<Account> = new Set();

// ✅ Honnête, et débarrassé du type
const accounts: Set<Account> = new Set();

Méfiez-vous aussi des noms qui ne diffèrent que de façon infime. Combien de temps faut-il pour repérer la différence entre XYZControllerForEfficientHandlingOfStrings et XYZControllerForEfficientStorageOfStrings ? Les deux mots ont des formes terriblement semblables. Orthographier des concepts semblables de façon semblable est une information ; le faire de façon incohérente est de la désinformation. Avec l'autocomplétion, on choisit souvent un nom dans une liste sans lire les commentaires : les différences doivent sauter aux yeux.

Pire encore : les minuscules l et majuscules O, qui se confondent avec les chiffres 1 et 0.

// ❌ Cauchemar de relecture
let a = l;
if (O === l) a = O1;

Faire des distinctions qui ont un sens

On se crée des problèmes quand on écrit du code uniquement pour satisfaire le compilateur. Comme on ne peut pas donner deux fois le même nom dans un même scope, on est tenté de modifier l'un de façon arbitraire. Si deux noms doivent être différents, c'est qu'ils doivent désigner des choses différentes.

Les suites numériques (a1, a2, … aN) sont l'exact opposé d'un nommage intentionnel : elles ne sont pas désinformatives, juste totalement vides de sens.

// ❌ a1, a2 : aucune intention
function copyChars(a1: string[], a2: string[]): void {
	for (let i = 0; i < a1.length; i++) a2[i] = a1[i];
}

// ✅ source / destination racontent l'histoire
function copyChars(source: string[], destination: string[]): void {
	for (let i = 0; i < source.length; i++) {
		destination[i] = source[i];
	}
}

Autre piège : les mots de bruit (noise words). Si vous avez une classe Product, en créer une ProductInfo ou ProductData rend les noms différents sans rien rendre différent. Info et Data sont des mots vides, comme « le » ou « un ». Le mot variable ne devrait jamais figurer dans un nom de variable, ni table dans un nom de table. En quoi NameString vaut-il mieux que Name ? Un nom serait-il un jour un nombre flottant ?

Voici l'exemple emblématique du livre, une vraie API trouvée dans la nature :

// ❌ Comment savoir laquelle appeler ?
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();
À éviterPourquoiPréférer
moneyAmount vs moneyaucune distinction réelleun seul des deux
customerInfo vs customerInfo est du bruitcustomer
accountData vs accountData est du bruitaccount
theMessage vs messagepréfixe gratuitmessage

Des noms prononçables

Notre cerveau est câblé pour le langage parlé. Autant en profiter : rendez vos noms prononçables. Si vous ne pouvez pas prononcer un nom, vous ne pouvez pas en discuter sans avoir l'air ridicule — or programmer est une activité sociale.

// ❌ Imprononçable, donc indiscutable
class DtaRcrd102 {
	private genymdhms: Date;
	private modymdhms: Date;
	private readonly pszqint = "102";
}

Que voulez-vous dire à l'oral à propos de genymdhms ? Comparez avec une version où l'on peut tenir une vraie conversation :

// ✅ « le timestamp de génération est à demain ! »
class Customer {
	private generationTimestamp: Date;
	private modificationTimestamp: Date;
	private readonly recordId = "102";
}

Des noms cherchables

Les noms d'une seule lettre et les constantes numériques partagent un défaut : on ne les retrouve pas facilement avec une recherche. On peut « grepper » MAX_CLASSES_PER_STUDENT sans peine, mais chercher 7 remonte des résultats partout — dans des noms de fichiers, d'autres constantes, des expressions sans rapport. De même, e est un piètre choix : c'est la lettre la plus fréquente en anglais.

La longueur d'un nom devrait être proportionnelle à la taille de son scope.

Les noms d'une lettre ne se justifient que comme variables locales dans des fonctions courtes. Dès qu'une valeur peut apparaître à plusieurs endroits, donnez-lui un nom cherchable.

// ❌ Que valent 34, 4, 5 ? Introuvables et opaques
for (let j = 0; j < 34; j++) {
	s += (t[j] * 4) / 5;
}
// ✅ Des constantes nommées, donc cherchables
const REAL_DAYS_PER_IDEAL_DAY = 4;
const WORK_DAYS_PER_WEEK = 5;
const NUMBER_OF_TASKS = taskEstimates.length;

let sum = 0;
for (let task = 0; task < NUMBER_OF_TASKS; task++) {
	const realTaskDays = taskEstimates[task] * REAL_DAYS_PER_IDEAL_DAY;
	const realTaskWeeks = realTaskDays / WORK_DAYS_PER_WEEK;
	sum += realTaskWeeks;
}

La version intentionnelle est plus longue, mais songez à la facilité avec laquelle on retrouve WORK_DAYS_PER_WEEK, contre l'épreuve de débusquer tous les 5 du code pour ne garder que les bons.

Éviter les encodages

Nous avons déjà bien assez d'encodages à gérer sans en ajouter. Encoder le type ou le scope dans un nom ne fait qu'imposer un déchiffrage supplémentaire — un « langage » de plus à apprendre, des noms imprononçables et faciles à mal taper.

La notation hongroise

À l'époque où les compilateurs ne vérifiaient pas les types (l'API Windows en C, par exemple), les développeurs encodaient le type dans le nom comme béquille. Les langages modernes ont des systèmes de types riches et vérifiés par l'outillage : la notation hongroise n'est plus qu'un obstacle. Elle complique le renommage et le changement de type, et risque même d'induire le lecteur en erreur.

// ❌ Le nom ment quand le type change
let phoneString: PhoneNumber; // ce n'est plus une string !

// ✅ Le type est dans la déclaration, pas dans le nom
let phone: PhoneNumber;

Les préfixes de membres

Inutile aussi de préfixer les attributs avec m_. Vos classes et fonctions devraient être assez petites pour s'en passer, et l'éditeur colore déjà les membres.

// ❌ Préfixe m_ devenu un bruit qu'on apprend à ignorer
class Part {
	private m_dsc: string; // description textuelle
	setName(name: string): void {
		this.m_dsc = name;
	}
}

// ✅ Net, et le nom est mieux choisi
class Part {
	private description: string;
	setDescription(description: string): void {
		this.description = description;
	}
}

Interfaces et implémentations

Cas particulier : faut-il nommer une interface IShapeFactory et son implémentation ShapeFactory ? Martin préfère laisser l'interface sans ornement. Le I initial est au mieux une distraction, au pire un excès d'information : l'utilisateur n'a pas besoin de savoir qu'on lui tend une interface. S'il faut absolument encoder quelque chose, encodez l'implémentation (ShapeFactoryImp).

Note

Le préfixe I reste une convention répandue dans l'écosystème TypeScript et .NET. La position du livre est tranchée ; à vous d'arbitrer selon les usages de votre équipe, sans jamais perdre de vue que c'est le lecteur qu'on cherche à servir.

Éviter la traduction mentale

Le lecteur ne devrait pas avoir à traduire mentalement vos noms en d'autres noms qu'il connaît déjà. Un compteur de boucle peut s'appeler i, j ou k (jamais l !) dans un scope minuscule — c'est une tradition. Mais ailleurs, un nom d'une lettre n'est qu'un substitut que le lecteur doit décoder. Il n'y a pas de pire raison d'appeler une variable c que « parce que a et b étaient déjà pris ».

Les programmeurs sont intelligents, et l'intelligence aime parfois s'exhiber par des prouesses de mémoire. Si vous retenez fidèlement que r est la version minuscule de l'URL sans son hôte ni son schéma, vous êtes sûrement très malin.

La différence entre un programmeur malin et un programmeur professionnel, c'est que le professionnel sait que la clarté est reine.

Noms de classes et noms de méthodes

Deux règles symétriques et simples :

  • Une classe est un substantif (ou un groupe nominal) : Customer, WikiPage, Account, AddressParser. Évitez Manager, Processor, Data, Info. Un nom de classe n'est jamais un verbe.
  • Une méthode est un verbe (ou un groupe verbal) : postPayment, deletePage, save. Les accesseurs, mutateurs et prédicats se préfixent par get, set et is.
const name = employee.getName();
customer.setName("mike");
if (paycheck.isPosted()) {
	/* ... */
}

Quand un constructeur a plusieurs formes, préférez des fabriques statiques dont le nom décrit les arguments :

// ✅ Le nom dit ce que construit la fabrique
const fulcrum = Complex.fromRealNumber(23.0);

// ❌ Le constructeur seul n'explique pas l'argument
const fulcrum2 = new Complex(23.0);

Ne pas être « mignon »

Un nom trop malin n'est mémorable que pour ceux qui partagent l'humour de son auteur, et seulement tant qu'ils se souviennent de la blague. Devinera-t-on ce que fait une fonction nommée holyHandGrenade ? C'est amusant, mais deleteItems serait sans doute plus utile. Préférez la clarté à la valeur de divertissement.

// ❌ Argot et private jokes
function whack(): void {} // veut dire « kill »
function eatMyShorts(): void {} // veut dire « abort »

// ✅ Dites ce que vous voulez dire
function kill(): void {}
function abort(): void {}

Dites ce que vous voulez dire. Voulez dire ce que vous dites.

Un mot par concept

Choisissez un seul mot pour un concept abstrait et tenez-vous-y. Avoir fetch, retrieve et get comme méthodes équivalentes sur des classes différentes prête à confusion : comment se souvenir laquelle va avec quoi ? De même, un controller, un manager et un driver dans la même base de code sèment le doute. Quelle est la différence essentielle entre un DeviceManager et un ProtocolController ? Le nom laisse attendre deux choses de nature différente.

// ❌ Trois mots pour un même concept
class UserRepository { fetchById(id: string) {} }
class OrderRepository { getById(id: string) {} }
class CartRepository { retrieveById(id: string) {} }

// ✅ Un lexique cohérent
class UserRepository { getById(id: string) {} }
class OrderRepository { getById(id: string) {} }
class CartRepository { getById(id: string) {} }

Un lexique cohérent est un cadeau précieux pour quiconque utilise votre code.

Ne pas faire de jeu de mots

Le revers de la règle précédente : n'utilisez pas le même mot pour deux usages différents. Si vous suivez « un mot par concept », vous aurez beaucoup de classes avec une méthode add. Tant que ces add ont la même sémantique (créer une nouvelle valeur en additionnant ou concaténant deux valeurs), tout va bien.

Mais le jour où vous écrivez une méthode qui se contente de placer son unique paramètre dans une collection, l'appeler add « par cohérence » serait un jeu de mots : la sémantique est différente.

// ❌ « add » employé pour deux sens distincts
class Money {
	add(other: Money): Money {
		return this; // additionne deux montants
	}
}
class Cart {
	add(item: Item): void {
		/* place un élément dans la collection */
	}
}

// ✅ Un nom honnête pour chaque sémantique
class Cart {
	append(item: Item): void {
		/* ... */
	}
}

Attention

Notre but, en tant qu'auteurs, est de rendre le code aussi facile à comprendre que possible. On veut un code qui se survole, pas un code qui s'étudie. Un jeu de mots oblige le lecteur à faire l'étude.

Domaine de la solution, domaine du problème

Ceux qui liront votre code sont des programmeurs : utilisez sans crainte le vocabulaire de la solution — termes d'informatique, noms d'algorithmes, noms de patrons de conception, termes mathématiques. AccountVisitor parle immédiatement à qui connaît le patron Visitor ; tout le monde sait ce qu'est une JobQueue. Inutile de puiser tous les noms dans le métier au risque que vos collègues doivent courir voir le client pour chaque nom.

Quand il n'existe pas de terme informatique, recourez au vocabulaire du domaine métier. Au moins, la personne qui maintiendra le code pourra interroger un expert. Savoir séparer concepts de la solution et concepts du problème fait partie du travail d'un bon concepteur : le code qui touche au métier doit porter des noms métier.

Ajouter un contexte utile

Peu de noms se suffisent à eux-mêmes. La plupart ont besoin d'un contexte, fourni par une classe, une fonction ou un module bien nommés. Imaginez les variables firstName, lastName, street, houseNumber, city, state, zipcode : ensemble, elles forment clairement une adresse. Mais si vous croisez state seule dans une méthode, devinerez-vous qu'elle fait partie d'une adresse ?

En dernier recours, on peut préfixer (addrState). Mais la meilleure solution est de créer une classe Address : le contexte devient explicite, et même le compilateur sait que ces champs appartiennent à un concept plus vaste.

Considérez cette méthode où le contexte des variables est opaque :

// ❌ number, verb, pluralModifier : contexte à deviner
function printGuessStatistics(candidate: string, count: number): void {
	let number: string;
	let verb: string;
	let pluralModifier: string;
	if (count === 0) {
		number = "no";
		verb = "are";
		pluralModifier = "s";
	} else if (count === 1) {
		number = "1";
		verb = "is";
		pluralModifier = "";
	} else {
		number = String(count);
		verb = "are";
		pluralModifier = "s";
	}
	print(`There ${verb} ${number} ${candidate}${pluralModifier}`);
}

En extrayant une classe GuessStatisticsMessage, les trois variables deviennent des champs porteurs d'un contexte clair, ce qui permet aussi de découper l'algorithme en petites fonctions :

// ✅ La classe fournit le contexte ; chaque cas est isolé
class GuessStatisticsMessage {
	private number = "";
	private verb = "";
	private pluralModifier = "";

	make(candidate: string, count: number): string {
		this.createPluralDependentMessageParts(count);
		return `There ${this.verb} ${this.number} ` +
			`${candidate}${this.pluralModifier}`;
	}

	private createPluralDependentMessageParts(count: number): void {
		if (count === 0) this.thereAreNoLetters();
		else if (count === 1) this.thereIsOneLetter();
		else this.thereAreManyLetters(count);
	}

	private thereAreNoLetters(): void {
		this.number = "no";
		this.verb = "are";
		this.pluralModifier = "s";
	}

	private thereIsOneLetter(): void {
		this.number = "1";
		this.verb = "is";
		this.pluralModifier = "";
	}

	private thereAreManyLetters(count: number): void {
		this.number = String(count);
		this.verb = "are";
		this.pluralModifier = "s";
	}
}

Ne pas ajouter de contexte gratuit

Le contexte est utile, mais il a une limite. Dans une application « Gas Station Deluxe », préfixer chaque classe par GSD est une mauvaise idée : tapez G dans l'éditeur et l'autocomplétion vous noie sous toutes les classes du système. Vous travaillez contre vos outils.

Pire, si vous nommez GSDAccountAddress une classe d'adresse postale du module comptable, que faites-vous le jour où vous avez besoin d'une adresse pour le module de contact client ? Dix caractères sur dix-sept sont redondants.

// ❌ Préfixe gratuit, non réutilisable
class GSDAccountAddress { /* ... */ }

// ✅ Un nom court et réutilisable ; on précise si besoin
class Address { /* ... */ }
class PostalAddress { /* ... */ } // si l'on doit distinguer
class Uri { /* ... */ } // adresse web

Les noms courts valent mieux que les longs, tant qu'ils restent clairs. N'ajoutez pas plus de contexte que nécessaire. accountAddress et customerAddress sont de bons noms pour des instances de Address, mais de mauvais noms de classes.

Astuce

Le plus dur, dans le nommage, c'est qu'il exige de bonnes qualités descriptives et une culture partagée. N'ayez pas peur de renommer : les outils de refactoring rendent l'opération sûre, et vos relecteurs vous en seront reconnaissants.

À retenir

  • Un nom doit révéler son intention : pourquoi la chose existe, ce qu'elle fait, comment on l'utilise. S'il faut un commentaire, le nom a échoué.
  • Fuyez la désinformation : pas de accountList qui n'est pas une liste, pas de noms qui ne diffèrent que d'un caractère, pas de l/O qui imitent 1/0.
  • Distinguez ce qui doit l'être : bannissez les suites a1, a2 et les mots de bruit (Info, Data, Object). Préférez des noms prononçables et cherchables.
  • Pas d'encodage : ni notation hongroise, ni préfixe m_, ni I devant les interfaces — les types modernes et l'éditeur s'en chargent.
  • Classes = substantifs, méthodes = verbes. Un mot par concept (get partout, pas fetch/retrieve), et jamais le même mot pour deux sens.
  • Dosez le contexte : enfermez les variables liées dans une classe bien nommée (Address), mais n'ajoutez pas de préfixe gratuit. Le nom le plus court et le plus clair gagne.