Diseño de Código Base
Diseña módulos profundos: mucho comportamiento detrás de una interfaz pequeña, colocados en una costura limpia, testeables a través de esa interfaz. Usa este lenguaje y estos principios siempre que se esté diseñando o reestructurando código. El objetivo es apalancamiento para los llamadores, localidad para los mantenedores y testeabilidad para todos.
Glosario
Usa estos términos exactamente: no sustituyas "componente", "servicio", "API" o "límite". El lenguaje consistente es el punto central.
Módulo: cualquier cosa con una interfaz y una implementación. Deliberadamente agnóstico en escala: una función, clase, paquete o rebanada que cruza capas. Evita: unidad, componente, servicio.
Interfaz: todo lo que un llamador debe saber para usar el módulo correctamente: la firma de tipos, pero también invariantes, restricciones de orden, modos de error, configuración requerida y características de rendimiento. Evita: API, firma (son demasiado estrechas, se refieren solo a la superficie a nivel de tipos).
Implementación: lo que hay dentro de un módulo, su cuerpo de código. Distinto de Adaptador: una cosa puede ser un adaptador pequeño con una implementación grande (un repositorio Postgres) o un adaptador grande con una implementación pequeña (un simulacro en memoria). Recurre a "adaptador" cuando el tema sea la costura; "implementación" en caso contrario.
Profundidad: apalancamiento en la interfaz. La cantidad de comportamiento que un llamador (o una prueba) puede ejercer por unidad de interfaz que tiene que aprender. Un módulo es profundo cuando una gran cantidad de comportamiento se encuentra detrás de una interfaz pequeña, y superficial cuando la interfaz es casi tan compleja como la implementación.
Costura (Michael Feathers): un lugar donde puedes alterar el comportamiento sin editar en ese lugar; la ubicación donde vive la interfaz de un módulo. Dónde poner la costura es su propia decisión de diseño, distinta de lo que hay detrás. Evita: límite (sobrecargado con el contexto delimitado de DDD).
Adaptador: una cosa concreta que satisface una interfaz en una costura. Describe el rol (qué ranura llena), no la esencia (qué hay dentro).
Apalancamiento: lo que los llamadores obtienen de la profundidad. Más capacidad por unidad de interfaz que aprenden. Una implementación se amortiza en N sitios de llamada y M pruebas.
Localidad: lo que los mantenedores obtienen de la profundidad. El cambio, los errores, el conocimiento y la verificación se concentran en un solo lugar en lugar de dispersarse entre los llamadores. Arregla una vez, arreglado en todas partes.
Profundo vs Superficial
Módulo profundo = interfaz pequeña + mucha implementación:
┌─────────────────────┐
│ Interfaz Pequeña │ ← Pocos métodos, parámetros simples
├─────────────────────┤
│ │
│ Implementación │ ← Lógica compleja oculta
│ Profunda │
│ │
└─────────────────────┘
Módulo superficial = interfaz grande + poca implementación (evitar):
┌─────────────────────────────────┐
│ Interfaz Grande │ ← Muchos métodos, parámetros complejos
├─────────────────────────────────┤
│ Implementación Fina │ ← Solo pasa a través
└─────────────────────────────────┘
Al diseñar una interfaz, pregúntate:
- ¿Puedo reducir el número de métodos?
- ¿Puedo simplificar los parámetros?
- ¿Puedo ocultar más complejidad dentro?
Principios
- La profundidad es una propiedad de la interfaz, no de la implementación. Un módulo profundo puede estar internamente compuesto de partes pequeñas, simulables e intercambiables; simplemente no forman parte de la interfaz. Un módulo puede tener costuras internas (privadas a su implementación, usadas por sus propias pruebas) además de la costura externa en su interfaz.
- La prueba de eliminación. Imagina eliminar el módulo. Si la complejidad desaparece, era un paso a través. Si la complejidad reaparece en N llamadores, estaba ganando su lugar.
- La interfaz es la superficie de prueba. Los llamadores y las pruebas cruzan la misma costura. Si quieres probar más allá de la interfaz, el módulo probablemente tiene la forma incorrecta.
- Un adaptador significa una costura hipotética. Dos adaptadores significa una real. No introduzcas una costura a menos que algo realmente varíe a través de ella.
Diseñando para la testeabilidad
Las buenas interfaces hacen que las pruebas sean naturales:
-
Acepta dependencias, no las crees.
// Testeable function processOrder(order, paymentGateway) {} // Difícil de probar function processOrder(order) { const gateway = new StripeGateway(); } -
Devuelve resultados, no produzcas efectos secundarios.
// Testeable function calculateDiscount(cart): Discount {} // Difícil de probar function applyDiscount(cart): void { cart.total -= discount; } -
Superficie pequeña. Menos métodos = menos pruebas necesarias. Menos parámetros = preparación de pruebas más simple.
Relaciones
- Un Módulo tiene exactamente una Interfaz (la superficie que presenta a los llamadores y las pruebas).
- La Profundidad es una propiedad de un Módulo, medida con respecto a su Interfaz.
- Una Costura es donde vive la Interfaz de un Módulo.
- Un Adaptador se sienta en una Costura y satisface la Interfaz.
- La Profundidad produce Apalancamiento para los llamadores y Localidad para los mantenedores.
Encuadres rechazados
- Profundidad como relación entre líneas de implementación y líneas de interfaz (Ousterhout): recompensa inflar la implementación. Nosotros usamos profundidad-como-apalancamiento en su lugar.
- "Interfaz" como la palabra clave
interfacede TypeScript o los métodos públicos de una clase: demasiado estrecho: la interfaz aquí incluye todos los hechos que un llamador debe saber. - "Límite": sobrecargado con el contexto delimitado de DDD. Di costura o interfaz.
Profundizando más
- Profundizando un clúster dadas sus dependencias, consulta DEEPENING.md: categorías de dependencias, disciplina de costuras y pruebas de reemplazo en lugar de capas.
- Explorando interfaces alternativas, consulta DESIGN-IT-TWICE.md: inicia subagentes paralelos para diseñar la interfaz de varias formas radicalmente diferentes, luego compara en profundidad, localidad y colocación de la costura.