Pourquoi le code propre en TypeScript n'est pas un luxe, mais une nécessité
Chaque développeur a déjà vécu cette situation : vous ouvrez un projet écrit il y a quelques mois et vous ne comprenez pas ce qui s'y passe. Les variables s'appellent a, b, data, les fonctions font 200 lignes, et les types sont placés de temps en temps. Cela vous dit quelque chose ?
TypeScript nous offre un outil puissant : le typage statique. Mais à lui seul, il ne rend pas le code propre. On peut écrire du code horrible aussi bien en TypeScript que dans n'importe quel autre langage. Le code propre est une discipline, un style et un respect de soi-même et de ses collègues.
Dans cet article, nous allons voir comment écrire du code propre en TypeScript, qui sera facile à lire, à maintenir et à faire évoluer. Vous découvrirez les principes SOLID, le typage strict, le nommage, le travail avec les interfaces et bien plus encore. C'est parti !
1. Le nommage : le visage de votre code
Un bon nommage des variables, fonctions et classes représente 50% du succès. Un bon nom doit répondre aux questions : "Qu'est-ce que c'est ?" ou "Que fait cette fonction ?".
Variables et constantes
Utilisez des noms et le camelCase. Évitez les noms d'une seule lettre (sauf dans les boucles).
// Mauvaisconst d = new Date();const u = users.find(u => u.id === 5);
// Bonconst currentDate = new Date();const activeUser = users.find(user => user.id === 5);Fonctions
Les noms de fonctions doivent commencer par un verbe. Cela indique immédiatement l'action effectuée.
// Mauvaisfunction data(id: number) { ... }
// Bonfunction fetchUserData(id: number): Promise<UserData> { ... }Classes et interfaces
Les classes sont des noms en PascalCase. Les interfaces aussi, mais souvent avec le préfixe I (bien que la communauté soit divisée). Il est préférable de donner simplement un nom clair sans préfixe.
// Mauvaisinterface IUser { name: string;}
// Boninterface User { name: string; email: string;}2. Le typage strict : votre meilleur ami
TypeScript est conçu pour que les types travaillent pour vous. N'utilisez pas any sans nécessité absolue. Chaque fois que vous écrivez any, vous désactivez la protection du compilateur.
Évitez any
// Mauvaisfunction process(data: any) { return data.name;}
// Boninterface Item { name: string; price: number;}
function process(data: Item): string { return data.name;}Utilisez les types Union et Intersection
Lorsqu'une variable peut accepter plusieurs types, utilisez Union (|). Pour la combinaison, utilisez Intersection (&).
type Status = 'active' | 'inactive' | 'pending';type AdminUser = User & { role: 'admin' };Les génériques : fonctions universelles
Les génériques permettent d'écrire du code flexible sans perdre le typage.
function firstElement<T>(arr: T[]): T | undefined { return arr[0];}
const num = firstElement([1, 2, 3]); // numberconst str = firstElement(['a', 'b']); // string3. SOLID : des principes pour les adultes
Les principes SOLID aident à construire une architecture flexible et maintenable. Examinons chacun d'eux avec des exemples en TypeScript.
Responsabilité unique (SRP)
Une classe ou une fonction ne doit avoir qu'une seule raison de changer.
// Mauvais : la classe fait toutclass UserService { createUser() { ... } sendEmail() { ... } generateReport() { ... }}
// Bon : on sépare les responsabilitésclass UserCreator { ... }class EmailService { ... }class ReportGenerator { ... }Ouvert/Fermé (OCP)
Les classes sont ouvertes à l'extension, mais fermées à la modification. Utilisez des interfaces et l'héritage.
Substitution de Liskov (LSP)
Les sous-classes doivent pouvoir remplacer la classe de base sans modifier le comportement du programme.
Ségrégation des interfaces (ISP)
Les interfaces doivent être spécifiques plutôt que générales. Il vaut mieux plusieurs interfaces spécialisées qu'une seule interface monolithique.
Inversion des dépendances (DIP)
Dépendre des abstractions, pas des implémentations concrètes. Utilisez l'injection de dépendances.
4. Motifs de conception utiles en TypeScript
Les motifs de conception (patterns) sont des solutions éprouvées à des problèmes récurrents. En voici quelques-uns particulièrement utiles en TypeScript.
Pattern Singleton
Garantit qu'une classe n'a qu'une seule instance.
class Database { private static instance: Database; private constructor() {}
static getInstance(): Database { if (!Database.instance) { Database.instance = new Database(); } return Database.instance; }}Pattern Factory
Crée des objets sans spécifier la classe exacte.
interface Animal { speak(): void;}
class Dog implements Animal { speak() { console.log('Woof!'); }}
class Cat implements Animal { speak() { console.log('Meow!'); }}
class AnimalFactory { static create(type: 'dog' | 'cat'): Animal { if (type === 'dog') return new Dog(); return new Cat(); }}Pattern Decorator
Ajoute dynamiquement des fonctionnalités à un objet.
function log(target: any, key: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Appel de ${key} avec`, args); return original.apply(this, args); };}
class Calculator { @log add(a: number, b: number): number { return a + b; }}5. Travailler avec les promesses et l'asynchrone
Le code asynchrone en TypeScript peut devenir un véritable désordre. Utilisez async/await et gérez correctement les erreurs.
Évitez les promesses chaînées
// Mauvaisfunction fetchData() { return fetch('/api/data') .then(response => response.json()) .then(data => process(data)) .catch(error => console.error(error));}
// Bonasync function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return process(data); } catch (error) { console.error(error); }}Gestion des erreurs typée
Créez des classes d'erreur personnalisées pour un meilleur contrôle.
class ApiError extends Error { constructor(public statusCode: number, message: string) { super(message); }}
async function getUser(id: number): Promise<User> { const response = await fetch(`/api/users/${id}`); if (!response.ok) { throw new ApiError(response.status, 'Erreur lors de la récupération de l\\'utilisateur'); } return response.json();}6. Tests : votre filet de sécurité
Le code propre doit être testé. TypeScript s'intègre parfaitement avec Jest, Vitest et d'autres frameworks de test.
Exemple de test unitaire
// math.tsexport function add(a: number, b: number): number { return a + b;}
// math.test.tsimport { add } from './math';
describe('add', () => { it('devrait additionner deux nombres', () => { expect(add(2, 3)).toBe(5); });
it('devrait gérer les nombres négatifs', () => { expect(add(-1, 1)).toBe(0); });});Conclusion
Écrire du code propre en TypeScript n'est pas seulement une question de syntaxe. C'est une approche qui inclut le nommage, le typage, l'architecture, les motifs et les tests. Commencez par appliquer ces règles une par une, et vous verrez comment la qualité de votre code s'améliore.
N'oubliez pas : le code propre n'est pas un luxe, c'est un investissement dans l'avenir de votre projet. Bonne chance dans votre développement !