Cómo pensar como un arquitecto de software
Dos ingenieros, una funcionalidad automotriz. Tres meses de código que falló en certificación, frente a dos semanas (una de diseño, una de código) que se entregaron limpias. La secuencia de cuatro pasos que marcó la diferencia.
Hace unos años vi a un ingeniero pasar tres meses en una funcionalidad para un cliente automotriz. La entregó dentro del plazo. El código pasó las pruebas unitarias. El equipo siguió adelante. Entonces empezó la certificación, y uno por uno los ciclos de prueba empezaron a fallar — casos límite que la implementación no había previsto, comportamiento que contradecía el spec, edge cases que hacían crashear la ECU en el banco. Tres meses de trabajo que no se podían entregar.
Reescribí la misma funcionalidad en dos semanas. Una semana de diseño, una semana de código. La misma persona tecleando — yo. El mismo compilador. El mismo target. La única diferencia es que tenía un diseño antes de tener un archivo.
Este post va sobre lo que marcó la diferencia. No "diseña primero" como eslogan — la secuencia real que sigo en el escritorio antes de escribir código. Está anclado en C automotriz embebido, donde cada cambio tiene que sobrevivir a la certificación y vive en producción durante una década. Los principios viajan más allá.
La cicatriz que me lo enseñó
Años antes, trabajaba en un sistema de seguimiento de flotas de vehículos. C heredado, sin estructura, cada cambio introducía una regresión. Pasábamos seis meses corrigiendo bugs de funcionalidades menores.
El momento que me caló fue pequeño. Teníamos la misma lógica copiada en tres archivos diferentes. Llegó un bug. Encontré una de las copias, la corregí, entregué. Al sprint siguiente el bug volvió, reportado por otro usuario, llegando a través de una de las otras dos copias. Corregí esa. El bug volvió por tercera vez.
El bug no era realmente el bug. El bug era que el código no tenía un hogar decidido para esa responsabilidad concreta. Tres archivos pensaban que eran dueños. Ninguno lo era.
Deduplicé, el bug se quedó muerto, y me llevé algo más grande que "deduplica el código". Cada responsabilidad en un sistema necesita exactamente un hogar. Si no decides dónde vive desde el principio, el sistema decide por ti, mal, repartido por archivos que se desincronizan independientemente durante los próximos diez años.
Ahí dejé de fiarme del código que se había escrito sin un diseño.
¿Cómo diseño software antes de escribir una sola línea de código?
Los ingenieros evitan semanas de retrabajo en certificación dedicando una semana a diseñar una funcionalidad antes de escribir código. La secuencia de abajo — escribir los requisitos en lenguaje natural, enumerar los modos de fallo que la funcionalidad debe sobrevivir, dibujar las fronteras de los módulos con una responsabilidad por caja, y definir cada firma de interfaz — es lo que convirtió la implementación de tres meses que falló la certificación en una implementación de dos semanas que pasó limpia. Mismo compilador, mismo target, misma persona tecleando. La diferencia fue que el diseño existía antes que el fichero. La mayoría de artículos se saltan esta parte porque te enseñan qué teclear, no qué pensar antes de teclear. La semana de diseño no es tiempo perdido — produce justamente lo que hace que la semana de implementación sea rápida y el ciclo de certificación predecible. No es waterfall. Es el suelo por debajo del cual no empiezo a teclear.
| Etapa | Primero código (3 meses → fallo) | Primero diseño (1 semana + 1 semana → ship) |
|---|---|---|
| Requisitos | Implícitos, en la cabeza mientras se teclea | Escritos en lenguaje natural antes del primer fichero |
| Modos de fallo | Se descubren en certificación | Enumerados de inicio, dentro del diseño |
| Fronteras de módulo | Surgen del corta-y-pega | Una responsabilidad por fichero, declarada de inicio |
| Interfaces | Se negocian a mitad de implementación | Las firmas se definen antes de cualquier cuerpo de función |
| Dónde se va el tiempo | La mayoría arreglando lo que el diseño habría capturado | La mayoría en implementación que simplemente funciona |
1. Escribo lo que la funcionalidad tiene que hacer — en lenguaje natural. No pseudocódigo, no tipos, no interfaces. Una lista con bullets de comportamientos, en las mismas palabras que usaría para explicárselo a un PM no técnico. "Cuando el vehículo arranca, envía un ping de estado. Si la red está caída, encola el ping para más tarde. Si la cola supera N, descarta el más antiguo." Sigo hasta que no se me ocurra otra cosa que la funcionalidad deba hacer.
2. Después escribo lo que NO debe hacer. Casos límite, modos de fallo, lo que la gente suele llamar edge cases. "Y si se va la corriente a mitad del ping. Y si el archivo de cola se corrompe. Y si el reloj del sistema salta hacia atrás. Y si dos pings hacen carrera." Si no puedes listar los modos de fallo, todavía no entiendes la funcionalidad — y son los que fallan en certificación.
3. Dibujo una caja por cada archivo que necesitará la funcionalidad, y le asigno un trabajo a cada caja. Trabajo en C, así que pienso en archivos, no en clases — pero la unidad de diseño es universal: una cosa con nombre y una sola responsabilidad. "Este archivo es dueño de la cola. Este es dueño del transporte de red. Este es dueño del ciclo de vida." Si no puedes escribir el trabajo en la caja en una sola frase corta, la caja está mal.
4. Defino las interfaces entre las cajas, y hacia el exterior. Las firmas de funciones que cada archivo expone, más las interfaces al OS, al hardware y a APIs externas. Las escribo como cabeceras — nombres, parámetros, tipos de retorno — antes de que se implemente cualquier lógica. Si no puedo escribir la firma, no entiendo el contrato. Si no entiendo el contrato, no estoy listo para implementar.
Cuando los cuatro pasos están hechos, el diseño está hecho. La implementación es entonces casi solo teclear. Las interfaces ya están en su sitio, los casos límite están mapeados, las responsabilidades están fijadas. La semana de diseño no fue tiempo invertido en producir nada — fue tiempo invertido en producir lo que hace que el código sea rápido y la certificación limpia.
Por qué los equipos siguen saltándose esto
Si esto funciona, ¿por qué no lo hace todo el mundo? Tres razones que veo una y otra vez:
Los ingenieros junior no pueden. No porque no sean inteligentes — porque los entrenaron para pensar con código delante. El autocompletado es parte de su proceso de pensamiento. Pídeles que diseñen sin un editor abierto y se quedan paralizados. La solución no es "diles que diseñen más". Es emparejarlos durante la fase de diseño, igual que les emparejamos durante la fase de código.
Presión del PMO por output visible. Una semana de cabeceras y diagramas es invisible para las métricas que los managers reportan hacia arriba. El equipo que piensa una semana y entrega en otra parece más lento que el equipo que no entrega nada usable en tres meses pero hace commits cada día.
Proyectos asignados a un único recurso. Cuando un ingeniero está solo en una funcionalidad, no hay nadie que empuje contra un diseño que falta. Nadie que pregunte "qué pasa cuando la cola se desborda". La presión por empezar a codificar se hace abrumadora, porque teclear se siente como progreso.
Ninguna de estas se arregla escribiendo mejores ingenieros. Se arreglan cuando las personas que dirigen el proyecto deciden que el output de diseño es output real.
Dos vacas sagradas del automotriz que no ayudan
Dos partes del proceso de software automotriz que creo que pierden tiempo sin producir seguridad:
Cumplimiento de MISRA C tratado como casilla de verificación. Algunas reglas MISRA capturan bugs reales, sin duda. Pero "somos MISRA-compliant" se ha convertido en luz verde en la puerta de la mitad de los proyectos que veo, con el código real sin revisar en cuanto a diseño. Una función puede ser perfectamente MISRA-limpia y a la vez ser una función-dios de 600 líneas con cinco razones para cambiar. Cumplir no es ser correcto.
Documentos de V-model que existen para la firma. Specs que se desincronizan del código en cuanto empieza el siguiente sprint. Planes de prueba escritos para el auditor, no para el ingeniero. El proceso es sólido sobre el papel. En la práctica los artefactos se vuelven decorativos — existen para que la lista de entregables esté completa, no porque alguien los lea. Si un documento no se lee después de la firma, no es un documento; es overhead.
Un documento de diseño de dos páginas que el equipo se relee antes de cada sprint vale más que un paquete V-model de 200 páginas que nadie abre después del release uno.
Qué hacer al respecto
Si lideras un equipo de ingeniería: Convierte la fase de diseño en un entregable. Revisa el diseño antes de que se escriba el código, igual que revisas el código antes de que se entregue. Si tus stand-ups no pueden representar "ayer dibujé el mapa de archivos y acordamos las responsabilidades" como progreso legítimo, arregla el stand-up.
Si eres ingeniero: Resiste el tirón de teclear. Si no puedes escribir lo que la funcionalidad debe hacer, lo que no debe hacer, qué archivos son dueños de qué, y qué interfaces exponen, no estás listo para implementar. El coste de descubrirlo después — normalmente durante la certificación, delante de un cliente — es todo lo que has tecleado.
Volviendo
La reescritura de una semana no fue una hazaña heroica. Fue el ritmo natural de un trabajo que se ha planificado. Los cuatro pasos de arriba no son únicos míos — son lo que queda cuando sacas "pensar como un arquitecto" de lo abstracto y lo pones en un escritorio, delante de un proyecto que no puede fallar.
Si alguna vez te encuentras parcheando el tercer caso inesperado en una función y alcanzando un cuarto — para. Los parches no son tu problema. El diseño que te saltaste sí.
Sobre el autor
Fundador y Director Técnico
Richin es el fundador de NeuraByte, una pequeña consultora que construye software para clientes que quieren hacerlo bien a la primera. Ha pasado más de una década en ingeniería embebida y automotriz — en microcontroladores, plataformas Linux y sistemas críticos en seguridad ISO 26262 — y escribe aquí sobre cómo esa experiencia da forma a la manera en que construye hoy.