Clean Code en TypeScript: principios y ejemplos prácticos
El código limpio (Clean Code) no es solo un término de moda, sino una habilidad fundamental del desarrollador profesional. En el contexto de TypeScript, que añade tipado estático al dinámico mundo de JavaScript, los principios del código limpio se vuelven aún más importantes. TypeScript nos brinda herramientas poderosas para autodocumentar el código, pero sin un enfoque adecuado pueden convertirse en un caos.
En este artículo analizaremos los principios clave de Clean Code aplicados a TypeScript y veremos ejemplos prácticos que te ayudarán a escribir código más legible, mantenible y confiable.
1. Nomenclatura: hable el lenguaje del negocio
El primer y más obvio principio de Clean Code es la nomenclatura correcta. Los nombres de variables, funciones, clases e interfaces deben reflejar su esencia y la intención del desarrollador. TypeScript, con su tipado estricto, permite que los nombres sean aún más informativos.
Ejemplo incorrecto:
// ¿Qué hace esta función? ¿Cuánto tiempo se necesita para entenderlo?function d(a: number, b: number): number { return a * b - (a + b);}
// ¿Y qué es esta interfaz?interface I { n: string; a: number; e: string;}Ejemplo correcto:
// Función para calcular descuento considerando un umbralfunction calculateDiscountedPrice(price: number, discountPercent: number): number { const discountAmount = price * (discountPercent / 100); return price - discountAmount;}
// Interfaz de usuario con campos explícitosinterface UserProfile { name: string; age: number; email: string;}Reglas para TypeScript:
- Interfaces — use sustantivos sin el prefijo
I(por ejemplo,User, noIUser). TypeScript moderno y ESLint recomiendan este enfoque. - Tipos — para uniones complejas use el operador
|y nombres significativos:StatusType,ApiResponse. - Variables booleanas — comience con
is,has,should:isActive,hasPermission. - Funciones — use verbos:
getUserData,validateEmail,processOrder.
2. SOLID en TypeScript: aplicación práctica
Los principios SOLID (Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) se adaptan perfectamente a TypeScript gracias al soporte de interfaces, clases abstractas y genéricos.
Principio de responsabilidad única (SRP): una clase debe tener solo una razón para cambiar.
// Violación de SRP: la clase hace demasiadoclass UserService { createUser(data: any) { /* ... */ } sendEmail(email: string) { /* ... */ } generateReport() { /* ... */ }}
// Enfoque correcto: separación de responsabilidadesclass UserRepository { save(user: User): void { /* ... */ }}
class EmailService { sendWelcomeEmail(email: string): void { /* ... */ }}
class ReportGenerator { generateUserReport(users: User[]): Report { /* ... */ }}Principio de sustitución de Liskov (LSP): las subclases deben ser intercambiables con la clase base. En TypeScript esto es especialmente relevante al trabajar con herencia.
// Violación de LSP: la subclase restringe el contratoclass Bird { fly(): void { console.log('Flying'); }}
class Penguin extends Bird { fly(): void { throw new Error('Penguins cannot fly!'); }}
// Correcto: extraemos una interfazinterface Flyable { fly(): void;}
class Bird implements Flyable { fly(): void { console.log('Flying'); }}
class Penguin { s