Clean code et refactoring - 18 - Constantes, configuration et magic numbers

Les magic numbers et les valeurs en dur rendent le code fragile. Voici comment extraire, nommer et organiser tes constantes.

  1. 01 Clean code et refactoring - 00 - Pourquoi le clean code est un investissement, pas un luxe
  2. 02 Clean code et refactoring - 01 - Nommage : la competence la plus sous-estimee
  3. 03 Clean code et refactoring - 02 - Fonctions : courtes, claires, responsables
  4. 04 Clean code et refactoring - 03 - Conditions et lisibilité : sortir de la pyramide
  5. 05 Clean code et refactoring - 04 - Commentaires et documentation : quand le code ne suffit pas
  6. 06 Clean code et refactoring - 05 - Immutabilite et effets de bord : moins de surprises, moins de bugs
  7. 07 Clean code et refactoring - 06 - Gestion des erreurs propre : fail fast, fail loud
  8. 08 Clean code et refactoring - 07 - Programmation defensive vs offensive : valider aux frontieres, faire confiance a l'intérieur
  9. 09 Clean code et refactoring - 08 - SOLID en pratique avec TypeScript
  10. 10 Clean code et refactoring - 09 - DRY, KISS, YAGNI
  11. 11 Clean code et refactoring - 10 - Couplage et cohesion
  12. 12 Clean code et refactoring - 11 - Complexite cyclomatique
  13. 13 Clean code et refactoring - 12 - Abstractions prematurees vs tardives
  14. 14 Clean code et refactoring - 13 - Code smells
  15. 15 Clean code et refactoring - 14 - Techniques de refactoring
  16. 16 Clean code et refactoring - 15 - Refactoring legacy sans tout casser
  17. 17 Clean code et refactoring - 16 - Tests comme filet de sécurité pour le refactoring
  18. 18 Clean code et refactoring - 17 - Structurer un projet — feature-based vs layer-based
  19. 19 Clean code et refactoring - 18 - Constantes, configuration et magic numbers
  20. 20 Clean code et refactoring - 19 - Linting et formatting — ESLint, Biome, automatiser la qualité
  21. 21 Clean code et refactoring - 20 - Conventions d'équipe et ADR
  22. 22 Clean code et refactoring - 21 - Dette technique — quand elle est acceptable, quand elle tue le projet
  23. 23 Clean code et refactoring - 22 - Code review — donner et recevoir du feedback
  24. 24 Clean code et refactoring - 23 - Glossaire — tous les termes de la serie

18 - Constantes, configuration et magic numbers

Ce que tu vas apprendre

  • Ce qu'est un magic number (et un magic string) et pourquoi c'est un problème
  • Comment extraire et nommer des constantes
  • Les conventions de nommage : SCREAMING_SNAKE vs camelCase
  • Fichiers de config, variables d'environnement et feature flags
  • Le pattern as const en TypeScript
  • Centraliser ou coloquer les constantes

Prerequisites

Article précédent : 17 - Structurer un projet


J'ai passe une heure la semaine dernière a comprendre pourquoi un calcul de TVA echouait. Le code contenait price * 1.196. Pas de commentaire, pas de constante. Apres investigation, l'ancien taux de TVA français etait 19.6%. Quelqu'un avait change le taux a 1.20 dans trois fichiers sur cinq. Les deux autres utilisaient toujours l'ancien taux. Le meme nombre magique, duplique et desynchronise.

Les magic numbers tuent la maintenabilité a petit feu.

Les magic numbers : le problème

Un magic number est une valeur numérique qui apparaît directement dans le code sans explication. Un magic string, c'est la meme chose avec du texte.

typescript// Magic numbers et strings partout
if (user.age >= 18) { ... }
if (password.length < 8) { ... }
if (retryCount > 3) { ... }
if (status === "PNDG") { ... }
setTimeout(callback, 86400000);

Les problèmes sont multiples. Tu ne sais pas ce que 18 represente sans contexte. Si la regle change (majorite a 16 ans dans un autre pays), tu dois chercher tous les 18 dans le code. Certains 18 n'ont rien a voir avec l'age legal — bonne chance pour les distinguer. Et 86400000, c'est quoi ? Des millisecondes. Combien de temps ? 24 heures. Tu as du faire le calcul de tête.

Extraire des constantes

La solution est directe : donne un nom a chaque valeur.

typescriptconst LEGAL_AGE = 18;
const MIN_PASSWORD_LENGTH = 8;
const MAX_RETRY_COUNT = 3;
const ORDER_STATUS_PENDING = "PNDG";
const ONE_DAY_MS = 24 * 60 * 60 * 1000;

if (user.age >= LEGAL_AGE) { ... }
if (password.length < MIN_PASSWORD_LENGTH) { ... }
if (retryCount > MAX_RETRY_COUNT) { ... }
if (status === ORDER_STATUS_PENDING) { ... }
setTimeout(callback, ONE_DAY_MS);

Le code se lit maintenant comme une phrase. Tu sais immédiatement ce que chaque valeur represente. Si la regle change, tu modifies un seul endroit. La recherche dans le code base est triviale : LEGAL_AGE est unique, 18 ne l'est pas.

Conventions de nommage

En TypeScript et JavaScript, deux conventions coexistent :

SCREAMING_SNAKE_CASE pour les constantes qui representent des valeurs fixes connues a la compilation :

typescriptconst MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 Mo
const API_BASE_URL = "https://api.example.com";
const DEFAULT_PAGE_SIZE = 20;

camelCase pour les constantes qui sont simplement des variables non reassignees :

typescriptconst userService = new UserService();
const logger = createLogger("app");
const router = express.Router();

La distinction est utile : SCREAMING_SNAKE signale "c'est une configuration, une valeur métier, un seuil". camelCase signale "c'est une instance, un objet complexe". Ce n'est pas une regle universelle mais c'est la convention dominante dans l'ecosysteme TypeScript.

`as const` : les constantes typees en TypeScript

TypeScript offre as const pour créer des types literals à partir de valeurs :

typescript// Sans as const : type string[]
const ROLES = ["admin", "editor", "viewer"];

// Avec as const : type readonly ["admin", "editor", "viewer"]
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer"

// Marche aussi avec les objets
const HTTP_STATUS = {
  OK: 200,
  NOT_FOUND: 404,
  INTERNAL_ERROR: 500,
} as const;

type StatusCode = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS]; // 200 | 404 | 500

as const elimine les magic numbers tout en gardant le type system au courant des valeurs possibles. C'est un outil que j'utilise quotidiennement. On en parlait deja dans l'article sur le nommage — donner un nom a une valeur, c'est fondamental.

Config files vs variables d'environnement

Les constantes métier vivent dans le code. Les constantes d'infrastructure vivent dans la configuration. La frontiere entre les deux est parfois floue, mais la regle de base est simple : si la valeur change entre les environnements (dev, staging, prod), c'est de la config.

typescript// config.ts - centralise la configuration d'infrastructure
import { z } from "zod";

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  PORT: z.coerce.number().default(3000),
  LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
  SMTP_HOST: z.string(),
  SMTP_PORT: z.coerce.number().default(587),
});

export const config = envSchema.parse(process.env);

Valider les variables d'environnement au démarrage de l'application avec Zod (ou une autre librairie de validation) est une pratique que je recommande fortement. Un crash au démarrage avec un message clair vaut mieux qu'un crash en production a 3h du matin parce que SMTP_HOST est undefined.

Feature flags : constantes dynamiques

Les feature flags sont des constantes qui changent a l'exécution. Elles permettent d'activer ou désactiver des fonctionnalités sans déployer du code. Pour en savoir plus sur la gestion des configurations en production, tu peux consulter paltemps.fr.

typescript// Feature flags simples via config
const FEATURES = {
  NEW_CHECKOUT: process.env.FEATURE_NEW_CHECKOUT === "true",
  DARK_MODE: process.env.FEATURE_DARK_MODE === "true",
  AI_SUGGESTIONS: process.env.FEATURE_AI_SUGGESTIONS === "true",
} as const;

// Utilisation
if (FEATURES.NEW_CHECKOUT) {
  return <NewCheckoutFlow />;
}
return <LegacyCheckout />;

Pour un projet simple, des variables d'environnement suffisent. Pour des besoins plus avances (feature flags par utilisateur, pourcentage de rollout), des outils comme LaunchDarkly ou Unleash sont plus adaptes.

Le pattern config object

Quand une fonction accepte plusieurs paramètres de configuration, regroupe-les dans un objet. Ca évité les magic numbers dans les appels de fonction.

typescript// Avant : magic numbers dans l'appel
const limiter = createRateLimiter(100, 60000, 5, true);

// Apres : config object
const RATE_LIMIT_CONFIG = {
  maxRequests: 100,
  windowMs: 60 * 1000,
  maxRetries: 5,
  blockOnExceed: true,
} as const;

const limiter = createRateLimiter(RATE_LIMIT_CONFIG);

Le premier appel est incomprehensible sans lire la signature de la fonction. Le second se lit tout seul. C'est le meme principe que l'extraction de constantes, applique aux paramètres de fonction. L'article 02 - Fonctions détaillé ce pattern.

Centraliser ou coloquer ?

La question revient souvent : est-ce qu'on met toutes les constantes dans un fichier constants.ts a la racine, ou on les place a cote du code qui les utilise ?

La réponse depend de la portee de la constante :

  • Utilisee dans une seule feature : coloquer dans le dossier de la feature
  • Partagee entre deux ou trois features : fichier shared/constants.ts
  • Configuration globale : fichier config.ts a la racine

Le fichier constants.ts monolithique de 500 lignes est un anti-pattern. Il devient un point de contention en équipe (merge conflicts permanents) et un fourre-tout ou on met tout ce qu'on ne sait pas ranger. Mieux vaut plusieurs petits fichiers bien nommes.

Résumé

  • Les magic numbers rendent le code incomprehensible et fragile
  • Extraire une constante nommee coûte 10 secondes et economise des heures de debug
  • SCREAMING_SNAKE_CASE pour les valeurs fixes, camelCase pour les instances
  • as const donne a TypeScript la connaissance des valeurs exactes
  • Valide tes variables d'environnement au démarrage avec un schema
  • Les feature flags sont des constantes dynamiques qui evitent les déploiements
  • Coloquer les constantes avec le code qui les utilise sauf si elles sont partagees

Article précédent : 17 - Structurer un projet

Article suivant : 19 - Linting et formatting

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.