Escribiendo Software Mantenible

una platica por @jailandrade

"El mejor software no es el más elegante. Es el que se puede mantener."

Antes de empezar

Unas preguntas antes de comenzar

¿Cuántos han vuelto a un código que escribieron hace un año y no lo entendieron?

¿Cuántos le tienen miedo a tocar cierto archivo del proyecto?

¿Cuántos han preferido reescribir todo antes que entender lo que ya existía?

¿Tu código sobrevive sin ti?

"Si te identificaste con alguna, esta charla es para ti."

Un poco de mí

Más de 10 años escribiendo software

He pasado por agencias, freelance, startups y producto.

He escrito frontend, backend, scripts, infraestructura, sitios y aplicaciones móviles.

He trabajado solo y he trabajado en equipo, con y sin proceso, con y sin presión de negocio.

Hoy soy Software Product Engineer.

Lo que he construido en estos años

De todo un poco

  • Sitios y aplicaciones Web para clientes de agencia.
  • Herramientas internas que nadie más iba a mantener.
  • Productos completos desde cero, con equipo y sin equipo.
  • Automatizaciones, scripts, integraciones.
  • Código que sobrevivió años... y código que no sobrevivió ni el primer release.
"La diferencia entre esos dos nunca fue el lenguaje ni el framework."

La lección del último año

Algo que cambió cómo escribo software

El mejor software no es el más rápido de escribir.

No es el que usa la tecnología más nueva.

No es el que tiene más funcionalidades.

Es el que se puede mantener. Es el que se puede escalar.

"Todo lo demás es secundario."

¿Qué es software mantenible?

Una definición práctica

Software que alguien más —o tú mismo en 6 meses— puede entender, modificar y extender sin miedo.

  • Se entiende sin tener que preguntar.
  • Se cambia sin romper todo lo demás.
  • Se prueba sin sufrir.
  • Se documenta lo suficiente, ni más ni menos.

Mantenible no es sinónimo de perfecto

Algunos mitos comunes

"Mantenible" no significa cero deuda técnica.

No significa 100% de cobertura de tests.

No significa arquitectura perfecta desde el día uno.

Significa que el costo de cambiar algo se mantiene bajo con el tiempo.

No significa tener mucha documentación.

"Mantenible es una propiedad que se cuida, no que se logra una sola vez."

El costo real de no mantener

Nadie lo nota... hasta que ya es muy tarde

Cada atajo que tomas hoy, alguien lo paga después. A veces ese alguien eres tú.

La deuda técnica no desaparece, se acumula con interés.

Un feature que tomaba un día empieza a tomar una semana.

Y en algún punto, el equipo prefiere reescribir todo antes que entender lo que hay.

"Reescribir desde cero no es una solución, es una rendición."

La trampa de "shippear rápido, siempre"

Rápido hoy no debería ser lento mañana

Shippear rápido es bueno. Shippear rápido siempre, sin pensar en el después, es una trampa.

La velocidad de hoy no debería robarle la velocidad de mañana a tu equipo.

Software mantenible no es lento de escribir. Es rápido de seguir escribiendo.

"No estás compitiendo contra el tiempo. Estás compitiendo contra tu yo del futuro."

Señales de que tu código no es mantenible

Code smells que ya conoces

  • Le tienes miedo a tocar cierto archivo o módulo.
  • Nadie en el equipo quiere tocar cierta parte del sistema.
  • Cada cambio pequeño rompe algo que no debería.
  • Solo una persona entiende cómo funciona algo crítico.
  • La documentación, si existe, ya está desactualizada.

Así se ve en código

No hace falta imaginarlo

Mal

function calc(o) {
  if (o.t == 1) {
    if (o.s == 2) return o.p * 0.85;
    return o.p * 0.95;
  }
  return o.p;
}

¿Qué es t? ¿Qué es s? ¿Por qué 0.85? Funciona, pero nadie sabe por qué ni por cuánto tiempo seguirá funcionando.

Bien

function calcularPrecioFinal(pedido) {
  if (!esClientePremium(pedido)) return pedido.precio;

  return esTemporadaAlta(pedido)
    ? pedido.precio * DESCUENTO_PREMIUM_ALTA
    : pedido.precio * DESCUENTO_PREMIUM_REGULAR;
}

Así se ve en código (otra vez)

El "arrow of doom"

Mal

function procesarReserva($reserva) {
  if ($reserva) {
    if ($reserva['habitacion']) {
      if ($reserva['habitacion']['disponible']) {
        if ($reserva['pago']) {
          confirmar($reserva);
        }
      }
    }
  }
}

Cuatro niveles de anidamiento para llegar a lo único que realmente importa. Y cada caso nuevo agrega uno más.

Bien

function procesarReserva($reserva) {
  if (!$reserva) return;
  if (!$reserva['habitacion']['disponible']) return;
  if (!$reserva['pago']) return;

  confirmar($reserva);
}

Otros clásicos que seguro has visto

Y seguramente escrito también

  • Funciones "todólogo" — una función que valida, calcula, guarda en base de datos y manda el correo. Todo en 200 líneas.
  • Copiar y pegar en vez de reutilizar — el mismo bloque de lógica repetido en seis archivos, cada uno con su propio bug.
  • Errores que se tragan en silencio — un catch (e) {} vacío, y nadie se entera hasta que revienta en producción.
  • Comentarios que mienten — el código cambió, el comentario no. Ahora dice lo contrario de lo que hace.
  • Credenciales y configuración hardcodeadas — funciona en tu máquina, y en ningún otro lado. O peor, funciona en todos lados con el mismo secreto.
  • Commits "fix", "arreglos", "wip" — ningún rastro de por qué se hizo un cambio.
"Ninguno de estos ejemplos es hipotético. Todos los he escrito yo mismo, en algún punto."

Un caso real: el sitio de hoteles

Vendieron Laravel, entregaron PHP vainilla

Heredé un sitio de reservas de hoteles.

Al cliente le habían vendido Laravel. Por debajo era PHP vainilla, sin estructura, sin convenciones.

Las credenciales de la base de datos y de los servicios de pago estaban hardcodeadas directo en el código.

Nadie sabía qué hacía cada archivo. No había documentación, ni tests, ni nadie que explicara las decisiones.

"Vender una tecnología y entregar otra no es solo un engaño al cliente. Es deuda técnica que alguien más va a pagar."

Principio 1: Legibilidad ante todo

Se escribe una vez, se lee cientos

El código se lee muchas más veces de las que se escribe.

Nombra las cosas por lo que hacen, no por lo que son.

Prefiere código aburrido y claro sobre código ingenioso.

"El código ingenioso es una carta de amor para ti mismo y una carta de odio para quien lo hereda."

Principio 2: Bajo acoplamiento, alta cohesión

Lo que pertenece junto, vive junto

Cada parte del sistema debería poder cambiar sin arrastrar a las demás.

Lo que pertenece junto, vive junto. Lo que no, se separa.

Si tocar un módulo te obliga a tocar otros cinco, ahí hay una señal.

Principio 3: Consistencia sobre cleverness

Aburrido y predecible, casi siempre gana

Un equipo se mueve más rápido con un estilo consistente y aburrido que con mil estilos brillantes.

La consistencia reduce la carga cognitiva de cada cambio.

Sigue las convenciones del proyecto, incluso si no son las que tú elegirías.

Principio 4: Documentación mínima viable

Documenta el por qué, no solo el qué

No necesitas documentar todo. Necesitas documentar lo que no es obvio.

Un buen README, decisiones de arquitectura, y el por qué detrás de cada decisión.

La mejor documentación es el código mismo, cuando se puede.

Principio 5: Tests como red de seguridad

No como religión

Los tests no son para llegar a un número. Son para poder cambiar código sin miedo.

Prueba lo que realmente importa: lo crítico, lo frágil, lo que ya se rompió antes.

Un test que nadie entiende es tan peligroso como no tener test.

Escalar no es solo infraestructura

También escalas al equipo

Escalar el software es escalar al equipo que lo mantiene también.

Código que solo una persona entiende no escala, sin importar cuántos servidores tengas.

El conocimiento tiene que poder distribuirse, igual que la carga.

Deuda técnica: pagar o dejar crecer

Pero a propósito

No toda deuda técnica es mala. A veces es la decisión correcta para ir rápido hoy.

El problema no es tener deuda. Es no saber que la tienes, o no tener plan para pagarla.

Trátala como tratas la deuda financiera: con un plan, no con negación.

Nadie mantiene software solo

La cultura de equipo también es mantenibilidad

El code review no es un trámite, es donde el conocimiento se comparte.

Las decisiones de arquitectura compartidas se sostienen más tiempo que las individuales.

Un equipo que se entiende entre sí mantiene mejor que un genio solitario.

Lo que cambiaría si empezara de nuevo

Con 10 años de por medio

Invertiría en legibilidad desde el día uno, no "cuando haya tiempo".

Documentaría decisiones, no solo código.

Optimizaría para el próximo desarrollador, no para impresionar al actual.

"Ese próximo desarrollador, muchas veces, eres tú mismo en seis meses."

El software mantenible es una decisión de cada día

Gracias por escuchar

No es una arquitectura. No es una herramienta. No es un framework.

Es la suma de decisiones pequeñas que tomas cada vez que escribes una línea.

"Que comiencen las preguntas."