Inicio / Artículos

JavaScript legible a través de ejemplos: Diez refactorizaciones antes y después

Diez pequeñas mejoras en JavaScript y React, que van desde la nomenclatura y los objetos de parámetros hasta el manejo de errores y el formato, para que el código sea más fácil de leer y modificar por el siguiente desarrollador.

Editor: REACTAPP.TOP7 min de lectura1583 palabras

Fuentes: 10 Clean Code Principles I Wish I Knew When I Started Coding

Hacer que una función funcione es la primera mitad del trabajo. La segunda mitad consiste en asegurarse de que un compañero de equipo, o usted mismo dentro de seis meses, pueda leer ese código, modificarlo de forma segura y no interpretarlo erróneamente. Este artículo analiza diez ejemplos concretos de refactorizaciones antes y después en JavaScript y React, cada uno con la lógica detrás de ellas y el punto en el que los consejos dejan de ser útiles.

1. Denomine los valores según lo que representan

Los nombres cortos y enigmáticos obligan al lector a buscar información en otro lugar. En este fragmento, nada indica qué es d ni qué propiedad contiene a.

const d = 30;
const x = users.filter(u => u.a);

Al renombrar ambos, la intención queda visible donde se utilizan. El número pasa a ser una longitud de prueba:

const trialPeriodDays = 30;

Y el filtro ahora se entiende como una oración sobre usuarios activos:

const activeUsers = users.filter(user => user.isActive);

Un buen nombre indica qué representa el valor; los nombres genéricos como data casi no dicen nada.

const data = getData();

Un nombre que expresa el concepto del dominio es mucho más informativo:

const customerOrders = getCustomerOrders();

Si sientes la necesidad de agregar un comentario para explicar una variable, eso suele ser señal de que el nombre debería contener esa información en su lugar.

2. Dale a cada función una única tarea

Una función que valida la entrada, da forma a los datos, escribe en una base de datos, envía correos electrónicos y registra actividades es difícil de seguir y aún más difícil de probar, ya que cada prueba debe configurar todas esas tareas al mismo tiempo. El esquema a continuación muestra tal función con cada responsabilidad incluida directamente.

function createUser(user) {
  // validate user
  // format data
  // save to database
  // send email
  // log activity
}

Dividirla en pasos nombrados convierte a la función de nivel superior en un resumen legible. Comienza con la validación:

function createUser(user) {
  validateUser(user);

y continúa con el formato, el guardado y el correo de bienvenida, cada uno delegado en su propia función:

  const formattedUser = formatUser(user);  saveUser(formattedUser);  sendWelcomeEmail(formattedUser);
}

Ahora createUser() parece una lista de pasos, y cada función auxiliar puede probarse de forma aislada. El compromiso es la indirectitud: quien necesite los detalles debe acceder a cada función auxiliar, por lo que sus nombres deben ser lo suficientemente precisos como para que rara vez sea necesario hacerlo.

3. Eliminar duplicados que compartan la misma razón de cambio

La lógica repetida es un candidato para ser extraída. Aquí la misma fórmula de impuestos aparece tres veces.

const total1 = price1 + price1 * 0.15;
const total2 = price2 + price2 * 0.15;
const total3 = price3 + price3 * 0.15;

Al trasladarla a una función, la regla recibe un nombre y un único lugar de aplicación:

function calculatePriceWithTax(price) {
  return price + price * 0.15;
}

Cada punto de llamada simplemente aplica esa regla:

const total1 = calculatePriceWithTax(price1);
const total2 = calculatePriceWithTax(price2);
const total3 = calculatePriceWithTax(price3);

Si la tasa pasa del 15 % al 18 %, basta con cambiar una sola línea en lugar de buscar por todo el proyecto. La advertencia importante es eliminar la duplicación real, no cualquier par de líneas que sucedan a ser similares. Dos fragmentos de código que hoy son idénticos pero cambian por razones distintas es mejor dejarlos separados; fusionarlos vincula elementos que deberían evolucionar de forma independiente. El artículo sobre cuándo debería existir código compartido analiza en profundidad ese equilibrio.

4. Reemplazar listas largas de parámetros posicionales por un objeto

Los argumentos posicionales ocultan su significado en el lugar de la llamada. Al observar esta llamada, no se puede saber qué significan 25 o true sin abrir la definición de la función.

createUser(
  "Shayan",
  "shayan@example.com",
  25,
  "developer",
  true,
  "Pakistan"
);

Al pasar un único objeto con propiedades nombradas, cada valor se vuelve autoexplicativo:

createUser({
  name: "Shayan",
  email: "shayan@example.com",
  age: 25,
  role: "developer",
  isActive: true,
  country: "Pakistan"
});

Este estilo ya es la norma en los payloads de API, objetos de configuración y propiedades de React, por lo que suele parecer natural en aplicaciones más grandes.

5. Prefiera código evidente sobre código ingenioso

Comprimir la lógica en una sola línea puede dificultar su lectura. La línea a continuación enlaza llamadas opcionales mediante nombres de propiedades abreviados.

const result = users?.filter(u => u.a)?.map(u => u.n);

Dividirlo en dos pasos con nombres descriptivos hace que cada transformación sea explícita. Primero, seleccione a los usuarios activos:

const activeUsers = users.filter(user => user.isActive);

Luego, extraiga sus nombres:

const activeUserNames = activeUsers.map(user => user.name);

Requiere una línea adicional, pero un lector puede comprenderlo de inmediato. Observe una diferencia en el comportamiento: la versión original utilizaba la cadena opcional, por lo que toleraba que users fuera null o undefined, mientras que la versión reescrita lanza un error en ese caso. Si users puede faltar legítimamente, maneje eso de forma explícita (por ejemplo con un valor por defecto de []) en lugar de ocultarlo en una cadena de ?.. El principio rector es hacer que el código sea claro, no impresionante.

6. Planifique para los fallos, no solo para el camino exitoso

Los sistemas reales fallan: las APIs devuelven errores, los usuarios envían datos inválidos, las redes se interrumpen y las bases de datos quedan fuera de línea. El código siguiente asume que todo funcionará sin problemas.

const response = await fetch("/api/users");
const users = await response.json();

Un detalle importante es que fetch solo rechaza las solicitudes en caso de fallos de red; un error HTTP 404 o 500 sigue resolviéndose. La versión mejorada envuelve la solicitud dentro de try:

try {
  const response = await fetch("/api/users");

y luego verifica response.ok, lanza un error si el estado indica fallo, analiza el cuerpo de la respuesta y captura cualquier problema que pueda surgir:

  if (!response.ok) {
    throw new Error("Failed to fetch users");
  }  const users = await response.json();  return users;
} catch (error) {
  console.error("Unable to load users:", error);
}

Esto hace que los fallos sean visibles y mucho más fáciles de depurar. Tenga en cuenta que el bloque catch aquí registra el error y luego devuelve implícitamente undefined, por lo que quienes llaman a la función no pueden distinguir entre un fallo y un resultado vacío. En el código real, sea reenviando el error después de registrarlo o devolviendo un valor explícito que quien llama pueda verificar, para así manejar el error donde realmente se puede abordar.

7. Comente el porqué, no el qué

Los comentarios son útiles; los redundantes son ruido. Este comentario simplemente repite exactamente lo que hace esa línea de código.

// Increment counter by 1
counter++;

Un buen comentario explica una decisión que el código no puede expresar por sí mismo, como por qué se retrasa intencionadamente una solicitud:

// Delay the request to avoid triggering the API rate limit.
setTimeout(fetchUsers, 1000);

Una regla útil: deje que el código indique qué hace y utilice comentarios para explicar por qué lo hace.

8. Mantenga las reglas de negocio fuera de JSX

En los componentes de React es fácil incluir directamente filtros y cálculos en el marcado. Esta salida de renderizado aplica dos filtros directamente antes de mapear los productos.

<div>
  {products
    .filter(product => product.stock > 0)
    .filter(product => product.price < 100)
    .map(product => (
      <ProductCard product={product} />
    ))}
</div>

Al mover la regla a una variable con nombre, se separa lo que considera un producto disponible de cómo se muestran los productos:

const availableProducts = products.filter(
  product => product.stock > 0 && product.price < 100
);

El JSX entonces describe únicamente la interfaz:

<div>
  {availableProducts.map(product => (
    <ProductCard key={product.id} product={product} />
  ))}
</div>

Observe que la versión refactorizada también agrega una propiedad key, que faltaba en la original y que React necesita para rastrear correctamente los elementos de la lista.

9. Deje que las herramientas impongan un formato consistente

El formato parece algo menor hasta que una base de código mezcla estilos. Aquí, la misma variable es declarada de tres maneras por tres personas: camelCase,

const userName = "Shayan";

snake_case,

const user_name = "Shayan";

y PascalCase sin espacios ni punto y coma:

const UserName="Shayan"

Todos los tres métodos funcionan, pero la inconsistencia genera dificultades cada vez que alguien lee o busca en el código. Herramientas como Prettier y ESLint eliminan este problema: Prettier formatea automáticamente, mientras que ESLint puede hacer cumplir las convenciones de nomenclatura y señalar errores comunes.

10. Escribe para quien lo mantenga después

Este principio conecta a los demás. La próxima persona que abra su archivo podría ser un nuevo colega, un compañero de equipo o su futuro yo, y su futuro yo ya habrá olvidado todo lo que usted pensaba hoy.

Antes de guardar los cambios, pregúntate si podrías entender el archivo si lo abrieras por primera vez en seis meses. Si no, simplifícalo: renombra las variables confusas, divide las funciones grandes, elimina la duplicación que tenga motivos similares para modificarse y mejora su estructura. El objetivo fundamental es reducir el esfuerzo necesario para comprender el código. Para conocer más hábitos con el mismo enfoque, consulta hábitos que facilitan que el siguiente desarrollador modifique el código.

Conclusión

Un código limpio no significa un código perfecto. Los plazos ajustados, los requisitos cambiantes y la evolución de los proyectos hacen que todo equipo escriba algo de código desordenado. Lo importante es la dirección: cada cambio debe hacer que el código sea un poco más claro, sencillo y fácil de mantener.

  • Los nombres deben indicar qué representa un valor.
  • Las funciones deben realizar una sola tarea, y la duplicación solo debe eliminarse cuando compartan el mismo motivo para modificarse.
  • Los objetos son mejores que las largas listas de parámetros posicionales.
  • La legibilidad es más importante que la brevedad, pero mantén el comportamiento idéntico al reescribir.
  • Maneja los fallos de forma explícita, y comenta las decisiones en lugar de la mecánica.
  • Mantén las reglas de negocio fuera del marcado, y deja que las herramientas se encarguen del formato.