Fundamentos 9 min de lectura

Clean Code: Programando para Humanos

Publicado el 8 de enero de 2026

Clean Code: Programando para Humanos

Cuando comencé a programar, creía que el objetivo era escribir código que funcionara. Punto. Si el programa hacía lo que se suponía que debía hacer, estaba bien. Pero con el tiempo, aprendí una lección crucial: el código se lee mucho más de lo que se escribe.

He trabajado en proyectos donde el código funcionaba perfectamente, pero cada vez que necesitaba hacer un cambio, pasaba horas tratando de entender qué hacía cada función. He trabajado en proyectos donde el código era tan claro que podía hacer cambios con confianza en minutos.

La diferencia no está en la funcionalidad, sino en cómo está escrito el código. Y esa diferencia puede ser la que separa un proyecto exitoso de uno que colapsa bajo su propio peso.

El código es para humanos, no para máquinas

La CPU no se preocupa por cómo se llama una variable o si una función tiene 50 líneas o 5. La CPU ejecuta instrucciones. Pero tú, tu equipo, y tu “yo” del futuro sí se preocupan.

Estadísticas que deberían hacerte pensar:

  • El 80% del tiempo de un desarrollador se gasta leyendo código, no escribiéndolo
  • El código se mantiene durante años, pero se escribe una vez
  • Un bug cuesta 10 veces más arreglarlo en producción que durante el desarrollo

Si el código es difícil de leer, cada cambio toma más tiempo, cada bug es más difícil de encontrar, y cada nuevo desarrollador tarda más en ser productivo.

¿Qué es Clean Code?

Clean Code no es un conjunto de reglas rígidas. Es código que:

  • Es fácil de entender: Cualquier desarrollador puede leerlo y entender qué hace
  • Es fácil de modificar: Cambios futuros no requieren descifrar el código primero
  • Expresa su intención: El código comunica claramente qué hace y por qué
  • No tiene sorpresas: Hace lo que dice que hace, nada más, nada menos

Nombres que significan algo

El nombre de una variable, función o clase es la primera oportunidad de comunicar intención.

❌ Mal: Nombres que no dicen nada

// ¿Qué es d? ¿Qué es t? ¿Qué hace esta función?
function calc(d, t) {
    return d / t;
}

// ¿Qué es data? ¿Qué contiene?
const data = getData();
process(data);

✅ Bien: Nombres que expresan intención

// Ahora está claro: calcula velocidad (distancia / tiempo)
function calculateVelocity(distance, time) {
    return distance / time;
}

// Ahora está claro: obtiene usuarios y los procesa
const users = getActiveUsers();
processUserSubscriptions(users);

Reglas para nombres

  1. Usa nombres pronunciables: userData es mejor que usrDta
  2. Usa nombres buscables: DAYS_PER_WEEK es mejor que 7 (si es una constante)
  3. Evita prefijos innecesarios: User es mejor que CUser o IUser
  4. Usa verbos para funciones: calculateTotal() es mejor que total()
  5. Usa sustantivos para clases: UserRepository es mejor que UserManager

Funciones pequeñas y con un solo propósito

Una función debe hacer una cosa, y hacerla bien. Si una función hace múltiples cosas, es difícil de entender, probar y mantener.

❌ Mal: Función que hace demasiado

function processUser(user) {
    // Validar
    if (!user.email || !user.email.includes('@')) {
        throw new Error('Invalid email');
    }
    
    // Guardar en base de datos
    db.users.insert(user);
    
    // Enviar email
    emailService.sendWelcomeEmail(user.email);
    
    // Generar reporte
    const report = generateReport(user);
    reportService.save(report);
    
    // Actualizar analytics
    analytics.track('user_created', user.id);
    
    return user;
}

✅ Bien: Funciones con responsabilidad única

function createUser(userData) {
    validateUserData(userData);
    const user = saveUserToDatabase(userData);
    sendWelcomeEmail(user);
    generateUserReport(user);
    trackUserCreation(user);
    return user;
}

function validateUserData(userData) {
    if (!userData.email || !isValidEmail(userData.email)) {
        throw new ValidationError('Invalid email');
    }
}

function saveUserToDatabase(userData) {
    return db.users.insert(userData);
}

function sendWelcomeEmail(user) {
    emailService.sendWelcomeEmail(user.email);
}

function generateUserReport(user) {
    const report = generateReport(user);
    reportService.save(report);
}

function trackUserCreation(user) {
    analytics.track('user_created', user.id);
}

Ahora cada función:

  • Tiene un nombre que describe exactamente qué hace
  • Hace una sola cosa
  • Es fácil de probar
  • Es fácil de modificar sin afectar otras partes

Comentarios: Cuándo sí, cuándo no

Los comentarios son una forma de comunicación, pero el mejor comentario es código que no necesita comentarios.

❌ Mal: Comentarios que explican código obvio

// Incrementar contador
counter++;

// Verificar si el usuario está activo
if (user.isActive) {
    // Enviar email
    sendEmail(user.email);
}

✅ Bien: Código que se explica a sí mismo

counter++;

if (user.isActive) {
    sendEmail(user.email);
}

✅ Bien: Comentarios que explican el “por qué”, no el “qué”

// Usamos O(n²) aquí porque la mayoría de los casos tienen menos de 10 elementos
// y la simplicidad del código es más importante que la optimización
function findDuplicates(items) {
    // ...
}

// Este timeout es necesario porque el servicio externo tarda ~2 segundos en responder
// y no queremos que el usuario espere más de 5 segundos
setTimeout(() => {
    handleTimeout();
}, 5000);

Formato y estructura

El formato del código importa. Código bien formateado es más fácil de leer y entender.

Indentación consistente

// ❌ Mal: Indentación inconsistente
function processOrder(order){
if(order.items.length > 0){
for(let item of order.items){
if(item.quantity > 0){
processItem(item);
}
}
}
}

// ✅ Bien: Indentación consistente
function processOrder(order) {
    if (order.items.length > 0) {
        for (let item of order.items) {
            if (item.quantity > 0) {
                processItem(item);
            }
        }
    }
}

Líneas en blanco para separar conceptos

// ❌ Mal: Todo junto
function calculateTotal(items, taxRate, discount) {
    let subtotal = 0;
    for (let item of items) {
        subtotal += item.price * item.quantity;
    }
    const tax = subtotal * taxRate;
    const discountAmount = subtotal * discount;
    const total = subtotal + tax - discountAmount;
    return total;
}

// ✅ Bien: Conceptos separados
function calculateTotal(items, taxRate, discount) {
    const subtotal = calculateSubtotal(items);
    
    const tax = calculateTax(subtotal, taxRate);
    const discountAmount = calculateDiscount(subtotal, discount);
    
    const total = subtotal + tax - discountAmount;
    return total;
}

Manejo de errores

El manejo de errores debe ser claro y explícito.

❌ Mal: Errores silenciosos

function getUser(id) {
    const user = db.users.findById(id);
    return user; // ¿Qué pasa si user es null?
}

function processPayment(amount) {
    try {
        paymentProcessor.charge(amount);
    } catch (e) {
        // Error silenciado - el usuario no sabe qué pasó
    }
}

✅ Bien: Errores explícitos

function getUser(id) {
    const user = db.users.findById(id);
    if (!user) {
        throw new UserNotFoundError(`User with id ${id} not found`);
    }
    return user;
}

function processPayment(amount) {
    try {
        paymentProcessor.charge(amount);
    } catch (error) {
        logger.error('Payment processing failed', { amount, error });
        throw new PaymentProcessingError('Failed to process payment', error);
    }
}

No te repitas (DRY)

Si encuentras código duplicado, es una señal de que deberías extraerlo a una función.

❌ Mal: Código duplicado

function validateUser(user) {
    if (!user.email || !user.email.includes('@')) {
        throw new Error('Invalid email');
    }
    if (!user.name || user.name.length < 2) {
        throw new Error('Invalid name');
    }
}

function validateAdmin(admin) {
    if (!admin.email || !admin.email.includes('@')) {
        throw new Error('Invalid email');
    }
    if (!admin.name || admin.name.length < 2) {
        throw new Error('Invalid name');
    }
}

✅ Bien: Código reutilizable

function validateEmail(email) {
    if (!email || !email.includes('@')) {
        throw new ValidationError('Invalid email');
    }
}

function validateName(name) {
    if (!name || name.length < 2) {
        throw new ValidationError('Invalid name');
    }
}

function validateUser(user) {
    validateEmail(user.email);
    validateName(user.name);
}

function validateAdmin(admin) {
    validateEmail(admin.email);
    validateName(admin.name);
}

Principios prácticos

1. Boy Scout Rule

“Deja el código mejor de como lo encontraste”. Cada vez que tocas código, haz una pequeña mejora.

2. La regla de los dos

La primera vez que haces algo, hazlo. La segunda vez que haces algo similar, refactoriza.

3. Pequeños pasos

No intentes refactorizar todo de una vez. Haz cambios pequeños y frecuentes.

4. Pruebas como documentación

Las pruebas bien escritas son documentación ejecutable. Muestran cómo se usa el código.

describe('calculateTotal', () => {
    it('should calculate total with tax and discount', () => {
        const items = [
            { price: 10, quantity: 2 },
            { price: 5, quantity: 1 }
        ];
        
        const total = calculateTotal(items, 0.1, 0.05);
        
        expect(total).toBe(26.25); // (20 + 5) * 1.1 * 0.95
    });
});

Mi experiencia práctica

He visto proyectos donde el código funcionaba, pero cada cambio era una pesadilla. He visto proyectos donde el código era tan claro que nuevos desarrolladores podían ser productivos en días, no semanas.

Aplicar estos principios constantemente no es sobre perfección, es sobre mejora continua. Cada vez que tocas código, intenta dejarlo un poco mejor.

Ejemplo práctico: Refactorización

Antes (código que funcionaba pero era difícil de entender):

function proc(u) {
    let t = 0;
    for (let i = 0; i < u.length; i++) {
        if (u[i].s === 'a') {
            t += u[i].v;
        }
    }
    return t;
}

Después (código claro):

function calculateTotalActiveUsersValue(users) {
    return users
        .filter(user => user.status === 'active')
        .reduce((total, user) => total + user.value, 0);
}

El código hace exactamente lo mismo, pero ahora cualquiera puede entenderlo en segundos.

La mentalidad de Clean Code

Clean Code no es un conjunto de reglas que sigues ciegamente. Es una mentalidad:

  • Piensa en el lector: ¿Será fácil para alguien más entender esto?
  • Piensa en el futuro: ¿Podré entender esto en 6 meses?
  • Piensa en el equipo: ¿Mi equipo podrá trabajar con esto?

No se trata de escribir código perfecto desde el inicio. Se trata de mejorar continuamente, de hacer el código un poco mejor cada vez que lo tocas.

Mi perspectiva personal

El código limpio no es un lujo, es una necesidad. En un mundo donde el código se lee mucho más de lo que se escribe, la claridad es más importante que la inteligencia.

He visto proyectos donde el código funcionaba, pero cada cambio era una pesadilla. He visto proyectos donde el código era tan claro que podía hacer cambios con confianza en minutos.

He aprendido que el mejor código no es el más inteligente o el más optimizado, sino el más claro. Código que cualquier desarrollador puede leer y entender. Código que no necesita un manual de descifrado.

Aplicar estos principios constantemente no es sobre perfección, es sobre mejora continua. Cada vez que toco código, intento dejarlo un poco mejor.

Porque al final del día, estás programando para humanos. Y el humano más importante para quien programas es tu “yo” del futuro, que te agradecerá por escribir código que pueda entender.