Diseño de la Base de Código
Diseña módulos profundos: mucho comportamiento detrás de una interfaz pequeña, colocada en una costura limpia, testeable 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 llamantes, 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 cuanto a escala: una función, clase, paquete o porción que abarca varios niveles. Evita: unidad, componente, servicio.
Interfaz: todo lo que un llamante 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 estrechos, 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). Usa "adaptador" cuando el tema sea la costura; "implementación" en caso contrario.
Profundidad: apalancamiento en la interfaz. La cantidad de comportamiento que un llamante (o una prueba) puede ejercer por cada 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 en la que vive la interfaz de un módulo. Dónde poner la costura es una decisión de diseño en sí misma, distinta de lo que hay detrás. Evita: límite (sobrecargado con el contexto acotado de DDD).
Adaptador: una cosa concreta que satisface una interfaz en una costura. Describe el rol (qué hueco llena), no la sustancia (qué hay dentro).
Apalancamiento: lo que los llamantes obtienen de la profundidad. Más capacidad por cada 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 distribuirse entre los llamantes. 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 en el interior?
Principios
- La profundidad es una propiedad de la interfaz, no de la implementación. Un módulo profundo puede estar compuesto internamente por partes pequeñas, simulables y intercambiables; simplemente no forman parte de la interfaz. Un módulo puede tener costuras internas (privadas a su implementación, utilizadas 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 llamantes, el módulo estaba justificando su existencia.
- La interfaz es la superficie de prueba. Los llamantes y las pruebas cruzan la misma costura. Si quieres probar detrás de la interfaz, el módulo probablemente tenga la forma incorrecta.
- Un adaptador significa una costura hipotética. Dos adaptadores significan una costura real. No introduzcas una costura a menos que algo varíe realmente a través de ella.
Diseño para la testeabilidad
Las buenas interfaces facilitan las pruebas de forma natural:
-
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 llamantes y las pruebas).
- La Profundidad es una propiedad de un Módulo, medida en relación con su Interfaz.
- Una Costura es donde vive la Interfaz de un Módulo.
- Un Adaptador se sitúa en una Costura y satisface la Interfaz.
- La Profundidad produce Apalancamiento para los llamantes y Localidad para los mantenedores.
Enfoques 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.
- "Interfaz" como la palabra clave
interfacede TypeScript o los métodos públicos de una clase: demasiado restrictivo: la interfaz aquí incluye todos los hechos que un llamante debe conocer. - "Límite": sobrecargado con el contexto acotado de DDD. Usa costura o interfaz.
Profundizando más
- Profundizar un grupo dadas sus dependencias, consulta DEEPENING.md: categorías de dependencias, disciplina de costuras y pruebas de reemplazar en lugar de estratificar.
- Explorar interfaces alternativas, consulta DESIGN-IT-TWICE.md: generar subagentes paralelos para diseñar la interfaz de varias formas radicalmente diferentes, luego comparar en cuanto a profundidad, localidad y colocación de la costura.