Este resumen es una referencia exhaustiva de prácticas, principios, "code smells" y refactorizaciones, con ejemplos prácticos en TypeScript y recomendaciones para aplicar día a día.
- ¿Qué es Clean Code?
- Regla del Boy Scout
- Nombres: la base del código legible
- Funciones: diseño y patrones
- Parámetros y tipos retornados
- Clases, objetos y SRP
- Principios SOLID
- Manejo de errores
- Tests: F.I.R.S.T.
- Code smells importantes
- Arquitectura y módulos
- Prácticas y herramientas (lint, format, CI)
- TypeScript: buenas prácticas
- Hoja de referencia rápida
- Cómo aplicarlo día a día
- Ejemplo práctico: De sucio a limpio
- Recomendaciones
Código limpio = código que:
- Se lee fácil y transmite intención.
- Es simple, directo y con pocas sorpresas.
- Facilita cambios (extensión y corrección) con bajo coste.
- Está bien probado y bien organizado en módulos/responsabilidades.
La ambición no es escribir código perfecto, sino escribir código limpio constantemente.
“Deja el campamento más limpio de lo que lo encontraste.”
Aplicación práctica:
- Cada vez que tocas un archivo, arreglo pequeño: Renombrar variable, extraer una función, añadir test, quitar duplicación.
- No necesitas refactorizar todo el módulo en una PR enorme; "Pequeñas mejoras incrementales son la vía".
Reglas:
- Revelan intención: Nombres deben responder “qué” y “por qué”.
- Evita abreviaturas crípticas.
- Prefiere nombres pronunciables y consistentes.
- Para booleanos usa prefijos claros (
is,has,can,should). - Nombres de funciones deben ser verbos; nombres de tipos/clases sustantivos.
// Malo
const d = 86400;
function get(u) { /* ... */ }
// Bueno
const SECONDS_IN_A_DAY = 86400;
function getActiveUsers(): User[] { /* ... */ }- Una sola responsabilidad por función.
- Corto (ideal < 20 líneas).
- Guard clauses para reducir anidamiento.
- Separar comandos (mutan estado) y consultas (devuelven datos).
// Malo
function sendWelcome(user?: User) {
if (user) {
if (!user.isBanned) {
sendEmail(user.email, "Welcome!");
}
}
}
// Bueno
function sendWelcome(user?: User) {
if (!user) return;
if (user.isBanned) return;
sendEmail(user.email, "Welcome!");
}- 0–2 parámetros es ideal (usar objeto cuando hay más).
- Evita flags booleanos que cambian comportamiento.
interface CreateUserProps { name: string; age: number; isAdmin?: boolean; }
function createUser(props: CreateUserProps) { /* ... */ }- Cada clase tiene una razón para cambiar.
- Encapsula estado y comportamiento correctamente.
- Evita clases que hacen todo.
class UserValidator { validate(user: User) { /* ... */ } }
class UserRepository { save(user: User) { /* ... */ } }
class UserNotifier { notify(user: User) { /* ... */ } }- S — Single Responsibility Principle.
- O — Open/Closed Principle.
- L — Liskov Substitution Principle.
- I — Interface Segregation Principle.
- D — Dependency Inversion Principle.
interface DiscountStrategy { apply(total: number): number; }
class NoDiscount implements DiscountStrategy { apply(total: number) { return total; } }
class BlackFridayDiscount implements DiscountStrategy { apply(total: number) { return total * 0.8; } }
function checkout(items: Item[], strategy: DiscountStrategy) {
const total = sum(items);
return strategy.apply(total);
}- Excepciones > códigos de error.
- Manejar errores en el nivel adecuado.
- Para flujo funcional, usar Result / Either.
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
function parseJson<T>(s: string): Result<T> {
try { return { ok: true, value: JSON.parse(s) as T }; }
catch (err) { return { ok: false, error: err as Error }; }
}- Fast
- Independent
- Repeatable
- Self-validating
- Timely
describe("addTax", () => {
it("adds tax correctly", () => {
expect(addTax(100, 0.21)).toBe(121);
});
});- Funciones largas → extraer funciones.
- Clases grandes → aplicar SRP.
- Duplicación → extraer método.
- Switch largos → strategy.
- Primitive obsession → value objects.
- Comentarios innecesarios → refactoriza.
- Diseñar módulos con API clara.
- Separar capas o usar vertical slices / hexagonal.
- Mantener dependencias hacia las abstracciones.
- ESLint + Prettier.
- Husky + pre-commit.
- Type-check en CI.
- Cobertura mínima en código crítico.
- strict: true en tsconfig.
- Evitar any; usar unknown + type guards.
- Usar readonly.
- Preferir discriminated unions.
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rect'; width: number; height: number };
function area(s: Shape) {
if (s.kind === 'circle') return Math.PI * s.radius ** 2;
return s.width * s.height;
}- ¿Nombres revelan intención?
- ¿Función hace una sola cosa?
- ¿Hay duplicación que quitar?
- ¿Tests cubren el cambio?
- ¿Errores manejados correctamente?
- ¿Refactor pequeño (Boy Scout)?
- ¿Lint/format en verde?
- Refactorizar un poco en cada PR.
- PRs pequeños y atómicos.
- Code reviews centrados en intención.
- Practicar katas de código.
// MAL
function processOrder(req: any) {
if (!req.userId) throw new Error('missing');
const total = req.items.reduce((s: number, i: any) => s + i.price, 0);
db.save({ userId: req.userId, total });
sendEmail(req.userId, `Total is ${total}`);
}
// BIEN
interface OrderRequest { userId: string; items: { price: number }[] }
function processOrder(req: OrderRequest) {
const order = buildOrder(req);
saveOrder(order);
notifyOrderCreated(order);
}
function buildOrder(req: OrderRequest) {
validateOrderRequest(req);
const total = calculateTotal(req.items);
return { userId: req.userId, items: req.items, total };
}- Clean Code = hábito.
- Refactorizar continuamente.
- Tests = confianza para cambiar.
- Código legible acelera a largo plazo.