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 consten 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.tsa 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 constdonne 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
- TypeScript Handbook, "const assertions" - https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions
- Martin Fowler, "Feature Toggles" - https://martinfowler.com/articles/feature-toggles.html
- Zod documentation - https://zod.dev/