Inicio / Artículos / Nueve hábitos a nivel de código que hacen que el trabajo de los ingenieros senior sea más fiable

Nueve hábitos a nivel de código que hacen que el trabajo de los ingenieros senior sea más fiable

Explora nueve prácticas concretas de programación, desde cláusulas de protección hasta un modelado estricto de datos, que hacen que el código sea más resistente, legible y fácil de depurar bajo presión.

1471 palabras

Durante mucho tiempo, parecía que la distancia entre los ingenieros más experimentados de un equipo y el resto se reducía al conocimiento puro: algún truco oculto de un framework, una API secreta, un atajo del que nadie más se había dado cuenta aún.

Observar de cerca cómo trabajan los ingenieros experimentados, a través de sesiones de trabajo en pareja, revisiones de código y llamadas para resolver incidentes, revela algo menos glamoroso. No necesariamente son más inteligentes; simplemente escriben código que resiste las averías accidentales, y eso hace que sea mucho más fácil repararlo cuando algo sale mal.

Nueve hábitos específicos aparecen con tanta frecuencia que vale la pena adoptarlos intencionadamente.

1. Cláusulas de protección en lugar de pirámides del desastre

Un error común al principio es anidar la lógica de validación hasta que forma una escalera de sangrías:

function processWithdrawal(account, amount) {
  if (account.isActive) {
    if (amount > 0) {
      if (account.balance >= amount) {
        return debit(account, amount);
      }
    }
  }
  throw new Error("Withdrawal failed");
}

Esto funciona, pero se vuelve ilegible después de la tercera condición, y la lógica real de retiro termina oculta a tres niveles de profundidad. El enfoque más experimentado invierte el orden: rechaza inmediatamente los casos inválidos y luego deja que la verdadera lógica se ejecute en el nivel superior, sin sangría de espacios:

function processWithdrawal(account, amount) {
  if (!account.isActive) throw new AccountInactiveError();
  if (amount <= 0) throw new InvalidAmountError();
  if (account.balance < amount) throw new InsufficientFundsError();

  return debit(account, amount);
}

No hay nada ingenioso aquí. Simplemente es legible bajo presión, lo cual es crucial justo cuando más se necesita: durante un incidente, a la una de la madrugada, medio dormido, tratando de averiguar cuál de las cinco condiciones anidadas te está engañando.

2. Nombres que describen el negocio, no el tipo de dato

Nombrear las variables según su forma en lugar de su significado —data, res, obj, list— funciona bien en un único archivo. Pero resulta terrible cuando la base de código cuenta con cuarenta archivos, cada uno definiendo data como algo diferente.

data = fetch(order_id)
if data["status"] == "done":
    process(data)

En comparación con:

order = fetch_order(order_id)
if order.is_fulfilled:
    archive_order(order)

La segunda versión indica qué representa el objeto y qué condición es realmente importante, sin obligar a rastrearla hasta la función de donde proviene. Cuesta unos pocos caracteres adicionales, pero permite que quien depure este código evite recorrer tres archivos no relacionados.

3. Una sola conexión entre tu código y el mundo exterior

Las APIs de terceros son narradores poco fiables. Los nombres de los campos cambian, las estructuras anidadas modifican su forma, los campos opcionales se convierten en obligatorios y viceversa. Permitir que las respuestas brutas de la API fluyan directamente hacia la lógica empresarial convierte cada uno de esos cambios en una búsqueda desesperada por todo el código.

// scattered everywhere
const price = apiResponse.line_items[0].unit_price_cents / 100;

El enfoque mejor es traducir la respuesta exactamente una vez, justo en el límite:

function toLineItem(raw) {
  return {
    label: raw.description,
    priceInDollars: raw.unit_price_cents / 100,
  };
}

Si el proveedor más tarde cambia el nombre de unit_price_cents a price, solo es necesario modificar una función. Todo lo que viene después permanece intacto y nunca se da cuenta de la diferencia.

4. Modelos de datos que no pueden mentir

Un tipo construido a partir de una docena de campos opcionales es un tipo que ha dejado de intentar representar la realidad con precisión:

type Ticket = {
  id?: string;
  assignee?: string;
  resolvedAt?: Date;
  resolution?: string;
};

Esta forma permite crear un ticket “resuelto” sin ninguna resolución asociada, o un ticket “asignado” sin asignatario; estados que deberían ser imposibles pero se compilan sin problemas. Dividir el tipo según el estado real elimina toda esa categoría de errores:

type OpenTicket = { id: string; assignee?: string };
type ResolvedTicket = { id: string; assignee: string; resolvedAt: Date; resolution: string };

Una función que envía un correo con un resumen de la resolución ahora puede requerir específicamente un argumento de tipo ResolvedTicket, y el compilador garantiza que nada incompleto llegue hasta ella.

5. Separar la pregunta de la orden

Cuando las reglas empresariales y los efectos secundarios se entrelazan, las pruebas se vuelven difíciles; y una vez que las pruebas son difíciles, deja de hacerse pruebas a las reglas en absoluto:

def promote_employee(employee_id):
    emp = get_employee(employee_id)
    if emp.tenure_months < 12:
        raise Error("Not eligible yet")
    if emp.current_rating < 3:
        raise Error("Rating too low")
    give_raise(emp)
    notify_hr(emp)
    log_promotion(emp)

Al extraer la verificación de elegibilidad en una función independiente, se puede probar la regla utilizando un objeto simple, sin necesidad de base de datos ni servicio de correo:

def promotion_eligibility(emp):
    if emp.tenure_months < 12:
        return Ineligible("Not enough tenure")
    if emp.current_rating < 3:
        return Ineligible("Rating too low")
    return Eligible()

Una vez que verificar la regla resulta sencillo, la gente realmente lo hace, y los casos límite dejan de pasar desapercibidos meses después cuando alguien modifica el requisito de permanencia.

6. Los comentarios explican por qué, nunca qué

Un comentario que simplemente repite lo que ya dice el código es un elemento innecesario:

// increment the counter
counter++;

Un comentario que explica la lógica detrás de una decisión es uno que vale la pena conservar:

// Retry once — the vendor's webhook occasionally arrives before
// the payment record finishes committing on their end.
retryOnce(processWebhook, payload);

Los ingenieros experimentados tienden a escribir notablemente menos comentarios que los más jóvenes, no por pereza, sino porque han comprendido que la mayoría de los comentarios existen para compensar un código que no se explica por sí mismo. Los comentarios que permanecen son aquellos que contienen información que el código simplemente no puede expresar por sí solo: una justificación, un compromiso, una advertencia sobre algo que no es obvio a simple vista.

7. Errores que apuntan hacia algo útil

Un mensaje de error como "Entrada inválida" no ofrece al siguiente desarrollador nada con lo que trabajar. ¿Inválida en qué sentido? ¿Qué entrada? Un error útil contiene suficientes detalles para que alguien pueda actuar en consecuencia:

{
  "code": "INVALID_DATE_RANGE",
  "message": "End date must be after start date.",
  "field": "endDate"
}

No es raro encontrar código frontend que inspecciona el texto de error para decidir qué mensaje mostrar, algo como if (err.message.includes("date")). Ese patrón es frágil por diseño. En el momento en que alguien reescribe el mensaje del backend, la lógica de la interfaz deja de funcionar silenciosamente. Los códigos existen para que las máquinas puedan tomar decisiones basadas en ellos; los mensajes existen para que los humanos puedan leerlos. Mantenerlos separados significa que ninguno tiene que sustituir torpemente al otro.

8. Una solicitud de pull, una idea

Una solicitud de pull etiquetada como “arreglar cosas relacionadas con la facturación” que abarca una docena de archivos y agrupa seis cambios no relacionados está casi fuera de posibilidad de revisión. Quien la revise o bien la aprueba sin verificarla realmente, o bien pasa una hora intentando averiguar qué cambio causó qué efecto.

El enfoque disciplinado puede parecer casi excesivamente cauteloso: renombrar el campo en una PR, introducir la nueva validación en otra y conectarla al flujo de trabajo en una tercera. Escribir de esta manera parece más lento en el momento, pero resulta mucho más rápido para revisar. Además, cuando algo falla en producción posteriormente, git log te proporciona una respuesta concreta en lugar de obligarte a analizar un diferencial de 400 líneas.

9. Trata el primer borrador como tal

Este último hábito tiene menos que ver con el código en sí y más con el ego. Los desarrolladores al inicio de sus carreras suelen considerar la primera versión que funciona como el producto final: ya que funciona, se lanza al mercado. Los ingenieros con más experiencia escriben esa primera versión asumiendo ya que la leerán de nuevo con ojo crítico antes de que llegue siquiera cerca de la producción.

Esa segunda revisión es cuando los condicionales anidados se transforman en cláusulas de protección, donde los nombres poco claros reciben nuevos nombres, y donde un estado supuestamente “imposible” se detecta antes de que el cliente se enfrente a él. Es un hábito sencillo: detenerse, volver a leer y preguntarse si esto confundiría a alguien que no tenga ningún contexto; pero es precisamente este el que hace que los otros ocho hábitos se apliquen realmente en la práctica.

El hilo conductor

Detrás de todo esto hay en realidad un mismo paso repetido: tomar la complejidad que de otro modo quedaría atrapada en la mente de otra persona más adelante y fijarla en algún lugar visible ahora mismo, ya sea en un nombre, un límite, un tipo o una diferencia pequeña y específica. Nada de esto requiere habilidades extraordinarias; solo exige decidir consistentemente que quien lea este código a continuación merezca una verdadera oportunidad de comprenderlo.

Lecturas relacionadas

  • Arqueología de Software: Un Método Práctico para Leer Código Legado — Aprenda un enfoque paso a paso para investigar de forma segura bases de código legado no documentadas, desde analizar el historial de commits hasta refactorizar sin interrumpir la operación en producción.
  • Siete Señales de Advertencia en Revisión que Predicen Cambios Costosos Futuros — Aprenda a identificar siete problemas comunes en el código que los revisores señalan tempranamente, desde flags de modo booleano hasta errores ignorados, y cómo determinar cuándo cada uno representa un problema real.