Por qué el código limpio sigue importando: ocho principios que conviene mantener

La IA puede refactorizar por ti, y eso hace los fundamentos más útiles, no menos. Ocho prácticas que mantienen manejable un proyecto.

La IA y el sitio que ocupa hoy el código limpio

La IA está por todas partes en este oficio, desde generar fragmentos hasta escribir funciones enteras, y es razonable preguntarse si escribir código limpio sigue teniendo sentido cuando un modelo puede refactorizarlo por ti.

Yo mismo he ido y venido con esa duda. La IA es un asistente competente, pero no lee la mente. Puede optimizar, refactorizar y proponer alternativas más claras, y todo eso funciona mejor cuando parte de algo sólido. Dale a un buen cocinero ingredientes excelentes y una receta caótica: el plato saldrá igual de mal.

El código limpio va de claridad, de mantenimiento y de cómo envejece un proyecto. De paso, hace más eficaces a tus herramientas de IA. Estos son ocho principios que aguantan.

1. Nombres con significado

La claridad empieza aquí. Imagina una historia donde todos los personajes se llaman "a", "b" y "c". Con el código pasa lo mismo. Variables, funciones y clases deberían decir qué hacen o qué representan antes de leer una sola línea de su cuerpo.

let d; // What is 'd'? Days? Data?
function p(a, b) {
  /* ... */
} // What does 'p' do? What are 'a' and 'b'?

Compáralo con esto:

let daysSinceLastLogin;
function calculateTotalPrice(quantity, unitPrice) {
  /* ... */
}

Los buenos nombres funcionan como documentación dentro del código. Reducen la carga mental de leerlo y aceleran la depuración. Un modelo que lea tu código se beneficia de la misma claridad que tú.

2. Una función debería hacer una sola cosa

Cada función debería tener un único motivo para cambiar. Cuando hace demasiado, cuesta más probarla, cuesta más seguirla y es más fácil romperla al tocarla.

function processOrder(order) {
  // Validate order
  // Save to database
  // Send confirmation email
  // Update inventory
}

Mejor separarlo:

function validateOrder(order) {
  /* ... */
}
function saveOrder(order) {
  /* ... */
}
function sendConfirmationEmail(order) {
  /* ... */
}
function updateInventory(order) {
  /* ... */
}

function processOrder(order) {
  validateOrder(order);
  saveOrder(order);
  sendConfirmationEmail(order);
  updateInventory(order);
}

Partir una tarea compleja en piezas concretas hace el código modular y bastante más fácil de leer después.

3. No te repitas

Si estás escribiendo la misma lógica por segunda vez, llévala a una función o a un componente reutilizable. La duplicación destruye el mantenimiento en silencio: cuando hay que corregir un fallo, hay que corregirlo en varios sitios, y tarde o temprano se te escapa uno.

// In file A
function calculateDiscountA(price) {
  if (price > 100) return price * 0.9;
  return price;
}

// In file B (same logic)
function calculateDiscountB(amount) {
  if (amount > 100) return amount * 0.9;
  return amount;
}

Con una sola implementación basta:

function applyStandardDiscount(price) {
  if (price > 100) return price * 0.9;
  return price;
}

// Now both A and B can use applyStandardDiscount

4. Comenta el porqué, no el qué

Quizá hayas oído que el buen código no necesita comentarios, y en parte es cierto. Un código claro, con nombres que dicen algo, vuelve redundantes muchos comentarios.

Lo que sí conviene dejar escrito es por qué algo se hizo de determinada manera, o por qué existe un apaño. Ese tipo de comentario vale mucho.

// Increment the counter by 1
counter++;

Ese no aporta nada. Este sí:

// This specific regex is used to handle legacy user IDs
// which sometimes contain leading zeros and special characters.
const userIdRegex = /^[0-9a-zA-Z\-_]+$/;

Si hace falta un comentario para explicar qué hace el código, suele ser señal de que el código podría estar más claro.

5. Gestiona los errores

Las redes se caen, los usuarios escriben cualquier cosa y las API devuelven algo inesperado. Ignorarlo produce comportamientos impredecibles y usuarios que no entienden qué ha fallado.

Cuánta gestión de errores es suficiente depende del contexto, pero una regla razonable es tratarlos allí donde puedes recuperarte o decir algo útil al usuario o al sistema.

function getUserData(userId) {
  const data = api.fetch(userId); // What if api.fetch fails?
  return data.name;
}

Algo más parecido a esto:

async function getUserData(userId) {
  try {
    const response = await api.fetch(userId);
    if (!response.ok) {
      throw new Error(`Failed to fetch user data: ${response.statusText}`);
    }
    const data = await response.json();
    return data.name;
  } catch (error) {
    console.error(`Error fetching user ${userId}:`, error);
    // Potentially return a default value, or re-throw a custom error
    throw new CustomApplicationError("User data unavailable");
  }
}

6. Mantén la consistencia

La consistencia importa sobre todo en equipos grandes: las mismas convenciones de nombres, el mismo formato, los mismos patrones de arquitectura y las mismas decisiones de diseño en todo el proyecto. Cuando una parte usa camelCase y otra snake_case, leer se vuelve más difícil sin ningún motivo.

Linters y formateadores como ESLint y Prettier resuelven casi todo esto de forma automática.

let firstName;
const user_id = 123;
function getProducts() {
  /* ... */
}
const fetchOrders = async () => {
  /* ... */
};

Frente a:

let firstName;
const userId = 123;
function getProducts() {
  /* ... */
}
async function fetchOrders() {
  /* ... */
}

Un proyecto consistente se lee como si lo hubiera escrito una sola persona, aunque hayan sido cincuenta. Eso reduce el esfuerzo de saltar entre archivos.

7. Escribe pruebas

Escribir pruebas puede parecer una tarea pesada. También es lo que te permite refactorizar y desplegar sin contener la respiración. Con la programación asistida por IA esto importa más, no menos: las pruebas son la forma de saber si el código generado se integra bien y no ha roto otra cosa.

Imagina hacer un cambio pequeño y ver que se cae una parte de la aplicación que no tenía nada que ver. Sin pruebas, eso es una sesión larga de depuración. Con pruebas, el caso que falla te señala el problema.

// function to test
function add(a, b) {
  return a + b;
}

// test file
describe("add function", () => {
  test("should add two positive numbers correctly", () => {
    expect(add(1, 2)).toBe(3);
  });

  test("should handle negative numbers", () => {
    expect(add(-1, 5)).toBe(4);
  });

  test("should return zero when adding opposite numbers", () => {
    expect(add(-3, 3)).toBe(0);
  });
});

8. Sencillez

Todo lo anterior se reduce a escribir código simple y fácil de leer. El código enrevesado atrae errores y se resiste al mantenimiento. Busca la solución más sencilla que cumpla el requisito.

function calculateTotal(items) {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    let item = items[i];
    if (item.type === "premium") {
      total += item.price * 1.1; // 10% premium fee
    } else {
      total += item.price;
    }
  }
  return total;
}

Lo mismo, más fácil de seguir:

const calculateItemPrice = (item) => {
  if (item.type === "premium") {
    return item.price * 1.1;
  }
  return item.price;
};

function calculateTotal(items) {
  return items.reduce((acc, item) => acc + calculateItemPrice(item), 0);
}

La sencillez no consiste en rebajar el código, sino en escribir algo que seguirás entendiendo dentro de un año.

El oficio no desaparece

Seguirán llegando herramientas nuevas y la IA ha cambiado bastantes cosas, pero amplifica lo que ya haces en lugar de eliminar la necesidad de hacerlo bien.

El código limpio se parece más a un hábito que a un reglamento. Es consideración hacia tus compañeros, hacia tus usuarios y hacia la persona que abrirá ese archivo dentro de año y medio, que probablemente serás tú.

Artículos relacionados