Add testing strategy documentation for software verification practices
This commit is contained in:
parent
fa9d95fa9e
commit
34b8f91538
61
.agents/skills/rdx-solid/SKILL.md
Normal file
61
.agents/skills/rdx-solid/SKILL.md
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
name: rdx-solid
|
||||
description: Aplica principios SOLID, TDD, código limpio, diseño de software y buenas prácticas de ingeniería al escribir código, implementar funcionalidades, refactorizar, diseñar arquitecturas, revisar código, crear pruebas o depurar errores. Prioriza soluciones correctas, sencillas, mantenibles y coherentes con el proyecto, evitando la sobreingeniería y las abstracciones innecesarias.
|
||||
---
|
||||
|
||||
# rdx-solid · Ingeniería de software
|
||||
|
||||
Desarrolla y revisa software con una complejidad proporcional a los requisitos. Este archivo establece el flujo de trabajo y orienta la consulta de las referencias; los criterios técnicos y ejemplos se mantienen en ellas.
|
||||
|
||||
## Prioridad y aplicación
|
||||
|
||||
- Respeta las instrucciones del usuario, las restricciones del proyecto, su `AGENTS.md` y las herramientas configuradas. Las preferencias de este skill se aplican cuando no exista una convención más específica.
|
||||
- Preservar la corrección, los contratos, la seguridad y el alcance de la tarea es obligatorio. Comunica cualquier limitación que impida comprobarlo.
|
||||
- TDD y las preferencias de diseño son adaptables al contexto. Las métricas, la regla de tres y los patrones son heurísticas, no requisitos numéricos ni motivos para introducir abstracciones.
|
||||
- Una excepción relevante debe tener una justificación técnica; no es necesario documentar excepciones triviales.
|
||||
|
||||
## Flujo de trabajo
|
||||
|
||||
1. **Comprender:** identifica requisitos, comportamiento existente, consumidores y archivos afectados. Contrasta los supuestos con el código y la documentación; aclara las ambigüedades que puedan cambiar el resultado.
|
||||
2. **Consultar:** selecciona las referencias de la tabla según el problema. Lee las secciones pertinentes y combina ámbitos cuando el cambio lo requiera; no cargues todos los documentos por defecto.
|
||||
3. **Implementar:** aplica la solución suficiente más sencilla y mantén los cambios dentro del alcance. Reutiliza las herramientas existentes; no aproveches la tarea para renombrar, reformatear o refactorizar áreas independientes.
|
||||
4. **Verificar y entregar:** ejecuta las pruebas y comprobaciones de tipos, análisis estático y compilación pertinentes al impacto. Revisa contratos y documentación afectados. Resume cambios, decisiones relevantes y verificaciones; identifica lo pendiente y sus motivos sin atribuir resultados a comprobaciones no ejecutadas.
|
||||
|
||||
## Referencias según la tarea
|
||||
|
||||
Cada documento desarrolla sus criterios, ejemplos y listas de comprobación. Consulta la referencia específica en lugar de reproducir su contenido en este archivo.
|
||||
|
||||
| Necesidad | Referencia |
|
||||
|---|---|
|
||||
| Ciclo TDD, triangulación, regresión y trabajo con código sin pruebas | [tdd.md](referencias/tdd.md) |
|
||||
| Estrategia de pruebas, dobles, contratos, aislamiento y asincronía | [testing.md](referencias/testing.md) |
|
||||
| Responsabilidades, extensión, sustitución, interfaces y dependencias | [principios-solid.md](referencias/principios-solid.md) |
|
||||
| Nombres, funciones, control de flujo, errores, comentarios y formato | [clean-code.md](referencias/clean-code.md) |
|
||||
| Encapsulación, contratos, composición, entidades, objetos de valor y agregados | [diseño-de-objetos.md](referencias/diseño-de-objetos.md) |
|
||||
| KISS, YAGNI, DRY, diseño simple y deuda técnica | [complejidad.md](referencias/complejidad.md) |
|
||||
| Detectar y priorizar problemas de diseño y refactorizaciones | [code-smells.md](referencias/code-smells.md) |
|
||||
| Elegir y aplicar un patrón ante una necesidad real | [patrones-de-diseño.md](referencias/patrones-de-diseño.md) |
|
||||
| Organización del sistema, límites y selección de estilos arquitectónicos | [arquitectura.md](referencias/arquitectura.md) |
|
||||
| APIs, persistencia, transacciones, seguridad y operación de servicios | [arquitectura-backend.md](referencias/arquitectura-backend.md) |
|
||||
| Componentes, estado, formularios, accesibilidad y consumo de APIs | [arquitectura-frontend.md](referencias/arquitectura-frontend.md) |
|
||||
| Contratos, esquemas, paquetes y compatibilidad entre frontend y backend | [arquitectura-shared.md](referencias/arquitectura-shared.md) |
|
||||
| Caracterización, modernización, migraciones y compatibilidad de sistemas heredados | [arquitectura-legacy.md](referencias/arquitectura-legacy.md) |
|
||||
| Comparación de alternativas, ADR y decisiones estructurales | [arquitectura-decisiones.md](referencias/arquitectura-decisiones.md) |
|
||||
|
||||
Para cambios arquitectónicos, parte de `arquitectura.md` y continúa con los ámbitos afectados. Para una corrección o refactorización, combina la guía del área con las de pruebas o diseño que resulten necesarias.
|
||||
|
||||
## Convenciones adicionales
|
||||
|
||||
Las convenciones generales de idioma, nomenclatura y formato se mantienen en `clean-code.md`, sección 9.4. Conserva las del módulo existente cuando difieran y evita normalizaciones ajenas a la tarea.
|
||||
|
||||
Cuando el proyecto no establezca otra norma:
|
||||
|
||||
- Escribe TSDoc en castellano. Usa prefijo `I` en interfaces únicamente si el área del proyecto ya sigue esa convención.
|
||||
- Guarda la documentación general en `docs/`, con nombres en castellano y `kebab-case`. Actualízala cuando cambien contratos o comportamiento; evita documentación innecesaria para cambios triviales.
|
||||
- En áreas con i18n, reutiliza sus claves y mecanismos; mantén coherentes `es` y `en` cuando ambos estén soportados.
|
||||
|
||||
## Salvaguardas transversales
|
||||
|
||||
- No modifiques código de terceros, dependencias o archivos generados salvo autorización expresa; respeta las exclusiones del proyecto y utiliza los generadores correspondientes.
|
||||
- Al contrastar claves no confiables con propiedades propias, utiliza `Object.hasOwn(...)` o `Object.prototype.hasOwnProperty.call(...)`. No uses `in` si las propiedades heredadas no deben admitirse; para `Map`, utiliza `has(...)`.
|
||||
- Cuando una tarea deba continuar con información incompleta, elige una alternativa conservadora que preserve el comportamiento existente y declara los supuestos materiales. No inventes requisitos, contratos ni resultados de verificación.
|
||||
176
.agents/skills/rdx-solid/referencias/arquitectura-backend.md
Normal file
176
.agents/skills/rdx-solid/referencias/arquitectura-backend.md
Normal file
@ -0,0 +1,176 @@
|
||||
# Arquitectura backend
|
||||
|
||||
> Referencia especializada del skill `rdx-solid`. Consulta primero [arquitectura.md](arquitectura.md) y aplica las convenciones de `SKILL.md` y `AGENTS.md` del proyecto.
|
||||
|
||||
## 1. Propósito y alcance
|
||||
|
||||
Este documento orienta el diseño y evolución de servicios backend, APIs, procesos asíncronos, persistencia e integraciones. Está pensado principalmente para TypeScript y Node.js, pero sus principios son transferibles a otros entornos.
|
||||
|
||||
**Objetivo:** construir servicios correctos, seguros, verificables y mantenibles con una complejidad proporcional a sus requisitos. No impongas arquitecturas hexagonales, DDD ni una capa por concepto cuando no aporten valor.
|
||||
|
||||
## 2. Reconocer la arquitectura existente
|
||||
|
||||
Antes de modificar código:
|
||||
|
||||
1. Identifica framework, estructura modular, puntos de entrada y convenciones.
|
||||
2. Localiza los casos de uso, modelos, contratos y consumidores afectados.
|
||||
3. Examina gestión de errores, autenticación, persistencia y pruebas existentes.
|
||||
4. Comprueba si el cambio afecta a contratos públicos, datos persistidos o transacciones.
|
||||
5. Limita las modificaciones al ámbito de la tarea.
|
||||
|
||||
Para sistemas heredados, consulta además [arquitectura-legacy.md](arquitectura-legacy.md). Para decisiones estructurales importantes, consulta [arquitectura-decisiones.md](arquitectura-decisiones.md).
|
||||
|
||||
## 3. Organización modular y responsabilidades
|
||||
|
||||
Prefiere módulos funcionales cuando faciliten la cohesión, pero conserva la organización del proyecto si ya resulta adecuada. Un módulo puede combinar presentación, aplicación, dominio e infraestructura sin exigir directorios físicos para todas las capas.
|
||||
|
||||
```text
|
||||
src/
|
||||
modules/
|
||||
orders/
|
||||
order-routes.ts
|
||||
create-order.ts
|
||||
order.ts
|
||||
order-repository.ts
|
||||
postgres-order-repository.ts
|
||||
order-contracts.ts
|
||||
customers/
|
||||
...
|
||||
infrastructure/
|
||||
database/
|
||||
logging/
|
||||
```
|
||||
|
||||
Esta distribución es ilustrativa. Divide un módulo cuando su tamaño o responsabilidades reales lo justifiquen, no porque falten directorios de una plantilla.
|
||||
|
||||
- **Transporte/presentación:** adapta HTTP, CLI, mensajería o RPC a entradas y respuestas de aplicación.
|
||||
- **Aplicación:** coordina casos de uso, autorización específica y límites transaccionales.
|
||||
- **Dominio:** aplica reglas, estados e invariantes.
|
||||
- **Infraestructura:** implementa persistencia, proveedores e integraciones externas.
|
||||
|
||||
Evita controladores que contengan toda la lógica de negocio y servicios que solo deleguen a otros servicios sin responsabilidad concreta. No coloques en `shared` reglas de negocio cuya propiedad corresponde a un módulo.
|
||||
|
||||
## 4. APIs y contratos
|
||||
|
||||
- Diseña endpoints y operaciones alrededor de recursos y casos de uso comprensibles.
|
||||
- Define formatos de entrada, salida y error explícitos; no expongas entidades de base de datos por conveniencia.
|
||||
- Valida datos no confiables en el límite de entrada. Los tipos TypeScript no validan JSON en ejecución.
|
||||
- Utiliza códigos HTTP, semántica de errores y cabeceras apropiados; no devuelvas éxito para ocultar fallos.
|
||||
- Evalúa paginación, límites, filtrado y orden estable en listados potencialmente grandes.
|
||||
- Considera versionado y compatibilidad cuando haya consumidores independientes.
|
||||
- No presupongas que frontend y backend se despliegan simultáneamente.
|
||||
|
||||
```typescript
|
||||
interface CreateOrderRequest {
|
||||
customerId: string;
|
||||
items: Array<{ productId: string; quantity: number }>;
|
||||
}
|
||||
|
||||
async function createOrderHandler(request: HttpRequest): Promise<HttpResponse> {
|
||||
const input = createOrderSchema.parse(request.body);
|
||||
const result = await createOrder.execute(input);
|
||||
return { status: 201, body: { orderId: result.orderId } };
|
||||
}
|
||||
```
|
||||
|
||||
El esquema, `HttpRequest`, `HttpResponse` y el caso de uso son colaboradores ilustrativos. Adapta los contratos a las herramientas del proyecto y no introduzcas validadores duplicados sin necesidad.
|
||||
|
||||
Consulta [arquitectura-shared.md](arquitectura-shared.md) al publicar tipos o esquemas consumidos por frontend.
|
||||
|
||||
## 5. Persistencia y transacciones
|
||||
|
||||
- Modela el acceso a datos según las necesidades del dominio; no crees repositorios genéricos de forma automática.
|
||||
- Mantén SQL y detalles de drivers fuera de las reglas de negocio cuando su acoplamiento dificulte evolucionar el sistema.
|
||||
- Utiliza transacciones cuando varias escrituras deban confirmarse o revertirse como una unidad.
|
||||
- Diseña restricciones de integridad también en la base de datos cuando corresponda.
|
||||
- Define migraciones reproducibles, compatibles y con procedimientos de recuperación cuando el riesgo lo exija.
|
||||
- Evita consultas N+1, cargas excesivas e índices especulativos: mide y evalúa el uso real.
|
||||
- No prometas atomicidad entre base de datos y servicios externos sin un mecanismo que la garantice.
|
||||
|
||||
```typescript
|
||||
async function confirmOrder(orderId: OrderId): Promise<void> {
|
||||
await database.transaction(async (tx) => {
|
||||
const order = await orderRepository.findForUpdate(tx, orderId);
|
||||
order.confirm();
|
||||
await orderRepository.save(tx, order);
|
||||
await outboxRepository.enqueue(tx, {
|
||||
type: 'ORDER_CONFIRMED',
|
||||
orderId: orderId.value,
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
El ejemplo requiere implementaciones transaccionales compatibles. Outbox es útil cuando hay que publicar eventos fiables tras una confirmación, pero no es obligatorio en sistemas que no lo necesitan.
|
||||
|
||||
## 6. Seguridad y aislamiento
|
||||
|
||||
- Distingue **autenticación** (identidad) de **autorización** (permiso para una operación o recurso).
|
||||
- Autoriza en el servidor, incluso si la interfaz oculta acciones no permitidas.
|
||||
- En sistemas multiempresa, comprueba el ámbito de datos y permisos en cada operación relevante; no confíes en un `tenantId` enviado por el cliente sin validación.
|
||||
- Usa consultas parametrizadas y controles contra inyección.
|
||||
- Protege secretos mediante mecanismos adecuados; nunca los incluyas en código ni registros.
|
||||
- Valida límites de tamaño, cargas, archivos y tipos de contenido.
|
||||
- Aplica privilegio mínimo a cuentas técnicas y conexiones.
|
||||
- Gestiona información sensible y errores sin filtrar datos internos.
|
||||
|
||||
No elimines controles existentes para simplificar los casos de uso o las pruebas.
|
||||
|
||||
## 7. Asincronía, concurrencia y resiliencia
|
||||
|
||||
- Usa correctamente `async/await`; evita promesas no esperadas salvo trabajo explícitamente desacoplado y supervisado.
|
||||
- Establece plazos de espera, cancelación y tratamiento de fallos en llamadas remotas.
|
||||
- Diseña reintentos solo para operaciones seguras o idempotentes, con límites y política de espera.
|
||||
- Considera claves de idempotencia para operaciones que puedan repetirse por red o colas.
|
||||
- Evita operaciones bloqueantes costosas en el hilo de eventos de Node.js.
|
||||
- Define consistencia, orden, duplicados y errores para consumidores de mensajes.
|
||||
- Protege cambios concurrentes cuando pueden vulnerar invariantes (bloqueos, versiones u otras técnicas adecuadas).
|
||||
- Distingue fallos transitorios de fallos permanentes; no reintentes indiscriminadamente.
|
||||
|
||||
## 8. Observabilidad y operación
|
||||
|
||||
- Usa logging estructurado donde aporte utilidad diagnóstica.
|
||||
- Conserva identificadores de correlación cuando existan flujos entre servicios.
|
||||
- Expón métricas y trazas proporcionales a la criticidad y operación.
|
||||
- Prepara comprobaciones de salud que reflejen correctamente el estado del servicio.
|
||||
- Cierra conexiones y recursos de forma controlada.
|
||||
- Evita registrar credenciales, tokens, datos personales o grandes cargas completas.
|
||||
- Define procedimientos de despliegue, migración y reversión cuando correspondan.
|
||||
|
||||
## 9. Pruebas y verificación
|
||||
|
||||
Elige pruebas según el riesgo, no por cuotas fijas:
|
||||
|
||||
- **Dominio:** reglas, invariantes y casos límite mediante pruebas rápidas.
|
||||
- **Aplicación:** coordinación, permisos, tratamiento de errores y transacciones.
|
||||
- **Infraestructura:** integración real con base de datos, consultas y adaptadores.
|
||||
- **API:** validación, autenticación, autorización y contratos.
|
||||
- **E2E:** flujos críticos y efectos entre componentes.
|
||||
|
||||
Prueba especialmente los casos de concurrencia, idempotencia y fallo parcial cuando existan. Consulta [testing.md](testing.md) y [tdd.md](tdd.md).
|
||||
|
||||
## 10. Señales de alerta
|
||||
|
||||
- Controladores con reglas de negocio extensas.
|
||||
- Entidades que importan directamente controladores o detalles de HTTP.
|
||||
- Transacciones repartidas entre operaciones sin un responsable definido.
|
||||
- Servicios que acceden a datos de otra empresa sin comprobar ámbito y permisos.
|
||||
- Promesas sin supervisión y errores capturados y descartados.
|
||||
- Operaciones de pago, envío o notificación repetibles sin garantías definidas.
|
||||
- Repositorios, interfaces y capas creados únicamente por plantilla.
|
||||
- Un cambio pequeño que obliga a modificar muchos módulos sin motivo real.
|
||||
|
||||
Cada señal exige análisis, no refactorización automática.
|
||||
|
||||
## 11. Lista de comprobación
|
||||
|
||||
- [ ] ¿He identificado el contrato y los consumidores afectados?
|
||||
- [ ] ¿Las reglas de negocio están en el componente apropiado?
|
||||
- [ ] ¿Las entradas externas se validan en ejecución?
|
||||
- [ ] ¿La autorización se comprueba en el servidor y en el ámbito correcto?
|
||||
- [ ] ¿Las escrituras y llamadas externas tienen garantías claras ante errores?
|
||||
- [ ] ¿He considerado concurrencia e idempotencia cuando corresponden?
|
||||
- [ ] ¿Se respeta el alcance y la estructura del proyecto?
|
||||
- [ ] ¿Se han ejecutado las verificaciones pertinentes y comunicado las pendientes?
|
||||
|
||||
**Principio final:** el backend debe preservar invariantes, contratos y seguridad con el mínimo acoplamiento y complejidad justificados.
|
||||
191
.agents/skills/rdx-solid/referencias/arquitectura-decisiones.md
Normal file
191
.agents/skills/rdx-solid/referencias/arquitectura-decisiones.md
Normal file
@ -0,0 +1,191 @@
|
||||
# Decisiones arquitectónicas y ADR
|
||||
|
||||
> Referencia especializada del skill `rdx-solid`. Consulta [arquitectura.md](arquitectura.md) y las referencias del ámbito técnico afectado.
|
||||
|
||||
## 1. Propósito
|
||||
|
||||
Establecer un procedimiento ligero para elegir tecnologías, contratos y estructuras con criterios explícitos. No todas las decisiones requieren un ADR; las decisiones importantes deben poder explicarse y revisarse.
|
||||
|
||||
**Objetivo:** seleccionar una solución suficiente y sostenible para los requisitos reales, documentando sus compromisos sin burocracia innecesaria.
|
||||
|
||||
## 2. Cuándo realizar una evaluación formal
|
||||
|
||||
Considérala cuando una decisión tenga impacto significativo en:
|
||||
|
||||
- Compatibilidad de contratos o datos persistidos.
|
||||
- Dependencias transversales y límites entre módulos.
|
||||
- Seguridad, rendimiento, disponibilidad o resiliencia.
|
||||
- Costes operativos o bloqueo con un proveedor.
|
||||
- Despliegue y capacidad de reversión.
|
||||
- Trabajo de varios equipos o evolución a medio plazo.
|
||||
|
||||
Para ajustes internos locales, una breve justificación técnica puede ser suficiente.
|
||||
|
||||
## 3. Proceso de decisión
|
||||
|
||||
1. **Definir el problema:** qué requisito o problema observado exige decidir.
|
||||
2. **Establecer restricciones:** compatibilidad, seguridad, equipo, presupuesto, plazo y entorno.
|
||||
3. **Identificar alternativas viables:** incluye mantener la solución actual cuando tenga sentido.
|
||||
4. **Comparar compromisos:** simplicidad, riesgo, operación, flexibilidad y migración.
|
||||
5. **Seleccionar:** justificar la opción frente a las alternativas.
|
||||
6. **Validar:** prototipo, medición o prueba dirigida si existe incertidumbre material.
|
||||
7. **Documentar:** registrar alcance, consecuencias y condiciones de revisión.
|
||||
|
||||
No simules un análisis exhaustivo si no hay datos: separa lo comprobado de lo supuesto.
|
||||
|
||||
## 4. Criterios de comparación
|
||||
|
||||
| Criterio | Preguntas |
|
||||
|---|---|
|
||||
| Adecuación funcional | ¿Resuelve los requisitos y restricciones reales? |
|
||||
| Complejidad | ¿Cuánto código, infraestructura y conocimiento añade? |
|
||||
| Compatibilidad | ¿Qué consumidores y formatos deben cambiar? |
|
||||
| Seguridad | ¿Qué superficie de riesgo introduce? |
|
||||
| Operación | ¿Cómo se despliega, observa y recupera? |
|
||||
| Rendimiento | ¿Existen mediciones o límites cuantificados? |
|
||||
| Equipo | ¿Se puede mantener con las capacidades disponibles? |
|
||||
| Coste | ¿Licencias, infraestructura, soporte y migración? |
|
||||
| Reversibilidad | ¿Puede sustituirse o revertirse razonablemente? |
|
||||
|
||||
No asignes ponderaciones numéricas arbitrarias ni presentes estimaciones inciertas como medidas reales.
|
||||
|
||||
## 5. Decisiones especialmente sensibles
|
||||
|
||||
### Arquitectura y modularidad
|
||||
|
||||
Antes de introducir microservicios, Clean Architecture o una nueva capa, comprueba que resuelve un problema real mejor que un monolito modular o una solución directa.
|
||||
|
||||
### Persistencia
|
||||
|
||||
Evalúa integridad, transacciones, consultas reales, capacidades del equipo, migraciones, backup y recuperación. No selecciones un motor solo por tendencias.
|
||||
|
||||
### Dependencias externas
|
||||
|
||||
Comprueba mantenimiento, compatibilidad, licencias, seguridad, tamaño, costes y alternativas nativas o ya instaladas. No agregues bibliotecas para operaciones triviales.
|
||||
|
||||
### Contratos compartidos
|
||||
|
||||
Evalúa versionado, consumidores desplegados, migración y fuente de verdad. Consulta [arquitectura-shared.md](arquitectura-shared.md).
|
||||
|
||||
### Legacy
|
||||
|
||||
Contrasta modernización gradual y sustitución; incluye paridad funcional, datos y reversión. Consulta [arquitectura-legacy.md](arquitectura-legacy.md).
|
||||
|
||||
## 6. Architecture Decision Records (ADR)
|
||||
|
||||
Un ADR registra una decisión arquitectónica significativa y los motivos por los que se tomó. No debe convertirse en una especificación técnica interminable.
|
||||
|
||||
### Cuándo crear un ADR
|
||||
|
||||
- Nuevo motor de base de datos o estrategia de persistencia.
|
||||
- Cambio en límites modulares o dirección de dependencias.
|
||||
- Selección de comunicación entre servicios.
|
||||
- Cambio importante de autenticación o autorización.
|
||||
- Decisión de migración con impacto sustancial.
|
||||
- Dependencia estratégica que comprometa mantenimiento y operación.
|
||||
|
||||
No crees ADR para cambios de nombres, pequeñas funciones o refactorizaciones locales sin impacto arquitectónico.
|
||||
|
||||
### Plantilla sugerida
|
||||
|
||||
```markdown
|
||||
# ADR 001: Título descriptivo
|
||||
|
||||
## Estado
|
||||
Propuesta | Aceptada | Sustituida | Descartada
|
||||
|
||||
## Contexto
|
||||
Problema, requisitos y restricciones relevantes.
|
||||
|
||||
## Alternativas consideradas
|
||||
- Alternativa A: ventajas y costes.
|
||||
- Alternativa B: ventajas y costes.
|
||||
- Mantener situación actual, si es viable.
|
||||
|
||||
## Decisión
|
||||
Opción elegida y razones verificables.
|
||||
|
||||
## Consecuencias
|
||||
Beneficios, riesgos, migración, operación y costes.
|
||||
|
||||
## Verificación y revisión
|
||||
Cómo se comprobará y qué condiciones obligarían a revisarla.
|
||||
```
|
||||
|
||||
### Ejemplo breve
|
||||
|
||||
```markdown
|
||||
# ADR 002: PostgreSQL para persistencia transaccional
|
||||
|
||||
## Estado
|
||||
Aceptada
|
||||
|
||||
## Contexto
|
||||
Se requieren transacciones, integridad relacional y consultas conocidas.
|
||||
|
||||
## Alternativas consideradas
|
||||
PostgreSQL, MySQL y mantener el motor actual.
|
||||
|
||||
## Decisión
|
||||
Adoptar PostgreSQL debido a requisitos concretos y experiencia del equipo.
|
||||
|
||||
## Consecuencias
|
||||
Migraciones, operación, copias de seguridad y adaptación de repositorios.
|
||||
|
||||
## Verificación y revisión
|
||||
Pruebas de integración, carga representativa y procedimiento de recuperación.
|
||||
```
|
||||
|
||||
No afirmes que una tecnología es superior sin evidencia contextual suficiente.
|
||||
|
||||
## 7. Cómo organizar ADR
|
||||
|
||||
- Guarda las decisiones donde indique el proyecto; si no existe convención, considera `docs/adr/`.
|
||||
- Utiliza nombres claros y estables, por ejemplo `001-persistencia-postgresql.md`.
|
||||
- Conserva los ADR históricos y marca los sustituidos en vez de reescribir el historial sin explicarlo.
|
||||
- Enlaza decisiones relacionadas y responsables cuando el proceso del proyecto lo requiera.
|
||||
- No dupliques documentación técnica extensa que ya existe en otros documentos.
|
||||
|
||||
## 8. Experimentos y mediciones
|
||||
|
||||
Para decisiones con incertidumbre material:
|
||||
|
||||
- Formula una hipótesis comprobable.
|
||||
- Define criterios de aceptación y escenarios representativos.
|
||||
- Mide antes de concluir sobre rendimiento o coste.
|
||||
- Diferencia datos reales de estimaciones.
|
||||
- Limita prototipos a aquello que deba validarse.
|
||||
- No conviertas el prototipo en implementación de producción sin revisar seguridad, errores y operación.
|
||||
|
||||
## 9. Evolución y reversión
|
||||
|
||||
- Identifica consumidores, contratos y datos afectados.
|
||||
- Planifica migración y coexistencia de versiones cuando corresponda.
|
||||
- Establece condiciones de reversión si el riesgo lo exige.
|
||||
- Prevé pruebas y observabilidad para comprobar el resultado.
|
||||
- Revisa decisiones cuando cambien requisitos o aparezca evidencia relevante.
|
||||
|
||||
No cambies una decisión vigente únicamente porque otra solución sea más moderna.
|
||||
|
||||
## 10. Conducta del agente de programación
|
||||
|
||||
- No inventes requisitos ni datos técnicos no comprobados.
|
||||
- Expón supuestos materiales si la tarea debe continuar sin aclaración.
|
||||
- Distingue propuesta, decisión aprobada e implementación realizada.
|
||||
- No modifiques grandes áreas del sistema para aplicar unilateralmente una preferencia arquitectónica.
|
||||
- Respeta `AGENTS.md`, contratos y restricciones específicas del proyecto.
|
||||
- Comunica alternativas y riesgos de forma proporcional a la importancia de la decisión.
|
||||
- Nunca declares verificados resultados de pruebas o benchmarks que no se hayan ejecutado.
|
||||
|
||||
## 11. Lista de comprobación
|
||||
|
||||
- [ ] ¿El problema y las restricciones están claramente definidos?
|
||||
- [ ] ¿Se consideró mantener la solución actual?
|
||||
- [ ] ¿Las alternativas son viables en este proyecto?
|
||||
- [ ] ¿Se compararon costes, riesgos y mantenibilidad?
|
||||
- [ ] ¿Se ha diferenciado evidencia de hipótesis?
|
||||
- [ ] ¿La decisión preserva contratos y requisitos relevantes?
|
||||
- [ ] ¿Necesita ADR o basta una justificación breve?
|
||||
- [ ] ¿Se definieron verificación y revisión cuando correspondía?
|
||||
|
||||
**Principio final:** una buena decisión arquitectónica es explícita, contextual, verificable y proporcional al problema que resuelve.
|
||||
144
.agents/skills/rdx-solid/referencias/arquitectura-frontend.md
Normal file
144
.agents/skills/rdx-solid/referencias/arquitectura-frontend.md
Normal file
@ -0,0 +1,144 @@
|
||||
# Arquitectura frontend
|
||||
|
||||
> Referencia especializada del skill `rdx-solid`. Consulta [arquitectura.md](arquitectura.md) y respeta las convenciones de `SKILL.md` y `AGENTS.md`.
|
||||
|
||||
## 1. Propósito
|
||||
|
||||
Organizar interfaces mantenibles, accesibles y verificables. Los ejemplos están orientados a React y TypeScript; adapta los criterios al framework utilizado. No reproduzcas automáticamente las capas del backend dentro del navegador.
|
||||
|
||||
## 2. Partir de la estructura del proyecto
|
||||
|
||||
Antes de modificar componentes, examina: enrutado, diseño visual, biblioteca de UI, convención de carpetas, gestión de estado, formularios, consumo de APIs, i18n y pruebas. Respeta los límites existentes y evita migraciones de arquitectura no solicitadas.
|
||||
|
||||
## 3. Organización por funcionalidades
|
||||
|
||||
Agrupa componentes, hooks, servicios y tipos de una funcionalidad cuando ello reduzca la dispersión. No crees todas las subcarpetas por defecto.
|
||||
|
||||
```text
|
||||
src/
|
||||
features/
|
||||
orders/
|
||||
components/
|
||||
order-list.tsx
|
||||
order-form-dialog.tsx
|
||||
hooks/
|
||||
use-orders.ts
|
||||
api/
|
||||
orders-client.ts
|
||||
index.ts
|
||||
shared/
|
||||
ui/
|
||||
hooks/
|
||||
```
|
||||
|
||||
- `shared/` contiene elementos realmente reutilizables, no un cajón para código sin dueño.
|
||||
- Mantén públicas solo las APIs que otros módulos deban consumir; evita importar detalles internos entre funcionalidades.
|
||||
- No crees un componente por cada etiqueta HTML: la extracción debe representar una responsabilidad, reutilización o complejidad significativa.
|
||||
- No concentres numerosos componentes independientes en un solo archivo si dificulta comprenderlos o mantenerlos.
|
||||
- Modales y diálogos con lógica propia deben tender a componentes separados; un cuadro trivial puede permanecer local cuando resulte más claro.
|
||||
|
||||
## 4. Componentes y responsabilidades
|
||||
|
||||
- Prefiere composición sobre jerarquías de herencia de componentes.
|
||||
- Mantén la lógica compleja de dominio fuera de la representación visual.
|
||||
- Utiliza props explícitas y contratos de componentes comprensibles.
|
||||
- Evita convertir cada componente en un conjunto de capas y controladores.
|
||||
- Extrae hooks cuando encapsulen lógica reactiva reutilizable o significativa; no para ocultar una única llamada simple.
|
||||
- No introduzcas renderizado derivado con estado duplicado cuando pueda calcularse a partir de props o estado existente.
|
||||
|
||||
```tsx
|
||||
interface OrderSummaryProps {
|
||||
order: OrderSummary;
|
||||
onSelect: (orderId: string) => void;
|
||||
}
|
||||
|
||||
export function OrderSummaryCard({ order, onSelect }: OrderSummaryProps) {
|
||||
return (
|
||||
<article>
|
||||
<h3>{order.reference}</h3>
|
||||
<button type="button" onClick={() => onSelect(order.id)}>
|
||||
Ver pedido
|
||||
</button>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
El ejemplo presupone un tipo de presentación `OrderSummary` y una interfaz accesible que puede requerir más detalles según el diseño.
|
||||
|
||||
## 5. Estado y datos remotos
|
||||
|
||||
Distingue:
|
||||
|
||||
- **Estado local:** interacción de un componente o pequeño subárbol.
|
||||
- **Estado compartido de UI:** necesidades de varios componentes relacionados.
|
||||
- **Estado remoto:** datos obtenidos del servidor, con carga, error y actualización.
|
||||
- **Estado derivado:** valores calculables que normalmente no deben almacenarse por duplicado.
|
||||
|
||||
No introduzcas un gestor global de estado para resolver un formulario aislado. Para datos remotos, reutiliza las bibliotecas de consulta, caché e invalidación que ya utiliza el proyecto. Evita duplicar en estado global la caché del servidor sin motivo.
|
||||
|
||||
Controla carreras entre solicitudes, cancelación, refresco y estados vacíos cuando corresponda.
|
||||
|
||||
## 6. Formularios y cuadros de diálogo
|
||||
|
||||
- Conserva validaciones de experiencia de usuario en frontend y la validación autoritativa en backend.
|
||||
- Muestra errores comprensibles y asociados al campo o acción pertinente.
|
||||
- Considera guardado pendiente, reintentos, cambios sin guardar y envío duplicado.
|
||||
- Mantén los formularios complejos y modales con responsabilidad propia en componentes independientes.
|
||||
- Respeta foco, navegación por teclado, cierre, etiquetas y mensajes accesibles.
|
||||
- Reutiliza los componentes del sistema de diseño y no edites paquetes de terceros para implementar personalizaciones locales.
|
||||
|
||||
## 7. Acceso a APIs y contratos
|
||||
|
||||
- Centraliza donde corresponda la adaptación de respuestas, autenticación y errores de transporte.
|
||||
- No expongas directamente modelos internos de la base de datos como modelos de UI.
|
||||
- Maneja explícitamente carga, vacío, éxito y error.
|
||||
- No confíes en tipos estáticos para garantizar la forma de datos remotos en ejecución.
|
||||
- Evita mezclar llamadas HTTP y transformación compleja dentro de componentes de presentación.
|
||||
- No asumas que frontend y backend se actualizan al mismo tiempo; mantén compatibilidad según sus contratos.
|
||||
|
||||
Consulta [arquitectura-shared.md](arquitectura-shared.md) para DTO, tipos, esquemas y generación de clientes.
|
||||
|
||||
## 8. Accesibilidad, i18n y seguridad
|
||||
|
||||
- Utiliza HTML semántico, etiquetas, navegación por teclado y gestión de foco.
|
||||
- Prueba estados accesibles de carga, error y modales.
|
||||
- No insertes textos literales en áreas gobernadas por i18n; conserva `es` y `en` cuando sean idiomas soportados.
|
||||
- Trata contenido no confiable de forma segura; evita inyección de HTML.
|
||||
- No almacenes secretos de servidor en código distribuido al navegador.
|
||||
- La ocultación de botones no sustituye la autorización del backend.
|
||||
|
||||
## 9. Rendimiento
|
||||
|
||||
Optimiza basándote en evidencia: identifica re-renderizados costosos, bundles grandes, rutas pesadas y solicitudes redundantes. No añadas memoización a todos los componentes por rutina. Utiliza división de código, virtualización o carga diferida cuando el volumen real lo requiera. Mantén claves estables en listas.
|
||||
|
||||
## 10. Pruebas
|
||||
|
||||
- Prueba comportamientos percibidos por el usuario en lugar de detalles de implementación.
|
||||
- Comprueba formularios, validaciones, errores, roles y accesibilidad relevante.
|
||||
- Utiliza pruebas de componentes e integración para flujos locales; E2E para recorridos críticos.
|
||||
- No hagas que pruebas de UI dependan innecesariamente de detalles internos de hooks.
|
||||
- Verifica los contratos de API cuando cambien.
|
||||
|
||||
## 11. Señales de alerta
|
||||
|
||||
- Componentes enormes con estado, transporte y reglas mezclados.
|
||||
- Estado remoto duplicado en varios almacenes.
|
||||
- Hooks extraídos sin responsabilidad concreta.
|
||||
- `shared/` que contiene reglas exclusivas de una funcionalidad.
|
||||
- Múltiples diálogos complejos incrustados en un componente contenedor.
|
||||
- Uso de `useEffect` para derivar valores calculables directamente.
|
||||
- Lógica de autorización exclusivamente en frontend.
|
||||
- Optimizaciones preventivas que dificultan la lectura.
|
||||
|
||||
## 12. Lista de comprobación
|
||||
|
||||
- [ ] ¿La modificación respeta las convenciones de React y del repositorio?
|
||||
- [ ] ¿Cada componente y hook mantiene una responsabilidad clara?
|
||||
- [ ] ¿El estado pertenece al ámbito apropiado?
|
||||
- [ ] ¿Los formularios y diálogos son accesibles y verificables?
|
||||
- [ ] ¿Se respetan i18n, seguridad y contratos de API?
|
||||
- [ ] ¿He evitado abstracciones o librerías nuevas sin necesidad?
|
||||
- [ ] ¿Las pruebas cubren el comportamiento modificado?
|
||||
|
||||
**Principio final:** el frontend debe expresar flujos de usuario claros sin convertir la interfaz en un conjunto de abstracciones innecesarias.
|
||||
143
.agents/skills/rdx-solid/referencias/arquitectura-legacy.md
Normal file
143
.agents/skills/rdx-solid/referencias/arquitectura-legacy.md
Normal file
@ -0,0 +1,143 @@
|
||||
# Arquitectura y evolución de sistemas heredados (Legacy)
|
||||
|
||||
> Referencia especializada del skill `rdx-solid`. Consulta [arquitectura.md](arquitectura.md) y el documento del ámbito afectado: [arquitectura-backend.md](arquitectura-backend.md), [arquitectura-frontend.md](arquitectura-frontend.md) o [arquitectura-shared.md](arquitectura-shared.md).
|
||||
|
||||
## 1. Propósito
|
||||
|
||||
Modernizar y mantener sistemas existentes con seguridad, preservando comportamientos y contratos requeridos. «Legacy» no significa necesariamente código de baja calidad: puede ser software estable, valioso y escasamente documentado.
|
||||
|
||||
**Prioridad:** comprender antes de cambiar; verificar antes de declarar el cambio correcto. No imponer patrones o nomenclaturas nuevas de forma masiva.
|
||||
|
||||
## 2. Reconocimiento inicial
|
||||
|
||||
1. Identifica límites del sistema, ejecutables, servicios y consumidores.
|
||||
2. Revisa dependencias, versiones, compilación y despliegue.
|
||||
3. Localiza contratos públicos, formatos de intercambio y persistencia.
|
||||
4. Identifica comportamientos implícitos, trabajos programados y efectos secundarios.
|
||||
5. Comprueba las pruebas existentes y los mecanismos de recuperación.
|
||||
6. Delimita la tarea, sus riesgos y los componentes afectados.
|
||||
|
||||
No presupongas que un comportamiento aparentemente extraño es un error: puede ser un requisito histórico o un contrato con consumidores externos.
|
||||
|
||||
## 3. Caracterización del comportamiento
|
||||
|
||||
Cuando falten especificaciones o pruebas, utiliza ejemplos de entrada y salida, datos anonimizados, consultas, trazas o pruebas de caracterización para describir el comportamiento actual.
|
||||
|
||||
Distingue:
|
||||
|
||||
- **Comportamiento a conservar:** contratos y resultados vigentes necesarios.
|
||||
- **Defecto a corregir:** fallo confirmado respecto al comportamiento esperado.
|
||||
- **Cambio funcional:** nuevo requisito que modifica la semántica.
|
||||
|
||||
No conviertas automáticamente un fallo conocido en una obligación permanente mediante una prueba de caracterización.
|
||||
|
||||
## 4. Refactorización incremental
|
||||
|
||||
- Realiza cambios pequeños y reversibles cuando sea posible.
|
||||
- Separa las correcciones funcionales de las refactorizaciones generales.
|
||||
- Introduce pruebas de regresión en torno a comportamientos críticos cuando sea viable.
|
||||
- No renombres, reformatees o muevas grandes áreas ajenas a la tarea.
|
||||
- Conserva convenciones y límites de módulos existentes salvo necesidad justificada.
|
||||
- Utiliza adaptadores temporales si permiten migrar consumidores gradualmente.
|
||||
- Evita reescrituras completas sin evaluación de costes, riesgos y continuidad del servicio.
|
||||
|
||||
La ausencia de pruebas no prohíbe todo cambio: exige mecanismos de verificación proporcionales, mayor cautela y documentación de incertidumbres.
|
||||
|
||||
## 5. Estrategias de modernización
|
||||
|
||||
| Estrategia | Cuándo considerarla | Riesgo a controlar |
|
||||
|---|---|---|
|
||||
| Refactorización localizada | Corregir estructura sin cambiar contratos | Regresiones involuntarias |
|
||||
| Adaptador / fachada | Aislar un contrato antiguo | Traducción semántica incorrecta |
|
||||
| Capa anticorrupción | Separar modelos de dominio antiguo y nuevo | Complejidad de mapeo |
|
||||
| Strangler Fig | Sustituir capacidades progresivamente | Enrutamiento y coexistencia |
|
||||
| Migración por etapas | Cambiar persistencia o infraestructura | Consistencia y reversión |
|
||||
| Sustitución completa | Sistema realmente inviable de mantener | Alcance, datos y paridad funcional |
|
||||
|
||||
Escoge la opción con menor riesgo y coste total razonable; no asumas que reescribir equivale a mejorar.
|
||||
|
||||
## 6. Capas de adaptación y anticorrupción
|
||||
|
||||
Una capa anticorrupción evita que el nuevo dominio adopte conceptos o errores del modelo antiguo.
|
||||
|
||||
```typescript
|
||||
interface CustomerDirectory {
|
||||
findCustomer(id: CustomerId): Promise<Customer | null>;
|
||||
}
|
||||
|
||||
class LegacyCustomerAdapter implements CustomerDirectory {
|
||||
constructor(private readonly legacyApi: LegacyCustomerApi) {}
|
||||
|
||||
async findCustomer(id: CustomerId): Promise<Customer | null> {
|
||||
const legacyRecord = await this.legacyApi.readCustomer(id.value);
|
||||
return legacyRecord ? mapLegacyCustomer(legacyRecord) : null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Los tipos y colaboradores son ilustrativos. Verifica diferencias de identificación, nulos, fechas, moneda, errores y codificación: un mapeo estructural no garantiza equivalencia semántica.
|
||||
|
||||
No crees una capa anticorrupción cuando un adaptador sencillo sea suficiente.
|
||||
|
||||
## 7. Migración de datos y persistencia
|
||||
|
||||
- Determina esquemas, claves, restricciones, volumen y dependencias.
|
||||
- Diseña migraciones reproducibles con validaciones de integridad.
|
||||
- Evalúa ventanas de mantenimiento, backups y recuperación.
|
||||
- Considera expansión-contracción para cambios de esquema compatibles.
|
||||
- Si existe doble escritura, define fallos, reconciliación y fuente de verdad; no la adoptes sin necesidad.
|
||||
- Verifica volumen, rendimiento y compatibilidad con versiones desplegadas.
|
||||
- Evita transformaciones destructivas sin un procedimiento probado o adecuado de recuperación.
|
||||
|
||||
## 8. Contratos, compatibilidad y despliegue
|
||||
|
||||
Identifica qué consumidores pueden quedar afectados: clientes externos, informes, procesos por lotes, integraciones, archivos de intercambio, aplicaciones móviles o servicios anteriores.
|
||||
|
||||
- Mantén formatos y significado de campos cuando sea requerido.
|
||||
- Utiliza versionado o adaptadores si los consumidores no pueden migrar a la vez.
|
||||
- Controla coexistencia de versiones y despliegues escalonados.
|
||||
- Define condiciones de reversión cuando el riesgo lo exija.
|
||||
- No elimines endpoints o campos aparentemente no usados sin verificar consumidores externos.
|
||||
|
||||
## 9. Dependencias y tecnologías obsoletas
|
||||
|
||||
- Determina compatibilidad y riesgos de seguridad de runtimes, librerías y drivers.
|
||||
- No actualices todas las dependencias durante una corrección puntual sin necesidad.
|
||||
- Prioriza vulnerabilidades o incompatibilidades relevantes según su impacto.
|
||||
- Realiza actualizaciones por etapas y verifica sus contratos.
|
||||
- Documenta dependencias no sustituibles y mecanismos de mitigación.
|
||||
|
||||
## 10. Pruebas y seguimiento
|
||||
|
||||
- Pruebas de caracterización para comportamiento existente.
|
||||
- Regresión para defectos corregidos.
|
||||
- Integración para adaptadores y persistencia.
|
||||
- Contrato para consumidores externos y coexistencia.
|
||||
- E2E para flujos de negocio críticos.
|
||||
- Comparación controlada de resultados antiguo/nuevo cuando sea viable.
|
||||
|
||||
Evita utilizar exclusivamente métricas de cobertura como garantía de migración. Observa errores, tiempos de respuesta, integridad de datos y resultados funcionales tras un cambio cuando el entorno lo permita.
|
||||
|
||||
## 11. Señales de alerta
|
||||
|
||||
- Reescribir todo antes de conocer el comportamiento existente.
|
||||
- Confundir limpieza estética con mejora arquitectónica necesaria.
|
||||
- Modificar contratos sin inventariar consumidores.
|
||||
- Migrar datos sin validar integridad ni recuperación.
|
||||
- Repartir reglas inconsistentes entre implementación antigua y nueva.
|
||||
- Eliminar lógica aparentemente redundante con efectos históricos desconocidos.
|
||||
- Introducir frameworks nuevos para una corrección localizada.
|
||||
- Declarar paridad funcional sin pruebas o evidencias suficientes.
|
||||
|
||||
## 12. Lista de comprobación
|
||||
|
||||
- [ ] ¿Conozco comportamiento, contratos y consumidores afectados?
|
||||
- [ ] ¿He distinguido defectos de comportamientos históricos a conservar?
|
||||
- [ ] ¿La estrategia de migración es proporcional al riesgo?
|
||||
- [ ] ¿Existe verificación antes y después del cambio?
|
||||
- [ ] ¿La integridad de los datos y la compatibilidad están protegidas?
|
||||
- [ ] ¿Se han previsto reversión o recuperación cuando corresponda?
|
||||
- [ ] ¿He evitado reestructuraciones ajenas a la tarea?
|
||||
- [ ] ¿He comunicado incertidumbres y verificaciones pendientes?
|
||||
|
||||
**Principio final:** modernizar significa reducir el riesgo y mejorar la capacidad de evolución, no reescribir por preferencia tecnológica.
|
||||
147
.agents/skills/rdx-solid/referencias/arquitectura-shared.md
Normal file
147
.agents/skills/rdx-solid/referencias/arquitectura-shared.md
Normal file
@ -0,0 +1,147 @@
|
||||
# Arquitectura del código compartido entre frontend y backend
|
||||
|
||||
> Referencia especializada del skill `rdx-solid`. Consulta [arquitectura.md](arquitectura.md) y, según la tarea, [arquitectura-backend.md](arquitectura-backend.md) y [arquitectura-frontend.md](arquitectura-frontend.md).
|
||||
|
||||
## 1. Propósito
|
||||
|
||||
Compartir **contratos y lógica realmente reutilizable** sin acoplar el navegador a implementaciones internas del servidor. Compartir código no equivale a compartir todas las entidades, dependencias o políticas del sistema.
|
||||
|
||||
## 2. Qué puede compartirse
|
||||
|
||||
| Candidato | Condiciones |
|
||||
|---|---|
|
||||
| DTO y contratos públicos | Deben describir el intercambio entre aplicaciones, no tablas internas. |
|
||||
| Tipos y enumeraciones contractuales | Semántica común y política de compatibilidad explícita. |
|
||||
| Esquemas de transporte | Independientes de secretos y recursos de servidor. |
|
||||
| Funciones puras | Sin dependencias del entorno ni efectos secundarios. |
|
||||
| Formatos e identificadores interoperables | Responsabilidad compartida, validación adecuada. |
|
||||
| Clientes generados de API | Fuente de verdad definida y versión compatible. |
|
||||
|
||||
Evita compartir directamente repositorios, servicios de dominio internos, modelos ORM, credenciales, acceso a archivos de servidor, lógica de autorización exclusivamente backend o tipos que dependan de Node.js cuando se ejecutarán en el navegador.
|
||||
|
||||
## 3. Diseñar contratos explícitos
|
||||
|
||||
Un contrato de API es una representación de intercambio. No tiene por qué coincidir con la entidad de dominio, el modelo de persistencia ni el modelo de presentación.
|
||||
|
||||
```typescript
|
||||
export interface OrderResponse {
|
||||
id: string;
|
||||
status: 'pending' | 'confirmed';
|
||||
totalMinorUnits: number;
|
||||
currency: string;
|
||||
}
|
||||
```
|
||||
|
||||
Un `OrderResponse` puede ser compartido entre aplicaciones; la entidad `Order` del backend puede mantener invariantes y objetos de valor internos diferentes. Evita filtrar propiedades privadas o campos de base de datos al contrato público.
|
||||
|
||||
## 4. Validación y límites de confianza
|
||||
|
||||
- TypeScript comprueba tipos durante el desarrollo, pero no valida datos externos en ejecución.
|
||||
- Valida datos que cruzan límites de confianza, especialmente en el servidor.
|
||||
- Puedes compartir esquemas de intercambio cuando ahorre duplicación y sean compatibles con ambos entornos.
|
||||
- No consideres que la validación de formulario sustituye la validación del servidor.
|
||||
- Diferencia validación de transporte de reglas de negocio y autorización.
|
||||
|
||||
```typescript
|
||||
// Ejemplo conceptual con una biblioteca de esquemas ya adoptada.
|
||||
export const createOrderSchema = schema.object({
|
||||
customerId: schema.string(),
|
||||
items: schema.array(
|
||||
schema.object({
|
||||
productId: schema.string(),
|
||||
quantity: schema.number().int().positive(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type CreateOrderInput = Infer<typeof createOrderSchema>;
|
||||
```
|
||||
|
||||
`schema` e `Infer` representan APIs ilustrativas; utiliza las de la biblioteca real del proyecto. No añadas una dependencia nueva exclusivamente por este ejemplo.
|
||||
|
||||
## 5. Estructura de paquetes
|
||||
|
||||
```text
|
||||
packages/
|
||||
contracts/
|
||||
src/
|
||||
orders/
|
||||
order-response.ts
|
||||
create-order-request.ts
|
||||
index.ts
|
||||
shared-utils/
|
||||
src/
|
||||
format-identifier.ts
|
||||
apps/
|
||||
api/
|
||||
web/
|
||||
```
|
||||
|
||||
Este esquema es opcional. Un proyecto pequeño puede compartir tipos sin múltiples paquetes; un monorepo grande puede requerir separación adicional. No crees paquetes vacíos ni abstraigas antes de conocer los consumidores.
|
||||
|
||||
## 6. Dirección de dependencias
|
||||
|
||||
- El paquete compartido no debe importar código de `apps/api` ni de `apps/web`.
|
||||
- Frontend y backend pueden depender de contratos compartidos, sin dependencia recíproca entre sus implementaciones.
|
||||
- Evita ciclos y dependencias transitivas que incluyan recursos exclusivos de Node.js en bundles de navegador.
|
||||
- Usa puntos de entrada públicos y exportaciones explícitas cuando aporten límites estables.
|
||||
- Separa código específico de entorno cuando sea necesario; no confíes en condiciones de ejecución que permitan empaquetar secretos.
|
||||
|
||||
```text
|
||||
apps/web ────┐
|
||||
├──► packages/contracts
|
||||
apps/api ────┘
|
||||
```
|
||||
|
||||
## 7. Versionado y compatibilidad
|
||||
|
||||
- Trata los contratos compartidos como contratos públicos cuando los consumidores se despliegan por separado.
|
||||
- Considera cambios aditivos, campos opcionales y estrategias de transición compatibles.
|
||||
- Evita cambiar el significado de un campo sin una migración explícita.
|
||||
- Define versiones y política de publicación cuando exista distribución independiente.
|
||||
- No asumas que actualizar una dependencia compartida actualiza automáticamente a todos sus consumidores.
|
||||
- Considera pruebas de contrato o compatibilidad entre versiones cuando el riesgo lo justifique.
|
||||
|
||||
## 8. Generación frente a mantenimiento manual
|
||||
|
||||
Puedes generar tipos y clientes desde OpenAPI, JSON Schema u otra fuente contractual si reduce divergencias. Debe existir **una fuente de verdad** identificable.
|
||||
|
||||
- No edites manualmente archivos generados.
|
||||
- Evita mantener simultáneamente tipos manuales y generados con significado idéntico sin necesidad.
|
||||
- Comprueba la estabilidad del generador, su coste y la calidad del resultado.
|
||||
- No introduzcas generación de código para dos tipos sencillos que evolucionan juntos sin problemas.
|
||||
|
||||
## 9. Reutilización y propiedad
|
||||
|
||||
Antes de mover código a un paquete compartido:
|
||||
|
||||
1. Identifica consumidores reales.
|
||||
2. Comprueba que representa el mismo conocimiento o contrato.
|
||||
3. Verifica compatibilidad de entorno y dependencias.
|
||||
4. Define quién mantiene el contrato.
|
||||
5. Comprueba que el movimiento no hace más difíciles cambios locales.
|
||||
|
||||
No confundas duplicación accidental con dos reglas similares pertenecientes a ámbitos de negocio diferentes.
|
||||
|
||||
## 10. Riesgos habituales
|
||||
|
||||
- Exponer entidades ORM al frontend.
|
||||
- Empaquetar módulos de Node.js en el bundle del navegador.
|
||||
- Compartir lógica de autorización que depende de contexto de servidor y utilizarla como control de seguridad en cliente.
|
||||
- Cambiar contratos sin verificar consumidores desplegados.
|
||||
- Crear `common` o `shared` como contenedor de responsabilidades inconexas.
|
||||
- Generar importaciones circulares mediante archivos barril.
|
||||
- Acoplar pruebas de frontend a implementaciones internas de backend.
|
||||
|
||||
## 11. Verificación y lista de comprobación
|
||||
|
||||
- [ ] ¿El código compartido expresa un contrato o comportamiento realmente común?
|
||||
- [ ] ¿No importa implementaciones específicas de frontend o backend?
|
||||
- [ ] ¿Es compatible con los entornos donde se ejecutará?
|
||||
- [ ] ¿La validación de datos externos está situada en límites correctos?
|
||||
- [ ] ¿Se han preservado contratos públicos y compatibilidad de versiones?
|
||||
- [ ] ¿La fuente de verdad del esquema está definida?
|
||||
- [ ] ¿No se han exportado secretos ni detalles internos?
|
||||
- [ ] ¿Se han comprobado builds, tipos y pruebas de ambos consumidores afectados?
|
||||
|
||||
**Principio final:** comparte contratos y conocimiento estable, no el acoplamiento entre implementaciones.
|
||||
441
.agents/skills/rdx-solid/referencias/arquitectura.md
Normal file
441
.agents/skills/rdx-solid/referencias/arquitectura.md
Normal file
@ -0,0 +1,441 @@
|
||||
# Arquitectura de software (Software Architecture)
|
||||
|
||||
La arquitectura de software define cómo se organizan los componentes de un sistema, qué responsabilidades asumen, cómo colaboran y qué dependencias existen entre ellos.
|
||||
|
||||
Su objetivo es facilitar el desarrollo, mantenimiento y evolución del software, respetando sus requisitos funcionales y no funcionales.
|
||||
|
||||
Este documento desarrolla los principios establecidos en el `SKILL.md` principal y constituye el punto de entrada a las recomendaciones arquitectónicas especializadas.
|
||||
|
||||
**La arquitectura debe adaptarse a las necesidades reales del proyecto. No existe un estilo arquitectónico universalmente adecuado para todos los sistemas.**
|
||||
|
||||
## 1. Objetivos de la arquitectura
|
||||
|
||||
Una arquitectura adecuada debe permitir:
|
||||
|
||||
1. **Añadir** funcionalidades con un esfuerzo razonable.
|
||||
2. **Modificar** comportamientos existentes de forma segura.
|
||||
3. **Eliminar** funcionalidades sin provocar dependencias innecesarias.
|
||||
4. **Probar** los comportamientos mediante mecanismos de verificación apropiados.
|
||||
5. **Desplegar** el sistema de forma fiable, incluyendo despliegues independientes cuando sean necesarios.
|
||||
6. **Comprender** las responsabilidades y relaciones entre componentes.
|
||||
7. **Evolucionar** el sistema sin introducir complejidad accidental desproporcionada.
|
||||
|
||||
También debe responder a los requisitos no funcionales relevantes:
|
||||
|
||||
- Seguridad.
|
||||
- Rendimiento.
|
||||
- Escalabilidad.
|
||||
- Disponibilidad.
|
||||
- Integridad y consistencia de datos.
|
||||
- Observabilidad.
|
||||
- Compatibilidad.
|
||||
- Recuperación ante fallos.
|
||||
- Coste de operación y mantenimiento.
|
||||
|
||||
**La calidad arquitectónica debe evaluarse respecto a los requisitos del sistema, no por la cantidad de capas, módulos, interfaces o patrones utilizados.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Principios arquitectónicos generales
|
||||
|
||||
### 2.1. Responsabilidades claras
|
||||
|
||||
Organiza el sistema mediante componentes con responsabilidades coherentes.
|
||||
|
||||
- Agrupa los elementos que evolucionen conjuntamente.
|
||||
- Separa responsabilidades que cambien por motivos independientes.
|
||||
- Evita componentes que acumulen funcionalidades sin relación clara.
|
||||
- No fragmentes el código en clases o módulos que no aporten una responsabilidad real.
|
||||
|
||||
Aplica el principio de responsabilidad única (SRP) con criterio profesional.
|
||||
|
||||
### 2.2. Cohesión y acoplamiento
|
||||
|
||||
Favorece componentes con alta cohesión y dependencias controladas.
|
||||
|
||||
- Mantén próximos los elementos relacionados.
|
||||
- Evita dependencias innecesarias entre funcionalidades.
|
||||
- Reduce el conocimiento de detalles internos ajenos.
|
||||
- Define contratos cuando aporten estabilidad o desacoplamiento.
|
||||
- Evita interfaces y capas que únicamente deleguen operaciones sin añadir valor.
|
||||
|
||||
**El objetivo no es eliminar todas las dependencias, sino mantener únicamente las necesarias y apropiadas.**
|
||||
|
||||
### 2.3. Organización modular
|
||||
|
||||
Utiliza límites modulares cuando faciliten la comprensión y evolución del sistema.
|
||||
|
||||
Una organización puede basarse en:
|
||||
|
||||
- Funcionalidades.
|
||||
- Dominios de negocio.
|
||||
- Responsabilidades técnicas.
|
||||
- Capas.
|
||||
- Subsistemas.
|
||||
- Una combinación de estos criterios.
|
||||
|
||||
Prefiere la organización por funcionalidades cuando permita mantener cohesionados los componentes relacionados.
|
||||
|
||||
No obstante:
|
||||
|
||||
- No reorganices automáticamente un proyecto existente.
|
||||
- No impongas módulos independientes para operaciones triviales.
|
||||
- No introduzcas estructuras de directorios que no aporten claridad.
|
||||
- Respeta los límites y convenciones existentes.
|
||||
|
||||
### 2.4. Separación de responsabilidades técnicas
|
||||
|
||||
Distingue las responsabilidades de presentación, aplicación, dominio e infraestructura cuando el sistema lo justifique.
|
||||
|
||||
- **Presentación:** interacción con usuarios o consumidores.
|
||||
- **Aplicación:** coordinación de casos de uso.
|
||||
- **Dominio:** reglas, conceptos e invariantes de negocio.
|
||||
- **Infraestructura:** persistencia, comunicaciones e integraciones externas.
|
||||
|
||||
Estas responsabilidades no obligan a crear cuatro capas físicas en todas las aplicaciones.
|
||||
|
||||
**La separación conceptual es más importante que la cantidad de directorios o clases utilizados.**
|
||||
|
||||
### 2.5. Dirección de las dependencias
|
||||
|
||||
Evita que las reglas de negocio dependan innecesariamente de detalles técnicos.
|
||||
|
||||
En arquitecturas limpias, hexagonales o equivalentes:
|
||||
|
||||
- Las dependencias del código deben orientarse hacia las políticas de mayor nivel.
|
||||
- La infraestructura puede implementar contratos definidos en la aplicación o el dominio.
|
||||
- Los componentes internos deben permanecer desacoplados de tecnologías externas cuando corresponda.
|
||||
|
||||
Distingue entre:
|
||||
|
||||
- **Dependencias del código:** qué módulos conocen y utilizan otros módulos.
|
||||
- **Flujo de ejecución:** qué componentes intervienen al ejecutar una operación.
|
||||
|
||||
Ambos pueden seguir direcciones diferentes.
|
||||
|
||||
No introduzcas interfaces únicamente para cumplir formalmente una regla de dependencias.
|
||||
|
||||
### 2.6. Contratos y compatibilidad
|
||||
|
||||
Los contratos delimitan cómo colaboran los componentes.
|
||||
|
||||
Pueden expresarse mediante:
|
||||
|
||||
- Interfaces y tipos.
|
||||
- Funciones.
|
||||
- APIs.
|
||||
- Eventos.
|
||||
- Esquemas de datos.
|
||||
- Protocolos.
|
||||
- Pruebas de contrato.
|
||||
|
||||
Antes de modificar un contrato público:
|
||||
|
||||
1. Identifica sus consumidores.
|
||||
2. Comprueba su comportamiento actual.
|
||||
3. Evalúa el impacto del cambio.
|
||||
4. Mantén la compatibilidad requerida.
|
||||
5. Define una estrategia de migración cuando sea necesaria.
|
||||
6. Verifica las implementaciones afectadas.
|
||||
|
||||
No presupongas que compartir una interfaz garantiza que dos implementaciones sean funcionalmente sustituibles.
|
||||
|
||||
### 2.7. Arquitectura proporcional
|
||||
|
||||
Selecciona la estructura arquitectónica según los requisitos reales.
|
||||
|
||||
No impongas automáticamente:
|
||||
|
||||
- Clean Architecture.
|
||||
- Arquitectura hexagonal.
|
||||
- Microservicios.
|
||||
- Arquitectura orientada a eventos.
|
||||
- Múltiples capas de servicios.
|
||||
- Contenedores de inyección de dependencias.
|
||||
- Repositorios para cualquier acceso a datos.
|
||||
|
||||
Estos mecanismos pueden aportar ventajas cuando existe una necesidad identificada.
|
||||
|
||||
**Una arquitectura sencilla y modular puede ser preferible a una estructura más sofisticada que incremente innecesariamente el coste de mantenimiento.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Estilos arquitectónicos
|
||||
|
||||
Conoce los estilos arquitectónicos habituales y selecciona el más adecuado según el contexto.
|
||||
|
||||
| Estilo | Características | Cuándo considerarlo |
|
||||
|---|---|---|
|
||||
| **Arquitectura por capas** | Organiza responsabilidades técnicas en capas. | Aplicaciones con flujos y responsabilidades relativamente sencillos. |
|
||||
| **Organización por funcionalidades** | Agrupa los componentes relacionados con una capacidad. | Sistemas con funcionalidades que evolucionan independientemente. |
|
||||
| **Monolito modular** | Módulos cohesionados dentro de una unidad de despliegue. | Aplicaciones empresariales que necesitan modularidad sin distribución operativa. |
|
||||
| **Arquitectura hexagonal** | Utiliza puertos y adaptadores para desacoplar mecanismos externos. | Sistemas con integraciones o dependencias de infraestructura relevantes. |
|
||||
| **Clean Architecture** | Separa políticas de negocio y detalles técnicos mediante límites explícitos. | Sistemas cuyo dominio y necesidades de evolución justifican esa separación. |
|
||||
| **Microservicios** | Distribuye capacidades en servicios desplegables independientemente. | Sistemas con necesidades reales de autonomía, escalabilidad u operación distribuida. |
|
||||
| **Arquitectura orientada a eventos** | Comunica componentes mediante eventos. | Procesos asíncronos o desacoplamiento temporal justificado. |
|
||||
|
||||
Los estilos no son necesariamente excluyentes.
|
||||
|
||||
Por ejemplo, un monolito modular puede organizarse por funcionalidades y aplicar principios hexagonales en determinados módulos.
|
||||
|
||||
### Criterios de selección
|
||||
|
||||
Antes de adoptar una arquitectura:
|
||||
|
||||
- Identifica los requisitos funcionales.
|
||||
- Evalúa los requisitos no funcionales.
|
||||
- Considera el tamaño y la evolución prevista del sistema.
|
||||
- Analiza las capacidades del equipo.
|
||||
- Evalúa el coste de desarrollo y operación.
|
||||
- Considera los riesgos de migración.
|
||||
- Comprueba si la arquitectura existente ya satisface las necesidades.
|
||||
|
||||
No cambies de estilo arquitectónico únicamente por tendencias tecnológicas.
|
||||
|
||||
---
|
||||
|
||||
## 4. Arquitectura en proyectos existentes
|
||||
|
||||
Al trabajar sobre un proyecto existente:
|
||||
|
||||
1. Identifica su estructura y estilo arquitectónico actual.
|
||||
2. Examina sus módulos, contratos y convenciones.
|
||||
3. Comprende el comportamiento que debe preservarse.
|
||||
4. Localiza los componentes directamente afectados.
|
||||
5. Evalúa el impacto de las modificaciones.
|
||||
6. Introduce cambios incrementales.
|
||||
7. Verifica los resultados.
|
||||
|
||||
### Reglas de evolución
|
||||
|
||||
- Respeta los límites existentes salvo que exista una necesidad justificada de modificarlos.
|
||||
- No introduzcas nuevas capas para una modificación puntual si no son necesarias.
|
||||
- No combines cambios funcionales con reestructuraciones arquitectónicas extensas.
|
||||
- Evita modificar componentes ajenos al alcance.
|
||||
- Mantén compatibilidad con consumidores existentes cuando corresponda.
|
||||
- Documenta las decisiones arquitectónicas significativas.
|
||||
|
||||
**Una arquitectura existente debe mejorarse para resolver problemas reales, no sustituirse automáticamente por otra considerada más moderna.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Documentación arquitectónica especializada
|
||||
|
||||
Las recomendaciones específicas se encuentran distribuidas en documentos independientes.
|
||||
|
||||
**Consulta únicamente los documentos relevantes para la tarea, combinándolos cuando sea necesario.**
|
||||
|
||||
### 5.1. Arquitectura backend
|
||||
|
||||
**[arquitectura-backend.md](arquitectura-backend.md)**
|
||||
|
||||
Consulta este documento cuando trabajes con:
|
||||
|
||||
- Servicios backend.
|
||||
- Casos de uso.
|
||||
- Lógica de dominio.
|
||||
- APIs.
|
||||
- Persistencia.
|
||||
- Transacciones.
|
||||
- Autenticación y autorización del servidor.
|
||||
- Procesamiento asíncrono.
|
||||
- Eventos y mensajería.
|
||||
- Integraciones externas.
|
||||
- Observabilidad y operaciones.
|
||||
|
||||
Incluye recomendaciones para organizar módulos y servicios backend sin introducir capas o abstracciones innecesarias.
|
||||
|
||||
### 5.2. Arquitectura frontend
|
||||
|
||||
**[arquitectura-frontend.md](arquitectura-frontend.md)**
|
||||
|
||||
Consulta este documento cuando trabajes con:
|
||||
|
||||
- Componentes de interfaz.
|
||||
- Aplicaciones React u otros frameworks frontend.
|
||||
- Organización por funcionalidades.
|
||||
- Hooks y lógica de presentación.
|
||||
- Gestión del estado.
|
||||
- Formularios y cuadros de diálogo.
|
||||
- Consumo de APIs.
|
||||
- Internacionalización.
|
||||
- Accesibilidad.
|
||||
- Rendimiento y pruebas de interfaz.
|
||||
|
||||
Incluye criterios para evitar componentes monolíticos y separar responsabilidades sin trasladar innecesariamente patrones propios del backend.
|
||||
|
||||
### 5.3. Código compartido entre backend y frontend
|
||||
|
||||
**[arquitectura-shared.md](arquitectura-shared.md)**
|
||||
|
||||
Consulta este documento cuando trabajes con:
|
||||
|
||||
- Paquetes compartidos.
|
||||
- Monorepos.
|
||||
- Tipos y contratos utilizados por frontend y backend.
|
||||
- Esquemas de validación.
|
||||
- DTO.
|
||||
- Enumeraciones contractuales.
|
||||
- Utilidades comunes.
|
||||
- Generación de tipos.
|
||||
- Compatibilidad entre aplicaciones.
|
||||
- Dependencias entre paquetes.
|
||||
|
||||
Su objetivo es permitir una reutilización segura sin acoplar el frontend a implementaciones internas del backend.
|
||||
|
||||
### 5.4. Arquitectura de sistemas heredados
|
||||
|
||||
**[arquitectura-legacy.md](arquitectura-legacy.md)**
|
||||
|
||||
Consulta este documento cuando trabajes con:
|
||||
|
||||
- Código legado.
|
||||
- Refactorización de sistemas existentes.
|
||||
- Modernización de aplicaciones.
|
||||
- Migraciones tecnológicas.
|
||||
- Sustitución progresiva de componentes.
|
||||
- Compatibilidad con contratos antiguos.
|
||||
- Migraciones de persistencia.
|
||||
- Pruebas de caracterización.
|
||||
- Adaptadores y capas anticorrupción.
|
||||
- Convivencia entre sistemas antiguos y nuevos.
|
||||
|
||||
Su prioridad es preservar el comportamiento y controlar el riesgo durante la evolución del software.
|
||||
|
||||
### 5.5. Decisiones arquitectónicas
|
||||
|
||||
**[arquitectura-decisiones.md](arquitectura-decisiones.md)**
|
||||
|
||||
Consulta este documento cuando necesites:
|
||||
|
||||
- Seleccionar tecnologías.
|
||||
- Comparar alternativas arquitectónicas.
|
||||
- Introducir dependencias importantes.
|
||||
- Diseñar nuevos módulos.
|
||||
- Modificar límites existentes.
|
||||
- Evaluar requisitos no funcionales.
|
||||
- Analizar costes y compromisos.
|
||||
- Documentar decisiones mediante ADR.
|
||||
- Planificar cambios arquitectónicos significativos.
|
||||
|
||||
No es necesario consultarlo para modificaciones rutinarias que no impliquen decisiones arquitectónicas relevantes.
|
||||
|
||||
---
|
||||
|
||||
## 6. Selección de referencias según la tarea
|
||||
|
||||
Antes de aplicar recomendaciones especializadas:
|
||||
|
||||
1. Identifica el ámbito del cambio.
|
||||
2. Determina qué componentes y contratos están afectados.
|
||||
3. Consulta los documentos correspondientes.
|
||||
4. Combina las recomendaciones cuando la tarea abarque varios ámbitos.
|
||||
5. Aplica las convenciones específicas del proyecto.
|
||||
6. Evita trasladar reglas de un ámbito a otro sin analizar su adecuación.
|
||||
|
||||
### Ejemplos
|
||||
|
||||
| Tarea | Referencias recomendadas |
|
||||
|---|---|
|
||||
| Crear un endpoint backend | `arquitectura-backend.md` |
|
||||
| Refactorizar un componente React | `arquitectura-frontend.md` |
|
||||
| Compartir un esquema de validación | `arquitectura-shared.md`, `arquitectura-backend.md` y `arquitectura-frontend.md` |
|
||||
| Migrar un servicio heredado | `arquitectura-legacy.md` y `arquitectura-backend.md` |
|
||||
| Modificar un contrato consumido por frontend y backend | `arquitectura-shared.md`, `arquitectura-backend.md` y `arquitectura-frontend.md` |
|
||||
| Elegir una nueva base de datos | `arquitectura-backend.md` y `arquitectura-decisiones.md` |
|
||||
| Rediseñar límites entre módulos | `arquitectura-decisiones.md` y los documentos correspondientes a los módulos afectados |
|
||||
| Modernizar una aplicación frontend heredada | `arquitectura-legacy.md` y `arquitectura-frontend.md` |
|
||||
|
||||
Los documentos especializados son complementarios, no categorías excluyentes.
|
||||
|
||||
---
|
||||
|
||||
## 7. Relación con otros documentos del skill
|
||||
|
||||
La documentación de arquitectura debe centrarse en:
|
||||
|
||||
- Organización del sistema.
|
||||
- Límites entre componentes.
|
||||
- Dependencias.
|
||||
- Contratos.
|
||||
- Selección de estilos.
|
||||
- Evolución arquitectónica.
|
||||
|
||||
Para profundizar en otros aspectos, utiliza:
|
||||
|
||||
- **[principios-solid.md](principios-solid.md):** principios SOLID y diseño de dependencias.
|
||||
- **[diseño-de-objetos.md](diseño-de-objetos.md):** responsabilidades, entidades, objetos de valor y agregados.
|
||||
- **[patrones-de-diseño.md](patrones-de-diseño.md):** patrones de diseño y criterios para aplicarlos.
|
||||
- **[complejidad.md](complejidad.md):** control de la complejidad y prevención de sobreingeniería.
|
||||
- **[code-smells.md](code-smells.md):** detección de problemas estructurales.
|
||||
- **[testing.md](testing.md):** estrategias y niveles de pruebas.
|
||||
- **[tdd.md](tdd.md):** desarrollo guiado por pruebas.
|
||||
|
||||
Evita duplicar explicaciones completas de estos conceptos dentro de los documentos arquitectónicos.
|
||||
|
||||
---
|
||||
|
||||
## 8. Señales de alerta arquitectónicas
|
||||
|
||||
Revisa la arquitectura cuando detectes:
|
||||
|
||||
- Dependencias circulares problemáticas.
|
||||
- Reglas de negocio acopladas innecesariamente a infraestructura.
|
||||
- Módulos con responsabilidades poco claras.
|
||||
- Cambios pequeños que requieren modificar numerosos componentes.
|
||||
- Dependencias sobre detalles internos de otros módulos.
|
||||
- Estado mutable compartido difícil de controlar.
|
||||
- Contratos públicos inestables.
|
||||
- Paquetes compartidos que acumulan responsabilidades inconexas.
|
||||
- Capas que únicamente delegan sin aportar valor.
|
||||
- Abstracciones destinadas a requisitos hipotéticos.
|
||||
- Duplicación de conocimiento entre módulos.
|
||||
- Dificultad para verificar comportamientos relevantes.
|
||||
|
||||
Estas señales justifican un análisis, pero no obligan automáticamente a realizar una refactorización.
|
||||
|
||||
**La refactorización debe estar motivada por un problema real y mantenerse dentro del alcance autorizado.**
|
||||
|
||||
---
|
||||
|
||||
## 9. Lista de comprobación arquitectónica
|
||||
|
||||
### Antes de implementar
|
||||
|
||||
- [ ] ¿Comprendo los requisitos y restricciones del sistema?
|
||||
- [ ] ¿He identificado la arquitectura existente?
|
||||
- [ ] ¿Conozco los módulos y contratos afectados?
|
||||
- [ ] ¿He consultado las referencias especializadas necesarias?
|
||||
- [ ] ¿La solución propuesta es proporcional al problema?
|
||||
- [ ] ¿Estoy evitando abstracciones innecesarias?
|
||||
|
||||
### Durante la implementación
|
||||
|
||||
- [ ] ¿Las responsabilidades permanecen cohesionadas?
|
||||
- [ ] ¿Las dependencias están justificadas?
|
||||
- [ ] ¿Se respetan los contratos existentes?
|
||||
- [ ] ¿Estoy evitando cambios arquitectónicos ajenos a la tarea?
|
||||
- [ ] ¿Las modificaciones mantienen la compatibilidad requerida?
|
||||
- [ ] ¿La implementación sigue las convenciones del proyecto?
|
||||
|
||||
### Al finalizar
|
||||
|
||||
- [ ] ¿Se satisfacen los requisitos?
|
||||
- [ ] ¿Se ha preservado el comportamiento que debía mantenerse?
|
||||
- [ ] ¿Se han ejecutado las verificaciones pertinentes?
|
||||
- [ ] ¿Los límites entre módulos siguen siendo coherentes?
|
||||
- [ ] ¿He evitado introducir complejidad accidental?
|
||||
- [ ] ¿Se han documentado las decisiones arquitectónicas significativas?
|
||||
- [ ] ¿He comunicado las limitaciones y riesgos pendientes?
|
||||
|
||||
---
|
||||
|
||||
## Principio final
|
||||
|
||||
> "La arquitectura debe facilitar la evolución del software, no imponer una complejidad superior a la que requieren sus necesidades."
|
||||
|
||||
Selecciona estructuras, módulos, capas y contratos según los requisitos y el contexto del proyecto.
|
||||
|
||||
Respeta la arquitectura existente y modifica sus límites únicamente cuando exista una razón técnica o funcional justificada.
|
||||
|
||||
**La mejor arquitectura es aquella que satisface los requisitos, mantiene responsabilidades claras, controla las dependencias y permite evolucionar el sistema de forma segura con la menor complejidad accidental razonable.**
|
||||
1051
.agents/skills/rdx-solid/referencias/clean-code.md
Normal file
1051
.agents/skills/rdx-solid/referencias/clean-code.md
Normal file
File diff suppressed because it is too large
Load Diff
925
.agents/skills/rdx-solid/referencias/code-smells.md
Normal file
925
.agents/skills/rdx-solid/referencias/code-smells.md
Normal file
@ -0,0 +1,925 @@
|
||||
# Problemas de diseño y antipatrones (Code Smells & Anti-Patterns)
|
||||
|
||||
## 1. ¿Qué son los Code Smells?
|
||||
|
||||
Los *Code Smells* son indicios de posibles problemas en el diseño o la estructura del software.
|
||||
|
||||
No representan necesariamente errores funcionales. Señalan características que pueden dificultar:
|
||||
|
||||
- Comprender el código.
|
||||
- Modificar funcionalidades.
|
||||
- Realizar pruebas.
|
||||
- Mantener contratos.
|
||||
- Reutilizar comportamientos.
|
||||
- Evolucionar la arquitectura.
|
||||
|
||||
**Un Code Smell es una señal para investigar, no una obligación automática de refactorizar.**
|
||||
|
||||
Su importancia depende del contexto, las responsabilidades, el impacto y el coste de mantenimiento.
|
||||
|
||||
Este documento desarrolla los criterios establecidos en el `SKILL.md` principal.
|
||||
|
||||
### Criterios generales
|
||||
|
||||
- No evalúes la calidad exclusivamente mediante métricas numéricas.
|
||||
- Identifica el problema concreto que produce la estructura observada.
|
||||
- Diferencia entre código mejorable y código que compromete la corrección.
|
||||
- Prioriza los problemas que afectan al mantenimiento, seguridad o evolución.
|
||||
- No modifiques código estable únicamente para satisfacer una regla estética.
|
||||
- Respeta el alcance de la tarea.
|
||||
- Conserva el comportamiento observable durante las refactorizaciones.
|
||||
|
||||
---
|
||||
|
||||
## 2. Las cinco categorías principales
|
||||
|
||||
### 2.1. Bloaters: estructuras excesivamente grandes
|
||||
|
||||
Son componentes que han acumulado tamaño o complejidad hasta dificultar su comprensión.
|
||||
|
||||
| Problema | Señal de revisión | Posible refactorización |
|
||||
|---|---|---|
|
||||
| **Long Method (Método largo)** | Método extenso con varios niveles de responsabilidad. | Extract Method, Compose Method. |
|
||||
| **Large Class (Clase grande)** | Clase con responsabilidades independientes o demasiadas dependencias. | Extract Class, SRP. |
|
||||
| **Long Parameter List (Lista larga de parámetros)** | Parámetros numerosos o difíciles de relacionar. | Introduce Parameter Object. |
|
||||
| **Data Clumps (Agrupaciones de datos)** | Los mismos datos aparecen juntos repetidamente. | Introducir un tipo u objeto que represente el concepto. |
|
||||
| **Primitive Obsession (Obsesión por los primitivos)** | Conceptos de dominio representados mediante primitivas ambiguas. | Value Objects, tipos nominales. |
|
||||
|
||||
Los límites de líneas y parámetros son orientativos, no criterios absolutos.
|
||||
|
||||
### 2.2. Object-Orientation Abusers: uso inadecuado de la orientación a objetos
|
||||
|
||||
| Problema | Señal de revisión | Posible refactorización |
|
||||
|---|---|---|
|
||||
| **Switch Statements (Condicionales por tipo)** | Comprobaciones complejas o repetidas de variantes de comportamiento. | Polimorfismo, Strategy o simplificación. |
|
||||
| **Parallel Inheritance (Herencia paralela)** | Ampliar una jerarquía obliga a ampliar otra. | Revisar responsabilidades, composición o jerarquías. |
|
||||
| **Refused Bequest (Herencia rechazada)** | Una subclase no puede cumplir correctamente el contrato heredado. | Replace Inheritance with Delegation. |
|
||||
| **Alternative Classes (Clases alternativas)** | Componentes equivalentes presentan contratos innecesariamente distintos. | Normalizar contratos o introducir una abstracción compartida cuando corresponda. |
|
||||
|
||||
No toda estructura `switch` ni toda jerarquía de herencia constituye un problema.
|
||||
|
||||
### 2.3. Change Preventers: estructuras que dificultan los cambios
|
||||
|
||||
| Problema | Señal de revisión | Posible refactorización |
|
||||
|---|---|---|
|
||||
| **Divergent Change (Cambios divergentes)** | Una clase cambia frecuentemente por motivos independientes. | Extract Class, SRP. |
|
||||
| **Shotgun Surgery (Cambios dispersos)** | Una modificación pequeña afecta a muchos componentes. | Reorganizar responsabilidades y localizar conocimiento compartido. |
|
||||
| **Parallel Inheritance (Herencia paralela)** | Varias jerarquías deben evolucionar coordinadamente. | Composición o revisión de jerarquías. |
|
||||
|
||||
El objetivo es reducir la amplificación de cambios sin introducir nuevas dependencias innecesarias.
|
||||
|
||||
### 2.4. Dispensables: elementos potencialmente prescindibles
|
||||
|
||||
| Problema | Señal de revisión | Posible refactorización |
|
||||
|---|---|---|
|
||||
| **Comments (Comentarios redundantes)** | Explican operaciones evidentes o compensan nombres poco claros. | Renombrar, simplificar o eliminar comentarios redundantes. |
|
||||
| **Duplicate Code (Código duplicado)** | Se repiten reglas o conocimiento que debe mantenerse sincronizado. | Extraer comportamiento común cuando esté justificado. |
|
||||
| **Dead Code (Código muerto)** | Código sin consumidores ni comportamiento alcanzable. | Eliminar tras verificar su falta de uso. |
|
||||
| **Speculative Generality (Generalización especulativa)** | Abstracciones creadas para necesidades hipotéticas. | Aplicar YAGNI y simplificar. |
|
||||
| **Lazy Class (Clase innecesariamente trivial)** | Clase sin responsabilidad o valor propio. | Inline Class o eliminar la abstracción. |
|
||||
|
||||
No elimines código público o aparentemente no utilizado sin comprobar consumidores externos, carga dinámica y mecanismos de extensión.
|
||||
|
||||
### 2.5. Couplers: acoplamiento excesivo
|
||||
|
||||
| Problema | Señal de revisión | Posible refactorización |
|
||||
|---|---|---|
|
||||
| **Feature Envy (Envidia de funcionalidades)** | Un método depende principalmente de datos y operaciones de otro componente. | Reubicar comportamiento o introducir un servicio de dominio. |
|
||||
| **Inappropriate Intimacy (Intimidad inapropiada)** | Los componentes conocen o modifican detalles internos ajenos. | Encapsulación, contratos, extracción de responsabilidades. |
|
||||
| **Message Chains (Cadenas de mensajes)** | Navegación profunda por estructuras internas ajenas. | Hide Delegate cuando reduzca un acoplamiento real. |
|
||||
| **Middle Man (Intermediario innecesario)** | Componente que solo delega sin aportar una responsabilidad. | Inline Class o simplificación. |
|
||||
|
||||
Las capas de adaptación, seguridad, transacciones o integración pueden parecer intermediarios, pero cumplir una responsabilidad arquitectónica legítima.
|
||||
|
||||
---
|
||||
|
||||
## 3. Los siete Code Smells más habituales
|
||||
|
||||
### 3.1. Long Method (Método largo)
|
||||
|
||||
**Señal:** una función acumula responsabilidades o detalles que dificultan comprender su intención.
|
||||
|
||||
El problema no es superar una cantidad concreta de líneas.
|
||||
|
||||
#### Ejemplo problemático
|
||||
|
||||
```typescript
|
||||
async function processOrder(order: Order): Promise<void> {
|
||||
if (order.items.length === 0) {
|
||||
throw new EmptyOrderError();
|
||||
}
|
||||
|
||||
if (!order.customer) {
|
||||
throw new MissingCustomerError();
|
||||
}
|
||||
|
||||
let subtotal = 0;
|
||||
|
||||
for (const item of order.items) {
|
||||
subtotal += item.price * item.quantity;
|
||||
subtotal -= item.discount ?? 0;
|
||||
}
|
||||
|
||||
const taxRate = getTaxRate(order.customer.state);
|
||||
const total = subtotal * (1 + taxRate);
|
||||
|
||||
await orderRepository.save(order, total);
|
||||
await emailService.sendConfirmation(order.customer.email);
|
||||
}
|
||||
```
|
||||
|
||||
La operación mezcla validación, cálculo, persistencia y notificación.
|
||||
|
||||
#### Alternativa con responsabilidades explícitas
|
||||
|
||||
```typescript
|
||||
async function processOrder(order: Order): Promise<void> {
|
||||
validateOrder(order);
|
||||
|
||||
const total = calculateOrderTotal(order);
|
||||
|
||||
await orderRepository.save(order, total);
|
||||
await emailService.sendConfirmation(order.customer.email);
|
||||
}
|
||||
```
|
||||
|
||||
Esta separación mejora la lectura, pero no resuelve por sí sola todos los aspectos de consistencia del proceso.
|
||||
|
||||
En una aplicación real deben definirse las transacciones y el tratamiento de errores, especialmente cuando la persistencia se completa y posteriormente falla la notificación.
|
||||
|
||||
#### Cuándo refactorizar
|
||||
|
||||
- La función realiza operaciones conceptualmente independientes.
|
||||
- Su flujo resulta difícil de seguir.
|
||||
- Los cambios afectan frecuentemente a partes no relacionadas.
|
||||
- Las pruebas requieren configuraciones complejas.
|
||||
- Existen fragmentos con significado propio que pueden extraerse.
|
||||
|
||||
#### Cuándo no refactorizar
|
||||
|
||||
- La función es extensa, pero lineal y comprensible.
|
||||
- La extracción multiplica funciones triviales.
|
||||
- El código representa un algoritmo cohesionado.
|
||||
- La refactorización no aporta una mejora clara.
|
||||
|
||||
**No fragmentes artificialmente un método únicamente para reducir su número de líneas.**
|
||||
|
||||
---
|
||||
|
||||
### 3.2. Large Class (Clase demasiado grande)
|
||||
|
||||
**Señal:** una clase concentra responsabilidades independientes y presenta múltiples motivos de cambio.
|
||||
|
||||
#### Ejemplo problemático
|
||||
|
||||
```typescript
|
||||
class User {
|
||||
login(): Promise<void> {
|
||||
// Autenticación.
|
||||
}
|
||||
|
||||
resetPassword(): Promise<void> {
|
||||
// Recuperación de credenciales.
|
||||
}
|
||||
|
||||
setTheme(theme: Theme): void {
|
||||
// Preferencias.
|
||||
}
|
||||
|
||||
sendEmail(message: string): Promise<void> {
|
||||
// Notificaciones.
|
||||
}
|
||||
|
||||
charge(amount: Money): Promise<void> {
|
||||
// Facturación.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La clase agrupa responsabilidades de autenticación, preferencias, comunicaciones y facturación.
|
||||
|
||||
#### Alternativa
|
||||
|
||||
```typescript
|
||||
class User {
|
||||
constructor(
|
||||
readonly id: UserId,
|
||||
readonly email: Email,
|
||||
) {}
|
||||
}
|
||||
|
||||
class AuthenticationService {
|
||||
// Gestiona los casos de uso de autenticación.
|
||||
}
|
||||
|
||||
class UserPreferences {
|
||||
// Gestiona las preferencias del usuario.
|
||||
}
|
||||
|
||||
class NotificationService {
|
||||
// Gestiona las comunicaciones.
|
||||
}
|
||||
|
||||
class BillingService {
|
||||
// Gestiona las operaciones de facturación.
|
||||
}
|
||||
```
|
||||
|
||||
Esta distribución es orientativa. Las responsabilidades concretas dependerán del dominio y de la arquitectura existente.
|
||||
|
||||
No es obligatorio crear todas estas clases si el proyecto no necesita dichas capacidades.
|
||||
|
||||
#### Indicadores relevantes
|
||||
|
||||
- Responsabilidades independientes.
|
||||
- Dependencias numerosas y poco relacionadas.
|
||||
- Modificaciones frecuentes por requisitos diferentes.
|
||||
- Pruebas difíciles de aislar.
|
||||
- Dificultad para describir el propósito de la clase.
|
||||
|
||||
**El tamaño de una clase es una señal secundaria. La cohesión y los motivos de cambio son criterios más importantes.**
|
||||
|
||||
---
|
||||
|
||||
### 3.3. Feature Envy (Envidia de funcionalidades)
|
||||
|
||||
**Señal:** un método utiliza ampliamente la información o el comportamiento de otro objeto.
|
||||
|
||||
#### Ejemplo
|
||||
|
||||
```typescript
|
||||
class Order {
|
||||
calculateShipping(customer: Customer): number {
|
||||
if (customer.country === 'US') {
|
||||
return customer.state === 'CA' ? 10 : 15;
|
||||
}
|
||||
|
||||
return 25;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La operación depende principalmente de la localización del cliente.
|
||||
|
||||
Sin embargo, trasladar directamente `calculateShipping()` a `Customer` no es necesariamente correcto.
|
||||
|
||||
La política de envío puede depender también de:
|
||||
|
||||
- Peso del pedido.
|
||||
- Dirección de entrega.
|
||||
- Transportista.
|
||||
- Modalidad de envío.
|
||||
- Restricciones comerciales.
|
||||
- Tarifas vigentes.
|
||||
|
||||
#### Alternativa mediante una responsabilidad específica
|
||||
|
||||
```typescript
|
||||
interface ShippingPolicy {
|
||||
calculate(order: Order, destination: Address): Money;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
class OrderService {
|
||||
constructor(
|
||||
private readonly shippingPolicy: ShippingPolicy,
|
||||
) {}
|
||||
|
||||
calculateShipping(order: Order, destination: Address): Money {
|
||||
return this.shippingPolicy.calculate(order, destination);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Esta alternativa resulta apropiada si el cálculo representa una política de negocio independiente.
|
||||
|
||||
Para un caso sencillo, una función de dominio puede ser suficiente.
|
||||
|
||||
#### Criterios de revisión
|
||||
|
||||
- Identifica quién es responsable de la regla.
|
||||
- No traslades métodos únicamente porque consulten muchos atributos de otra clase.
|
||||
- Considera servicios de dominio cuando una operación involucre varios conceptos.
|
||||
- Evita introducir interfaces y servicios innecesarios.
|
||||
|
||||
---
|
||||
|
||||
### 3.4. Primitive Obsession (Obsesión por los primitivos)
|
||||
|
||||
**Señal:** conceptos del dominio se representan mediante tipos primitivos que permiten confusiones o estados inválidos.
|
||||
|
||||
#### Ejemplo
|
||||
|
||||
```typescript
|
||||
function createUser(
|
||||
email: string,
|
||||
age: number,
|
||||
postalCode: string,
|
||||
): User {
|
||||
// Crear el usuario.
|
||||
}
|
||||
```
|
||||
|
||||
La firma no expresa determinadas restricciones ni diferencia conceptos semánticamente relacionados.
|
||||
|
||||
#### Alternativa mediante objetos de valor
|
||||
|
||||
```typescript
|
||||
class Email {
|
||||
constructor(readonly value: string) {
|
||||
if (!isValidEmail(value)) {
|
||||
throw new InvalidEmailError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Age {
|
||||
constructor(readonly value: number) {
|
||||
if (!Number.isInteger(value) || value < 0 || value > 150) {
|
||||
throw new InvalidAgeError();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
function createUser(
|
||||
email: Email,
|
||||
age: Age,
|
||||
address: Address,
|
||||
): User {
|
||||
return new User(email, age, address);
|
||||
}
|
||||
```
|
||||
|
||||
Los objetos de valor pueden proteger invariantes cuando su construcción y estado están correctamente controlados.
|
||||
|
||||
No sustituyen automáticamente la validación de datos externos.
|
||||
|
||||
#### Alternativas en TypeScript
|
||||
|
||||
Los *branded types* pueden ser suficientes cuando únicamente se necesita distinguir identificadores.
|
||||
|
||||
```typescript
|
||||
type UserId = string & { readonly __brand: 'UserId' };
|
||||
type OrderId = string & { readonly __brand: 'OrderId' };
|
||||
```
|
||||
|
||||
Estas distinciones existen durante la comprobación estática, no durante la ejecución.
|
||||
|
||||
#### Criterios de aplicación
|
||||
|
||||
Utiliza objetos de valor cuando aporten:
|
||||
|
||||
- Validación.
|
||||
- Invariantes.
|
||||
- Semántica.
|
||||
- Comportamiento propio.
|
||||
- Seguridad de tipos.
|
||||
|
||||
No encapsules todos los valores primitivos indiscriminadamente.
|
||||
|
||||
---
|
||||
|
||||
### 3.5. Switch Statements (Condicionales por tipo)
|
||||
|
||||
**Señal:** comprobaciones de tipo repetidas que distribuyen una misma variación de comportamiento por numerosos componentes.
|
||||
|
||||
#### Ejemplo
|
||||
|
||||
```typescript
|
||||
type Shape =
|
||||
| { type: 'circle'; radius: number }
|
||||
| { type: 'rectangle'; width: number; height: number }
|
||||
| { type: 'triangle'; base: number; height: number };
|
||||
|
||||
function getArea(shape: Shape): number {
|
||||
switch (shape.type) {
|
||||
case 'circle':
|
||||
return Math.PI * shape.radius ** 2;
|
||||
|
||||
case 'rectangle':
|
||||
return shape.width * shape.height;
|
||||
|
||||
case 'triangle':
|
||||
return shape.base * shape.height * 0.5;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Este código no constituye necesariamente un problema.
|
||||
|
||||
En TypeScript, las uniones discriminadas permiten representar variantes de forma clara y segura.
|
||||
|
||||
#### Cuándo considerar polimorfismo
|
||||
|
||||
Si los mismos condicionales aparecen repetidamente para calcular áreas, perímetros, dibujar figuras y aplicar transformaciones, puede resultar útil encapsular comportamientos.
|
||||
|
||||
```typescript
|
||||
interface Shape {
|
||||
getArea(): number;
|
||||
getPerimeter(): number;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
class Circle implements Shape {
|
||||
constructor(private readonly radius: number) {}
|
||||
|
||||
getArea(): number {
|
||||
return Math.PI * this.radius ** 2;
|
||||
}
|
||||
|
||||
getPerimeter(): number {
|
||||
return 2 * Math.PI * this.radius;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Este enfoque puede facilitar la incorporación de nuevas figuras con comportamientos propios.
|
||||
|
||||
#### Criterios de decisión
|
||||
|
||||
- Conserva `switch` cuando resulte sencillo y expresivo.
|
||||
- Aprovecha las uniones discriminadas y comprobaciones exhaustivas de TypeScript.
|
||||
- Utiliza polimorfismo cuando las variantes tengan comportamientos significativos.
|
||||
- No crees jerarquías artificiales para eliminar condicionales triviales.
|
||||
- Evalúa si resulta más frecuente añadir operaciones nuevas o variantes nuevas.
|
||||
|
||||
**La presencia de un `switch` no demuestra por sí sola un problema de diseño.**
|
||||
|
||||
---
|
||||
|
||||
### 3.6. Inappropriate Intimacy (Intimidad inapropiada)
|
||||
|
||||
**Señal:** varios componentes conocen o modifican directamente detalles internos ajenos.
|
||||
|
||||
#### Ejemplo problemático
|
||||
|
||||
```typescript
|
||||
class Order {
|
||||
process(inventory: Inventory): void {
|
||||
for (const item of this.items) {
|
||||
const stock = inventory.stockLevels[item.sku];
|
||||
|
||||
if (stock.quantity < item.quantity) {
|
||||
throw new OutOfStockError(item.sku);
|
||||
}
|
||||
|
||||
stock.quantity -= item.quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El pedido modifica directamente la estructura interna del inventario.
|
||||
|
||||
Además, podría dejar una reserva parcial si se agotan las existencias de un artículo después de haber descontado otros.
|
||||
|
||||
#### Alternativa con comportamiento encapsulado
|
||||
|
||||
```typescript
|
||||
class Inventory {
|
||||
reserve(items: readonly OrderItem[]): ReserveResult {
|
||||
for (const item of items) {
|
||||
if (!this.canReserve(item)) {
|
||||
return ReserveResult.outOfStock(item.sku);
|
||||
}
|
||||
}
|
||||
|
||||
this.deductStock(items);
|
||||
|
||||
return ReserveResult.success();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
class Order {
|
||||
process(inventory: Inventory): void {
|
||||
const result = inventory.reserve(this.items);
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
throw new OutOfStockError(result.failedSku);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Esta implementación ilustra la separación de responsabilidades.
|
||||
|
||||
Para garantizar una reserva atómica en producción, también deben analizarse la persistencia, las transacciones y la concurrencia.
|
||||
|
||||
#### Criterios de revisión
|
||||
|
||||
- Evita modificar directamente estados internos ajenos.
|
||||
- Ubica las invariantes en el componente responsable.
|
||||
- Utiliza contratos explícitos.
|
||||
- No introduzcas getters que permitan eludir el encapsulamiento.
|
||||
- Evalúa si las operaciones necesitan garantías transaccionales.
|
||||
|
||||
---
|
||||
|
||||
### 3.7. Speculative Generality (Generalización especulativa)
|
||||
|
||||
**Señal:** el sistema incorpora abstracciones y funcionalidades destinadas a necesidades hipotéticas.
|
||||
|
||||
#### Ejemplo problemático
|
||||
|
||||
```typescript
|
||||
interface PaymentProcessor {
|
||||
process(): Promise<void>;
|
||||
rollback(): Promise<void>;
|
||||
audit(): Promise<void>;
|
||||
generateReport(): Promise<void>;
|
||||
scheduleRecurring(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
Una implementación puede verse obligada a declarar capacidades que no necesita o no admite.
|
||||
|
||||
#### Alternativa
|
||||
|
||||
```typescript
|
||||
interface PaymentProcessor {
|
||||
process(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
class StripePaymentProcessor implements PaymentProcessor {
|
||||
async process(): Promise<void> {
|
||||
// Procesar el pago.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Incorpora nuevos contratos cuando exista una necesidad real.
|
||||
|
||||
#### Criterios
|
||||
|
||||
- Aplica YAGNI.
|
||||
- Evita capacidades hipotéticas.
|
||||
- No introduzcas interfaces genéricas para problemas todavía desconocidos.
|
||||
- Respeta ISP al diseñar contratos.
|
||||
- Prefiere extender una solución sencilla cuando aparezca una necesidad real.
|
||||
|
||||
---
|
||||
|
||||
## 4. Otros Code Smells importantes
|
||||
|
||||
### 4.1. Divergent Change (Cambios divergentes)
|
||||
|
||||
Se produce cuando un componente debe modificarse por motivos independientes.
|
||||
|
||||
Por ejemplo, un servicio que cambia por:
|
||||
|
||||
- Nuevas reglas de facturación.
|
||||
- Modificaciones de autenticación.
|
||||
- Cambios en plantillas de correo.
|
||||
- Evolución del almacenamiento.
|
||||
|
||||
**Solución:** identificar responsabilidades y separarlas cuando exista un beneficio real.
|
||||
|
||||
No confundas varios métodos de una misma responsabilidad con motivos de cambio independientes.
|
||||
|
||||
### 4.2. Shotgun Surgery (Cambios dispersos)
|
||||
|
||||
Se produce cuando una modificación pequeña obliga a alterar numerosos componentes.
|
||||
|
||||
Puede indicar:
|
||||
|
||||
- Conocimiento duplicado.
|
||||
- Reglas distribuidas.
|
||||
- Contratos inestables.
|
||||
- Acoplamiento excesivo.
|
||||
|
||||
**Solución:** localizar el conocimiento compartido y reorganizar las responsabilidades cuando corresponda.
|
||||
|
||||
No introduzcas un componente central que termine convirtiéndose en una clase todopoderosa.
|
||||
|
||||
### 4.3. Parallel Inheritance (Herencia paralela)
|
||||
|
||||
Ocurre cuando ampliar una jerarquía obliga sistemáticamente a ampliar otra.
|
||||
|
||||
Puede indicar acoplamiento estructural.
|
||||
|
||||
**Posibles soluciones:**
|
||||
|
||||
- Composición.
|
||||
- Interfaces compartidas.
|
||||
- Revisión de las relaciones de herencia.
|
||||
- Eliminación de jerarquías artificiales.
|
||||
|
||||
No unifiques automáticamente jerarquías que representen conceptos distintos.
|
||||
|
||||
### 4.4. Refused Bequest (Herencia rechazada)
|
||||
|
||||
Una clase derivada no puede utilizar o respetar correctamente parte del contrato heredado.
|
||||
|
||||
Puede ser una señal de incumplimiento de LSP.
|
||||
|
||||
**Posibles soluciones:**
|
||||
|
||||
- Sustituir herencia por composición.
|
||||
- Revisar el contrato base.
|
||||
- Separar interfaces.
|
||||
- Rediseñar la especialización.
|
||||
|
||||
La existencia de métodos heredados no utilizados no demuestra por sí sola un problema.
|
||||
|
||||
### 4.5. Data Clumps (Agrupaciones de datos)
|
||||
|
||||
Los mismos parámetros o propiedades aparecen juntos repetidamente.
|
||||
|
||||
```typescript
|
||||
function calculateDistance(
|
||||
startLatitude: number,
|
||||
startLongitude: number,
|
||||
endLatitude: number,
|
||||
endLongitude: number,
|
||||
): number {
|
||||
// Calcular distancia.
|
||||
}
|
||||
```
|
||||
|
||||
Puede resultar apropiado introducir un concepto `Coordinates`.
|
||||
|
||||
```typescript
|
||||
interface Coordinates {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
function calculateDistance(
|
||||
start: Coordinates,
|
||||
end: Coordinates,
|
||||
): number {
|
||||
// Calcular distancia.
|
||||
}
|
||||
```
|
||||
|
||||
No extraigas automáticamente todas las agrupaciones de parámetros.
|
||||
|
||||
### 4.6. Dead Code (Código muerto)
|
||||
|
||||
Incluye código sin comportamiento alcanzable o sin consumidores reales.
|
||||
|
||||
Antes de eliminarlo:
|
||||
|
||||
- Comprueba referencias.
|
||||
- Revisa exports públicos.
|
||||
- Considera carga dinámica.
|
||||
- Comprueba registros de componentes.
|
||||
- Revisa mecanismos de reflexión o configuración.
|
||||
- Verifica que no existan consumidores externos.
|
||||
|
||||
**No asumas que un símbolo está sin utilizar únicamente porque una búsqueda textual no encuentre referencias directas.**
|
||||
|
||||
### 4.7. Lazy Class (Clase innecesariamente trivial)
|
||||
|
||||
Una clase puede parecer pequeña, pero representar legítimamente:
|
||||
|
||||
- Un objeto de valor.
|
||||
- Una entidad sencilla.
|
||||
- Un adaptador.
|
||||
- Un contrato de integración.
|
||||
- Un componente de infraestructura.
|
||||
|
||||
No elimines clases únicamente por tener pocas líneas.
|
||||
|
||||
Evalúa si aportan semántica, encapsulamiento o una responsabilidad real.
|
||||
|
||||
### 4.8. Message Chains (Cadenas de mensajes)
|
||||
|
||||
Las cadenas profundas pueden indicar conocimiento excesivo sobre estructuras internas ajenas.
|
||||
|
||||
```typescript
|
||||
const city = order.getCustomer().getAddress().getCity();
|
||||
```
|
||||
|
||||
Puede ser preferible una operación de dominio:
|
||||
|
||||
```typescript
|
||||
const city = order.getShippingCity();
|
||||
```
|
||||
|
||||
Sin embargo, no toda cadena de llamadas constituye un problema.
|
||||
|
||||
Son legítimas las APIs fluidas, transformaciones de colecciones y navegación por DTO cuando no introducen acoplamiento problemático.
|
||||
|
||||
### 4.9. Middle Man (Intermediario innecesario)
|
||||
|
||||
Un componente delega todas sus operaciones sin aportar comportamiento relevante.
|
||||
|
||||
Antes de eliminarlo, comprueba si representa:
|
||||
|
||||
- Un límite arquitectónico.
|
||||
- Un contrato estable.
|
||||
- Una separación entre módulos.
|
||||
- Un adaptador.
|
||||
- Un mecanismo de autorización.
|
||||
- Un límite transaccional.
|
||||
|
||||
Una delegación aparentemente trivial puede tener una finalidad legítima.
|
||||
|
||||
---
|
||||
|
||||
## 5. Estrategias de prevención
|
||||
|
||||
### 5.1. Aplicar SOLID con criterio
|
||||
|
||||
Utiliza SOLID para organizar responsabilidades y controlar dependencias.
|
||||
|
||||
No introduzcas interfaces o capas innecesarias únicamente para cumplir formalmente sus principios.
|
||||
|
||||
### 5.2. Utilizar TDD cuando resulte apropiado
|
||||
|
||||
Las pruebas pueden revelar:
|
||||
|
||||
- Responsabilidades difíciles de aislar.
|
||||
- Dependencias excesivas.
|
||||
- Contratos ambiguos.
|
||||
- Efectos secundarios inesperados.
|
||||
|
||||
TDD debe ser una práctica preferente, no una obligación universal.
|
||||
|
||||
### 5.3. Utilizar Object Calisthenics como herramienta de análisis
|
||||
|
||||
Sus reglas ayudan a detectar posibles problemas de:
|
||||
|
||||
- Anidamiento.
|
||||
- Tamaño.
|
||||
- Encapsulamiento.
|
||||
- Responsabilidad.
|
||||
- Cohesión.
|
||||
|
||||
No conviertas los límites numéricos en prohibiciones absolutas.
|
||||
|
||||
### 5.4. Realizar revisiones de código
|
||||
|
||||
Las revisiones permiten detectar problemas que no siempre identifican las herramientas automáticas.
|
||||
|
||||
Prioriza:
|
||||
|
||||
- Corrección funcional.
|
||||
- Claridad.
|
||||
- Seguridad.
|
||||
- Contratos.
|
||||
- Responsabilidades.
|
||||
- Dependencias.
|
||||
- Complejidad accidental.
|
||||
|
||||
### 5.5. Refactorizar de forma incremental
|
||||
|
||||
No permitas que los problemas relevantes se acumulen indefinidamente.
|
||||
|
||||
Sin embargo, evita refactorizaciones generales no solicitadas o desconectadas de la tarea.
|
||||
|
||||
### 5.6. Utilizar análisis estático
|
||||
|
||||
Aprovecha las herramientas establecidas por el proyecto:
|
||||
|
||||
- TypeScript.
|
||||
- ESLint.
|
||||
- Formateadores.
|
||||
- Análisis de dependencias.
|
||||
- Herramientas de detección de código duplicado.
|
||||
- Comprobaciones de complejidad.
|
||||
|
||||
Las métricas automáticas son indicadores, no sustitutos del criterio de ingeniería.
|
||||
|
||||
---
|
||||
|
||||
## 6. Qué hacer cuando detectes un Code Smell
|
||||
|
||||
Sigue este procedimiento:
|
||||
|
||||
### Paso 1. Confirmar el problema
|
||||
|
||||
Determina si realmente perjudica la comprensión, el mantenimiento, las pruebas o la evolución.
|
||||
|
||||
### Paso 2. Evaluar el alcance
|
||||
|
||||
Comprueba si el problema está relacionado con la tarea actual.
|
||||
|
||||
No amplíes innecesariamente el alcance.
|
||||
|
||||
### Paso 3. Evaluar el riesgo
|
||||
|
||||
Considera:
|
||||
|
||||
- Consumidores afectados.
|
||||
- Contratos públicos.
|
||||
- Persistencia.
|
||||
- Compatibilidad.
|
||||
- Dependencias.
|
||||
- Pruebas disponibles.
|
||||
|
||||
### Paso 4. Proteger el comportamiento
|
||||
|
||||
Utiliza pruebas existentes, pruebas de caracterización u otros mecanismos de verificación.
|
||||
|
||||
No exijas escribir pruebas nuevas cuando no sean viables, pero mantén un procedimiento de comprobación adecuado.
|
||||
|
||||
### Paso 5. Refactorizar incrementalmente
|
||||
|
||||
Realiza cambios pequeños y cohesionados.
|
||||
|
||||
Evita combinar refactorizaciones extensas con cambios funcionales no relacionados.
|
||||
|
||||
### Paso 6. Verificar
|
||||
|
||||
Ejecuta:
|
||||
|
||||
- Pruebas relacionadas.
|
||||
- Comprobaciones de tipos.
|
||||
- Análisis estático.
|
||||
- Compilación cuando corresponda.
|
||||
- Verificaciones adicionales según el impacto.
|
||||
|
||||
### Paso 7. Comunicar el resultado
|
||||
|
||||
Identifica:
|
||||
|
||||
- Problema detectado.
|
||||
- Cambio realizado.
|
||||
- Beneficio esperado.
|
||||
- Verificaciones ejecutadas.
|
||||
- Limitaciones pendientes.
|
||||
|
||||
No declares una refactorización como verificada cuando no hayas ejecutado las comprobaciones pertinentes.
|
||||
|
||||
---
|
||||
|
||||
## 7. Prioridad de los problemas detectados
|
||||
|
||||
No todos los Code Smells tienen la misma importancia.
|
||||
|
||||
| Prioridad | Situación | Actuación recomendada |
|
||||
|---|---|---|
|
||||
| **Crítica** | Riesgo de corrupción de datos, vulnerabilidad o incumplimiento grave de invariantes. | Corregir o comunicar inmediatamente según el alcance y las autorizaciones. |
|
||||
| **Alta** | Responsabilidades mezcladas que provocan errores o cambios difíciles. | Priorizar si afecta a la tarea. |
|
||||
| **Media** | Duplicación de reglas, acoplamiento o complejidad mantenible pero relevante. | Planificar su corrección cuando exista oportunidad justificada. |
|
||||
| **Baja** | Problemas estéticos, nombres mejorables o métricas aisladas. | Corregir únicamente si aporta valor y está dentro del alcance. |
|
||||
|
||||
Esta clasificación es orientativa.
|
||||
|
||||
La prioridad real depende del impacto, la probabilidad de fallo, la criticidad del sistema y el coste de corrección.
|
||||
|
||||
---
|
||||
|
||||
## 8. Lista de comprobación de Code Smells
|
||||
|
||||
### Responsabilidades
|
||||
|
||||
- [ ] ¿Los componentes tienen responsabilidades coherentes?
|
||||
- [ ] ¿Existen clases con motivos de cambio independientes?
|
||||
- [ ] ¿Se están concentrando operaciones sin relación clara?
|
||||
- [ ] ¿La organización facilita localizar el comportamiento?
|
||||
|
||||
### Complejidad
|
||||
|
||||
- [ ] ¿Los métodos son comprensibles?
|
||||
- [ ] ¿El anidamiento dificulta seguir el flujo?
|
||||
- [ ] ¿Existen condiciones complejas que puedan simplificarse?
|
||||
- [ ] ¿He evitado fragmentaciones artificiales?
|
||||
- [ ] ¿Las abstracciones responden a necesidades reales?
|
||||
|
||||
### Acoplamiento
|
||||
|
||||
- [ ] ¿Los componentes conocen detalles internos ajenos?
|
||||
- [ ] ¿Existen dependencias innecesarias?
|
||||
- [ ] ¿Las reglas de negocio están ubicadas correctamente?
|
||||
- [ ] ¿Los contratos públicos son estables?
|
||||
- [ ] ¿Los cambios requieren modificar demasiados componentes?
|
||||
|
||||
### Duplicación
|
||||
|
||||
- [ ] ¿Existe conocimiento de negocio duplicado?
|
||||
- [ ] ¿Las duplicaciones representan realmente el mismo concepto?
|
||||
- [ ] ¿Una abstracción compartida simplificaría el mantenimiento?
|
||||
- [ ] ¿He evitado aplicar mecánicamente la regla de tres?
|
||||
|
||||
### Seguridad de la refactorización
|
||||
|
||||
- [ ] ¿He comprobado el comportamiento existente?
|
||||
- [ ] ¿Los cambios están relacionados con la tarea?
|
||||
- [ ] ¿Se conservan los contratos y la compatibilidad?
|
||||
- [ ] ¿He ejecutado las verificaciones pertinentes?
|
||||
- [ ] ¿He evitado cambios adicionales innecesarios?
|
||||
|
||||
---
|
||||
|
||||
## Principio final
|
||||
|
||||
> "Un Code Smell indica dónde puede existir un problema de diseño, pero no determina automáticamente cómo debe resolverse."
|
||||
|
||||
Utiliza los indicadores de esta guía para orientar el análisis, no para imponer refactorizaciones mecánicas.
|
||||
|
||||
La calidad del software depende de su corrección, claridad, cohesión, capacidad de evolución y coste de mantenimiento.
|
||||
|
||||
**No refactorices para satisfacer métricas. Refactoriza para resolver problemas reales de diseño y mantenimiento, preservando el comportamiento y evitando complejidad innecesaria.**
|
||||
842
.agents/skills/rdx-solid/referencias/complejidad.md
Normal file
842
.agents/skills/rdx-solid/referencias/complejidad.md
Normal file
@ -0,0 +1,842 @@
|
||||
# Gestión de la complejidad (Managing Complexity)
|
||||
|
||||
La complejidad es uno de los principales factores que afectan a la comprensión, evolución y mantenimiento del software.
|
||||
|
||||
El objetivo de una buena ingeniería no es eliminar toda complejidad, sino distinguir aquella que pertenece al problema de la que introducimos mediante nuestras decisiones técnicas.
|
||||
|
||||
Este documento desarrolla los principios establecidos en el `SKILL.md` principal.
|
||||
|
||||
**El objetivo es minimizar la complejidad accidental y expresar claramente la complejidad esencial, sin sacrificar la corrección, la seguridad ni los requisitos del sistema.**
|
||||
|
||||
## 1. Los dos tipos de complejidad
|
||||
|
||||
### 1.1. Complejidad esencial (Essential Complexity)
|
||||
|
||||
Es la complejidad inherente al problema que debe resolver el software.
|
||||
|
||||
Puede incluir:
|
||||
|
||||
- Reglas de negocio.
|
||||
- Lógica del dominio.
|
||||
- Requisitos funcionales.
|
||||
- Restricciones legales o normativas.
|
||||
- Estados y transiciones de procesos.
|
||||
- Relaciones entre entidades.
|
||||
- Requisitos de consistencia y seguridad.
|
||||
|
||||
No puede eliminarse simplemente mediante una refactorización si forma parte real de los requisitos.
|
||||
|
||||
Sin embargo, sí puede organizarse, encapsularse y expresarse de una manera más comprensible.
|
||||
|
||||
**Ejemplo:** un sistema de facturación debe contemplar impuestos, descuentos, monedas y reglas de redondeo.
|
||||
|
||||
Estas reglas pueden ser complejas por naturaleza. El objetivo es modelarlas correctamente, no ocultarlas mediante simplificaciones que alteren su comportamiento.
|
||||
|
||||
### 1.2. Complejidad accidental (Accidental Complexity)
|
||||
|
||||
Es la complejidad introducida por nuestras propias decisiones de implementación, organización o arquitectura.
|
||||
|
||||
Puede incluir:
|
||||
|
||||
- Abstracciones incorrectas.
|
||||
- Capas intermedias innecesarias.
|
||||
- Dependencias excesivas.
|
||||
- Configuraciones complejas sin justificación.
|
||||
- Patrones de diseño aplicados artificialmente.
|
||||
- Duplicación de conocimiento.
|
||||
- Acoplamiento entre componentes.
|
||||
- Estado global difícil de controlar.
|
||||
- Deuda técnica.
|
||||
- Mecanismos proporcionados por frameworks que podrían resolverse de manera más directa.
|
||||
|
||||
La complejidad accidental debe reducirse cuando hacerlo aporte una mejora real y el coste de la modificación esté justificado.
|
||||
|
||||
### Principio fundamental
|
||||
|
||||
> "La mejor solución expresa correctamente la complejidad del dominio introduciendo la menor complejidad técnica razonable."
|
||||
|
||||
No confundas simplicidad con ausencia de estructura.
|
||||
|
||||
En ocasiones, introducir una abstracción, un módulo o un contrato adicional reduce significativamente la complejidad global.
|
||||
|
||||
---
|
||||
|
||||
## 2. Detección de la complejidad
|
||||
|
||||
Existen tres indicadores especialmente útiles para identificar problemas estructurales.
|
||||
|
||||
### 2.1. Amplificación de cambios (Change Amplification)
|
||||
|
||||
Se produce cuando una modificación relativamente pequeña obliga a modificar numerosas partes del sistema.
|
||||
|
||||
**Síntoma habitual:**
|
||||
|
||||
"Para añadir este campo tengo que modificar quince archivos."
|
||||
|
||||
**Posibles causas:**
|
||||
|
||||
- Responsabilidades dispersas.
|
||||
- Conocimiento duplicado.
|
||||
- Contratos mal definidos.
|
||||
- Acoplamiento entre módulos.
|
||||
- Transformaciones redundantes de datos.
|
||||
- Límites arquitectónicos inadecuados.
|
||||
|
||||
### Cómo actuar
|
||||
|
||||
1. Identifica por qué deben modificarse tantos componentes.
|
||||
2. Distingue los cambios necesarios de los redundantes.
|
||||
3. Comprueba si existe conocimiento duplicado.
|
||||
4. Revisa los contratos y límites entre módulos.
|
||||
5. Valora una reorganización que reduzca futuros cambios.
|
||||
|
||||
No asumas que modificar muchos archivos constituye automáticamente un problema.
|
||||
|
||||
En arquitecturas modulares, una modificación legítima de un contrato puede requerir cambios explícitos en varios consumidores.
|
||||
|
||||
**Lo importante es evitar cambios innecesarios, no minimizar artificialmente el número de archivos modificados.**
|
||||
|
||||
### 2.2. Carga cognitiva (Cognitive Load)
|
||||
|
||||
Representa el esfuerzo mental necesario para comprender un comportamiento.
|
||||
|
||||
**Síntoma habitual:**
|
||||
|
||||
"Para entender esta función necesito comprender otras diez clases."
|
||||
|
||||
**Posibles causas:**
|
||||
|
||||
- Dependencias ocultas.
|
||||
- Exceso de abstracciones.
|
||||
- Nomenclatura poco clara.
|
||||
- Acoplamiento excesivo.
|
||||
- Flujo de ejecución fragmentado.
|
||||
- Responsabilidades mal distribuidas.
|
||||
- Estado compartido difícil de rastrear.
|
||||
|
||||
### Cómo actuar
|
||||
|
||||
- Utiliza nombres expresivos.
|
||||
- Mantén las responsabilidades cohesionadas.
|
||||
- Haz explícitas las dependencias relevantes.
|
||||
- Evita capas que únicamente deleguen operaciones.
|
||||
- Reduce los saltos innecesarios entre archivos.
|
||||
- Organiza el código según las necesidades de comprensión y mantenimiento.
|
||||
|
||||
No fragmentes funciones o clases únicamente para cumplir límites de líneas.
|
||||
|
||||
Una implementación lineal y comprensible puede ser preferible a varias funciones auxiliares sin significado propio.
|
||||
|
||||
### 2.3. Incógnitas desconocidas (Unknown Unknowns)
|
||||
|
||||
Son situaciones en las que el comportamiento del sistema resulta inesperado porque existen relaciones, efectos o dependencias que no se habían identificado.
|
||||
|
||||
**Síntoma habitual:**
|
||||
|
||||
"He modificado esta función y se ha roto una funcionalidad aparentemente independiente."
|
||||
|
||||
**Posibles causas:**
|
||||
|
||||
- Estado global.
|
||||
- Efectos secundarios ocultos.
|
||||
- Dependencias implícitas.
|
||||
- Contratos no documentados.
|
||||
- Comportamientos compartidos.
|
||||
- Acoplamiento entre módulos.
|
||||
- Orden de ejecución no evidente.
|
||||
|
||||
### Cómo actuar
|
||||
|
||||
- Identifica dependencias y efectos secundarios.
|
||||
- Define contratos claros.
|
||||
- Mejora la cobertura de los comportamientos relevantes.
|
||||
- Reduce el estado global mutable.
|
||||
- Utiliza pruebas de integración cuando correspondan.
|
||||
- Evita operaciones que modifiquen estados ajenos de forma inesperada.
|
||||
- Mantén trazabilidad suficiente de las operaciones relevantes.
|
||||
|
||||
**La previsibilidad del sistema es una característica fundamental de su mantenibilidad.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Valores de Extreme Programming (XP)
|
||||
|
||||
Extreme Programming propone cinco valores que ayudan a controlar la complejidad.
|
||||
|
||||
### 3.1. Comunicación (Communication)
|
||||
|
||||
El código debe comunicar claramente su propósito.
|
||||
|
||||
Contribuyen a ello:
|
||||
|
||||
- Nombres expresivos.
|
||||
- Estructuras comprensibles.
|
||||
- Contratos claros.
|
||||
- Pruebas significativas.
|
||||
- Documentación relevante.
|
||||
- Revisiones de código.
|
||||
|
||||
La comunicación no consiste en añadir comentarios indiscriminadamente, sino en reducir el esfuerzo necesario para comprender las decisiones importantes.
|
||||
|
||||
### 3.2. Simplicidad (Simplicity)
|
||||
|
||||
Implementa la solución más sencilla que satisfaga correctamente los requisitos.
|
||||
|
||||
No confundas simplicidad con:
|
||||
|
||||
- Menor número de líneas.
|
||||
- Menor número de clases.
|
||||
- Ausencia de abstracciones.
|
||||
- Eliminación de validaciones.
|
||||
- Reducción de controles de seguridad.
|
||||
|
||||
Una solución es sencilla cuando permite comprender su funcionamiento y modificarla con un esfuerzo razonable.
|
||||
|
||||
### 3.3. Retroalimentación (Feedback)
|
||||
|
||||
Los ciclos de retroalimentación permiten detectar errores y problemas de diseño antes de que se acumulen.
|
||||
|
||||
Herramientas habituales:
|
||||
|
||||
- TDD cuando resulte apropiado.
|
||||
- Pruebas automatizadas.
|
||||
- Integración continua.
|
||||
- Análisis estático.
|
||||
- Revisiones de código.
|
||||
- Monitorización.
|
||||
- Validación de requisitos.
|
||||
|
||||
Prioriza mecanismos de verificación que proporcionen información útil de manera rápida y reproducible.
|
||||
|
||||
### 3.4. Valentía (Courage)
|
||||
|
||||
La valentía técnica consiste en corregir problemas relevantes cuando existe una justificación y es posible controlar el riesgo.
|
||||
|
||||
No significa refactorizar agresivamente todo el código que se encuentre.
|
||||
|
||||
Actúa cuando:
|
||||
|
||||
- La complejidad bloquea funcionalidades.
|
||||
- El diseño provoca errores.
|
||||
- Las dependencias impiden avanzar.
|
||||
- La deuda técnica genera un coste significativo.
|
||||
- Existe una oportunidad de mejora relacionada con la tarea.
|
||||
|
||||
### 3.5. Respeto (Respect)
|
||||
|
||||
Escribe código pensando en quienes tendrán que comprenderlo y mantenerlo.
|
||||
|
||||
Esto implica:
|
||||
|
||||
- Respetar las convenciones del proyecto.
|
||||
- Evitar cambios innecesarios.
|
||||
- Mantener contratos.
|
||||
- Comunicar decisiones relevantes.
|
||||
- No introducir complejidad injustificada.
|
||||
- Preservar el trabajo existente que funciona correctamente.
|
||||
|
||||
**La mantenibilidad también depende de respetar la continuidad y coherencia del proyecto.**
|
||||
|
||||
---
|
||||
|
||||
## 4. KISS — Keep It Simple
|
||||
|
||||
> "La solución más sencilla que satisface correctamente los requisitos suele ser la más adecuada."
|
||||
|
||||
KISS propone evitar complejidad que no aporta valor.
|
||||
|
||||
### Cómo aplicarlo
|
||||
|
||||
1. Comienza por comprender el problema.
|
||||
2. Identifica una solución directa.
|
||||
3. Añade complejidad únicamente cuando exista una necesidad.
|
||||
4. Prefiere mecanismos conocidos y suficientemente probados.
|
||||
5. Cuestiona las abstracciones nuevas.
|
||||
6. Compara el coste de mantenimiento de las alternativas.
|
||||
|
||||
### Ejemplo de sobreingeniería
|
||||
|
||||
```typescript
|
||||
class UserServiceFactoryProvider {
|
||||
private static instance: UserServiceFactoryProvider;
|
||||
|
||||
static getInstance(): UserServiceFactoryProvider {
|
||||
// Gestionar la instancia global.
|
||||
}
|
||||
|
||||
createFactory(): UserServiceFactory {
|
||||
// Construir una factoría adicional.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Esta estructura introduce varios niveles de creación sin demostrar una necesidad concreta.
|
||||
|
||||
### Alternativa sencilla
|
||||
|
||||
```typescript
|
||||
class UserService {
|
||||
constructor(
|
||||
private readonly repository: UserRepository,
|
||||
) {}
|
||||
|
||||
async getUser(id: UserId): Promise<User | null> {
|
||||
return this.repository.findById(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El servicio tiene una responsabilidad clara y recibe explícitamente su dependencia.
|
||||
|
||||
### Cuándo añadir complejidad
|
||||
|
||||
Puede estar justificado introducir más estructura cuando:
|
||||
|
||||
- Existen variantes reales de comportamiento.
|
||||
- Deben protegerse contratos importantes.
|
||||
- Se necesita desacoplar infraestructura.
|
||||
- Existen invariantes de dominio complejas.
|
||||
- La concurrencia exige mecanismos específicos.
|
||||
- El mantenimiento de la solución directa se vuelve costoso.
|
||||
|
||||
**KISS no significa evitar el diseño, sino utilizar el diseño estrictamente necesario.**
|
||||
|
||||
---
|
||||
|
||||
## 5. YAGNI — You Aren't Gonna Need It
|
||||
|
||||
> "No implementes funcionalidades hasta que exista una necesidad real."
|
||||
|
||||
YAGNI ayuda a evitar desarrollar capacidades destinadas únicamente a requisitos hipotéticos.
|
||||
|
||||
### Señales de alerta
|
||||
|
||||
- "Puede que lo necesitemos algún día".
|
||||
- "Sería interesante añadirlo".
|
||||
- "Por si acaso".
|
||||
- "Así estará preparado para cualquier cambio".
|
||||
- "Vamos a hacerlo completamente configurable aunque solo exista un caso".
|
||||
|
||||
Estas expresiones no demuestran necesariamente un problema, pero justifican revisar la necesidad.
|
||||
|
||||
### Costes de incumplir YAGNI
|
||||
|
||||
1. **Tiempo de desarrollo:** implementación de funcionalidades no necesarias.
|
||||
2. **Mantenimiento:** código adicional que debe evolucionar.
|
||||
3. **Carga cognitiva:** más elementos que comprender.
|
||||
4. **Riesgo de abstracción incorrecta:** decisiones basadas en necesidades futuras mal estimadas.
|
||||
5. **Superficie de fallos:** más estados, configuraciones y rutas de ejecución.
|
||||
|
||||
### Ejemplo
|
||||
|
||||
```typescript
|
||||
// Modelo diseñado para posibilidades todavía no requeridas.
|
||||
interface User {
|
||||
name: string;
|
||||
email: string;
|
||||
middleName?: string;
|
||||
secondaryEmail?: string;
|
||||
faxNumber?: string;
|
||||
linkedinProfile?: string;
|
||||
twitterHandle?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Si esas propiedades no corresponden a requisitos actuales, pueden introducir complejidad innecesaria.
|
||||
|
||||
### Alternativa
|
||||
|
||||
```typescript
|
||||
interface User {
|
||||
name: string;
|
||||
email: Email;
|
||||
}
|
||||
```
|
||||
|
||||
### Aplicación pragmática
|
||||
|
||||
YAGNI no impide:
|
||||
|
||||
- Implementar requisitos futuros ya aprobados.
|
||||
- Cumplir necesidades de escalabilidad definidas.
|
||||
- Mantener compatibilidad prevista contractualmente.
|
||||
- Diseñar puntos de extensión necesarios.
|
||||
- Preparar migraciones conocidas.
|
||||
- Incorporar controles de seguridad o integridad indispensables.
|
||||
|
||||
Distingue entre:
|
||||
|
||||
**Necesidades futuras definidas:** forman parte de los requisitos y pueden justificar decisiones actuales.
|
||||
|
||||
**Necesidades futuras hipotéticas:** no deben condicionar innecesariamente el diseño.
|
||||
|
||||
**No utilices YAGNI como justificación para ignorar requisitos no funcionales conocidos.**
|
||||
|
||||
---
|
||||
|
||||
## 6. DRY — Don't Repeat Yourself
|
||||
|
||||
> "Cada conocimiento relevante del sistema debería tener una representación única y no ambigua."
|
||||
|
||||
DRY busca evitar la duplicación de conocimiento, especialmente cuando varias implementaciones deben mantenerse sincronizadas.
|
||||
|
||||
### 6.1. Duplicación de código frente a duplicación de conocimiento
|
||||
|
||||
Dos fragmentos pueden parecer iguales sin representar el mismo concepto.
|
||||
|
||||
Por ejemplo, dos procesos pueden compartir inicialmente una secuencia de validación, cálculo y persistencia, pero responder a reglas de negocio independientes.
|
||||
|
||||
Unificarlos prematuramente puede generar acoplamiento.
|
||||
|
||||
Por el contrario, dos implementaciones diferentes de una misma regla fiscal pueden representar una duplicación de conocimiento peligrosa.
|
||||
|
||||
**DRY no significa eliminar toda repetición sintáctica.**
|
||||
|
||||
### 6.2. La regla de tres
|
||||
|
||||
La regla de tres es una heurística que recomienda observar varias repeticiones antes de extraer una abstracción.
|
||||
|
||||
```text id="90ck2r"
|
||||
Primera aparición → Implementar directamente.
|
||||
Segunda aparición → Evaluar la similitud.
|
||||
Tercera aparición → Revisar si existe una responsabilidad compartida.
|
||||
```
|
||||
|
||||
No constituye una obligación.
|
||||
|
||||
### Criterios de aplicación
|
||||
|
||||
- No extraigas automáticamente código después de tres repeticiones.
|
||||
- No esperes obligatoriamente a tres apariciones cuando ya exista conocimiento compartido.
|
||||
- Comprueba que los fragmentos representan la misma regla o responsabilidad.
|
||||
- Evita introducir abstracciones para similitudes superficiales.
|
||||
- Prefiere pequeñas duplicaciones cuando permitan mantener responsabilidades independientes.
|
||||
|
||||
### 6.3. Ejemplo de duplicación
|
||||
|
||||
```typescript
|
||||
async function processUserOrder(order: Order): Promise<void> {
|
||||
validateOrder(order);
|
||||
calculateTaxes(order);
|
||||
await saveOrder(order);
|
||||
}
|
||||
|
||||
async function processGuestOrder(order: Order): Promise<void> {
|
||||
validateOrder(order);
|
||||
calculateTaxes(order);
|
||||
await saveOrder(order);
|
||||
await sendGuestConfirmation(order);
|
||||
}
|
||||
```
|
||||
|
||||
Existe una secuencia compartida, pero debe comprobarse si representa exactamente el mismo proceso de negocio.
|
||||
|
||||
### 6.4. Extracción justificada
|
||||
|
||||
Si ambos procesos comparten realmente validación, cálculo y persistencia, puede utilizarse una operación común.
|
||||
|
||||
```typescript id="fcu9zd"
|
||||
async function persistValidatedOrder(order: Order): Promise<void> {
|
||||
validateOrder(order);
|
||||
calculateTaxes(order);
|
||||
await saveOrder(order);
|
||||
}
|
||||
```
|
||||
|
||||
```typescript id="jds64w"
|
||||
async function processUserOrder(order: Order): Promise<void> {
|
||||
await persistValidatedOrder(order);
|
||||
}
|
||||
|
||||
async function processGuestOrder(order: Order): Promise<void> {
|
||||
await persistValidatedOrder(order);
|
||||
await sendGuestConfirmation(order);
|
||||
}
|
||||
```
|
||||
|
||||
Este ejemplo presupone que `calculateTaxes()` actualiza correctamente el estado del pedido antes de persistirlo y que ambas operaciones comparten las mismas reglas.
|
||||
|
||||
En una implementación real deben definirse las garantías transaccionales y el tratamiento de errores.
|
||||
|
||||
### 6.5. Evitar abstracciones que alteren el comportamiento
|
||||
|
||||
No introduzcas callbacks genéricos o mecanismos de extensión si su única finalidad es eliminar unas pocas líneas repetidas.
|
||||
|
||||
Las abstracciones deben representar una responsabilidad coherente.
|
||||
|
||||
Cuando existan descuentos, impuestos, pagos y persistencia, el orden de las operaciones puede ser una regla de negocio.
|
||||
|
||||
**Nunca reorganices operaciones únicamente para reducir duplicación si ello altera sus resultados o garantías.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Separación de responsabilidades (Separation of Concerns)
|
||||
|
||||
> "Cada componente debe ocuparse de un conjunto coherente de responsabilidades."
|
||||
|
||||
La separación de responsabilidades permite organizar el software en unidades comprensibles y con dependencias controladas.
|
||||
|
||||
### Responsabilidades habituales
|
||||
|
||||
- Reglas de negocio.
|
||||
- Casos de uso.
|
||||
- Persistencia.
|
||||
- Presentación.
|
||||
- Validación.
|
||||
- Integración con servicios externos.
|
||||
- Comunicación y notificaciones.
|
||||
|
||||
No es necesario crear una clase independiente para cada una de estas operaciones en todos los proyectos.
|
||||
|
||||
### 7.1. Ejemplo de responsabilidades mezcladas
|
||||
|
||||
```typescript id="8pohc9"
|
||||
class OrderProcessor {
|
||||
async process(order: Order): Promise<void> {
|
||||
if (order.items.length === 0) {
|
||||
throw new EmptyOrderError();
|
||||
}
|
||||
|
||||
const total = order.items.reduce(
|
||||
(sum, item) => sum + item.price * item.quantity,
|
||||
0,
|
||||
);
|
||||
|
||||
const database = new Database();
|
||||
await database.saveOrder(order, total);
|
||||
|
||||
const emailClient = new EmailClient();
|
||||
await emailClient.send(order.customer.email, 'Pedido confirmado');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El procesador conoce directamente las reglas del pedido, la infraestructura de persistencia y el proveedor de correo.
|
||||
|
||||
### 7.2. Alternativa con responsabilidades explícitas
|
||||
|
||||
```typescript id="x9g6u2"
|
||||
class OrderProcessor {
|
||||
constructor(
|
||||
private readonly repository: OrderRepository,
|
||||
private readonly notifier: OrderNotifier,
|
||||
) {}
|
||||
|
||||
async process(order: Order): Promise<void> {
|
||||
order.validate();
|
||||
|
||||
const total = order.calculateTotal();
|
||||
|
||||
await this.repository.save(order, total);
|
||||
await this.notifier.notifyConfirmation(order);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
En esta alternativa:
|
||||
|
||||
- `Order` protege sus reglas de dominio.
|
||||
- `OrderRepository` abstrae la persistencia.
|
||||
- `OrderNotifier` representa las notificaciones.
|
||||
- `OrderProcessor` coordina el caso de uso.
|
||||
|
||||
No se han introducido validadores ni calculadoras independientes porque el ejemplo no demuestra que sean necesarios.
|
||||
|
||||
### 7.3. Separación no significa fragmentación
|
||||
|
||||
Evita crear estructuras como:
|
||||
|
||||
```text id="jvkg1z"
|
||||
OrderValidator
|
||||
OrderCalculationService
|
||||
OrderTotalFactory
|
||||
OrderPersistenceCoordinator
|
||||
OrderNotificationDispatcher
|
||||
OrderProcessingFacade
|
||||
```
|
||||
|
||||
si únicamente delegan llamadas triviales y no representan responsabilidades reales.
|
||||
|
||||
### 7.4. Consistencia entre operaciones
|
||||
|
||||
La separación de responsabilidades no garantiza por sí sola la consistencia del proceso.
|
||||
|
||||
Por ejemplo, después de guardar un pedido puede fallar el envío de correo.
|
||||
|
||||
Debe definirse si:
|
||||
|
||||
- El pedido permanece confirmado.
|
||||
- La notificación se reintenta.
|
||||
- Se registra un evento pendiente.
|
||||
- Se utiliza una cola.
|
||||
- La operación necesita compensación.
|
||||
|
||||
Estas decisiones deben responder a los requisitos de negocio y arquitectura.
|
||||
|
||||
**Una arquitectura con responsabilidades separadas también necesita contratos y garantías de ejecución bien definidos.**
|
||||
|
||||
---
|
||||
|
||||
## 8. Gestión de la deuda técnica (Technical Debt)
|
||||
|
||||
La deuda técnica representa decisiones o deficiencias que incrementan el coste futuro de mantenimiento y evolución del software.
|
||||
|
||||
No toda deuda técnica es producto de una mala decisión. Puede responder a compromisos deliberados.
|
||||
|
||||
### 8.1. Tipos de deuda técnica
|
||||
|
||||
**Deuda deliberada**
|
||||
|
||||
Se acepta conscientemente una solución limitada para satisfacer una necesidad concreta.
|
||||
|
||||
Debe conocerse su impacto y establecerse cuándo conviene revisarla.
|
||||
|
||||
**Deuda accidental**
|
||||
|
||||
Surge por errores de diseño, desconocimiento, decisiones inadecuadas o falta de comprensión del sistema.
|
||||
|
||||
**Degradación progresiva**
|
||||
|
||||
Se produce cuando el software pierde adecuación con el tiempo debido a cambios en requisitos, dependencias, plataformas o prácticas técnicas.
|
||||
|
||||
Puede aparecer incluso aunque la implementación inicial fuese correcta.
|
||||
|
||||
### 8.2. Regla del Boy Scout
|
||||
|
||||
> "Deja el código en mejores condiciones de las que lo encontraste."
|
||||
|
||||
Este principio debe aplicarse sin contradecir el control del alcance.
|
||||
|
||||
No significa que cualquier tarea autorice refactorizaciones no solicitadas.
|
||||
|
||||
### Aplicación recomendada
|
||||
|
||||
- Mejora los elementos directamente relacionados con la tarea cuando sea necesario.
|
||||
- Corrige problemas pequeños que impidan realizar correctamente el cambio.
|
||||
- Evita modificar código ajeno a la tarea.
|
||||
- No reformatees archivos completos sin necesidad.
|
||||
- No introduzcas refactorizaciones extensas de manera oportunista.
|
||||
- Comunica los problemas relevantes que detectes fuera del alcance.
|
||||
|
||||
### 8.3. Cuándo reducir deuda técnica
|
||||
|
||||
Prioriza la reducción de deuda cuando:
|
||||
|
||||
- Bloquea una funcionalidad.
|
||||
- Provoca errores.
|
||||
- Dificulta significativamente las pruebas.
|
||||
- Incrementa el riesgo de cambios.
|
||||
- Afecta a componentes que evolucionan frecuentemente.
|
||||
- Existe una tarea específica de mantenimiento o refactorización.
|
||||
- Su coste futuro supera razonablemente el coste de corregirla.
|
||||
|
||||
### 8.4. Cuándo posponer una refactorización
|
||||
|
||||
Puede ser apropiado posponerla cuando:
|
||||
|
||||
- El código es estable y no se prevén modificaciones.
|
||||
- Va a sustituirse próximamente.
|
||||
- El beneficio esperado es reducido.
|
||||
- El cambio presenta un riesgo desproporcionado.
|
||||
- No está relacionada con la tarea actual.
|
||||
- No existen mecanismos adecuados para verificar una modificación amplia.
|
||||
|
||||
La ausencia de pruebas no debe interpretarse como una prohibición absoluta de refactorizar.
|
||||
|
||||
Cuando sea necesario modificar código legado sin pruebas:
|
||||
|
||||
1. Analiza su comportamiento.
|
||||
2. Identifica los contratos afectados.
|
||||
3. Incorpora pruebas de caracterización cuando sea viable.
|
||||
4. Realiza cambios incrementales.
|
||||
5. Utiliza los mecanismos de verificación disponibles.
|
||||
|
||||
### 8.5. Registrar deuda técnica relevante
|
||||
|
||||
Cuando corresponda, documenta:
|
||||
|
||||
- Problema identificado.
|
||||
- Consecuencias.
|
||||
- Riesgo.
|
||||
- Motivo por el que se mantiene.
|
||||
- Posibles actuaciones futuras.
|
||||
|
||||
No generes documentación extensa para problemas triviales.
|
||||
|
||||
---
|
||||
|
||||
## 9. Los cuatro elementos del diseño simple (XP)
|
||||
|
||||
Los cuatro elementos del diseño simple establecen prioridades para evaluar la calidad estructural del software.
|
||||
|
||||
### 9.1. Supera las pruebas
|
||||
|
||||
El software debe funcionar correctamente y satisfacer los requisitos.
|
||||
|
||||
Las pruebas deben proporcionar confianza suficiente en los comportamientos relevantes.
|
||||
|
||||
No es necesario ejecutar indiscriminadamente toda la batería de pruebas para cada modificación si el alcance permite una verificación más proporcionada.
|
||||
|
||||
### 9.2. Expresa claramente la intención
|
||||
|
||||
El código debe comunicar su propósito.
|
||||
|
||||
Esto incluye:
|
||||
|
||||
- Nombres comprensibles.
|
||||
- Responsabilidades coherentes.
|
||||
- Contratos claros.
|
||||
- Estructuras previsibles.
|
||||
- Dependencias explícitas cuando corresponda.
|
||||
|
||||
### 9.3. Evita la duplicación de conocimiento
|
||||
|
||||
Aplica DRY con criterio.
|
||||
|
||||
No elimines duplicaciones sintácticas que representen conceptos independientes.
|
||||
|
||||
No introduzcas abstracciones prematuras para satisfacer una regla numérica.
|
||||
|
||||
### 9.4. Mantiene una estructura mínima suficiente
|
||||
|
||||
Evita elementos innecesarios:
|
||||
|
||||
- Clases.
|
||||
- Interfaces.
|
||||
- Métodos.
|
||||
- Capas.
|
||||
- Dependencias.
|
||||
- Configuraciones.
|
||||
- Mecanismos de extensión.
|
||||
|
||||
Sin embargo, no reduzcas artificialmente el número de componentes cuando ello perjudique la cohesión, claridad o mantenibilidad.
|
||||
|
||||
**El objetivo es minimizar la complejidad estructural, no utilizar siempre la menor cantidad posible de código.**
|
||||
|
||||
---
|
||||
|
||||
## 10. Estrategias para controlar la complejidad
|
||||
|
||||
### 10.1. Favorecer cambios incrementales
|
||||
|
||||
- Divide las modificaciones extensas en pasos comprensibles.
|
||||
- Conserva el comportamiento existente cuando corresponda.
|
||||
- Verifica los resultados de cada etapa.
|
||||
- Evita introducir numerosos cambios independientes simultáneamente.
|
||||
|
||||
### 10.2. Mantener contratos estables
|
||||
|
||||
- Define interfaces comprensibles.
|
||||
- Evita exponer detalles internos.
|
||||
- Identifica consumidores antes de modificar contratos públicos.
|
||||
- Respeta la compatibilidad requerida.
|
||||
|
||||
### 10.3. Controlar dependencias
|
||||
|
||||
- Reutiliza las bibliotecas existentes cuando sean suficientes.
|
||||
- Prefiere APIs nativas para necesidades sencillas.
|
||||
- Introduce dependencias nuevas únicamente cuando aporten ventajas claras.
|
||||
- Evita acoplar reglas de negocio a detalles técnicos innecesarios.
|
||||
|
||||
### 10.4. Aplicar abstracciones justificadas
|
||||
|
||||
Antes de introducir una abstracción, comprueba:
|
||||
|
||||
1. ¿Qué problema resuelve?
|
||||
2. ¿Qué conocimiento o responsabilidad encapsula?
|
||||
3. ¿Reduce la complejidad global?
|
||||
4. ¿Facilita las pruebas o el mantenimiento?
|
||||
5. ¿Existe una alternativa más sencilla?
|
||||
|
||||
### 10.5. Mantener la arquitectura proporcional
|
||||
|
||||
No todas las aplicaciones necesitan:
|
||||
|
||||
- Arquitectura hexagonal.
|
||||
- Clean Architecture.
|
||||
- Microservicios.
|
||||
- Sistemas de eventos.
|
||||
- Contenedores de dependencias.
|
||||
- Múltiples capas de servicios.
|
||||
|
||||
Utiliza estas estructuras cuando los requisitos y la complejidad del sistema lo justifiquen.
|
||||
|
||||
### 10.6. Respetar el código existente
|
||||
|
||||
- No refactorices fuera del alcance.
|
||||
- Mantén las convenciones del proyecto.
|
||||
- Evita cambios cosméticos innecesarios.
|
||||
- Conserva los contratos.
|
||||
- Comunica los riesgos relevantes.
|
||||
- No introduzcas nuevas abstracciones únicamente por preferencia personal.
|
||||
|
||||
---
|
||||
|
||||
## 11. Errores habituales al gestionar la complejidad
|
||||
|
||||
| Error | Consecuencia | Enfoque recomendado |
|
||||
|---|---|---|
|
||||
| Confundir simplicidad con pocas líneas | Código compacto pero difícil de comprender. | Evaluar claridad y complejidad global. |
|
||||
| Aplicar YAGNI de forma absoluta | Ignorar requisitos futuros ya definidos. | Diferenciar necesidades reales de hipótesis. |
|
||||
| Eliminar toda duplicación | Abstracciones incorrectas y acoplamiento. | Aplicar DRY al conocimiento compartido. |
|
||||
| Aplicar literalmente la regla de tres | Retrasar abstracciones necesarias o forzar extracciones. | Utilizarla como heurística. |
|
||||
| Crear una clase por responsabilidad mínima | Fragmentación excesiva. | Agrupar responsabilidades cohesionadas. |
|
||||
| Refactorizar indiscriminadamente | Cambios fuera del alcance y regresiones. | Controlar el impacto y justificar cambios. |
|
||||
| Evitar refactorizar por falta de pruebas | Acumulación de problemas estructurales. | Utilizar caracterización y verificación incremental. |
|
||||
| Introducir patrones prematuramente | Complejidad accidental. | Diseñar para problemas reales. |
|
||||
| Optimizar sin mediciones | Esfuerzo y complejidad sin beneficio demostrado. | Medir antes de optimizar cuando sea posible. |
|
||||
| Ocultar dependencias | Comportamientos inesperados. | Mantener contratos y colaboraciones claras. |
|
||||
|
||||
---
|
||||
|
||||
## 12. Lista de comprobación de complejidad
|
||||
|
||||
### Antes de implementar
|
||||
|
||||
- [ ] ¿Comprendo los requisitos reales?
|
||||
- [ ] ¿He distinguido la complejidad esencial de la accidental?
|
||||
- [ ] ¿Existe una solución directa y comprensible?
|
||||
- [ ] ¿Estoy introduciendo capacidades para necesidades hipotéticas?
|
||||
- [ ] ¿Puedo reutilizar mecanismos existentes?
|
||||
- [ ] ¿He evaluado el impacto sobre los contratos actuales?
|
||||
|
||||
### Durante la implementación
|
||||
|
||||
- [ ] ¿Cada abstracción tiene una responsabilidad clara?
|
||||
- [ ] ¿Las dependencias están justificadas?
|
||||
- [ ] ¿El código mantiene un flujo comprensible?
|
||||
- [ ] ¿Estoy evitando duplicar reglas de negocio?
|
||||
- [ ] ¿He evitado fragmentaciones artificiales?
|
||||
- [ ] ¿Estoy respetando el alcance de la tarea?
|
||||
- [ ] ¿La solución preserva las invariantes y garantías necesarias?
|
||||
|
||||
### Durante la refactorización
|
||||
|
||||
- [ ] ¿Existe un problema real que justifique el cambio?
|
||||
- [ ] ¿La modificación reduce la complejidad global?
|
||||
- [ ] ¿Se conserva el comportamiento observable?
|
||||
- [ ] ¿Dispongo de mecanismos de verificación adecuados?
|
||||
- [ ] ¿Estoy evitando cambios ajenos al objetivo?
|
||||
- [ ] ¿La nueva estructura será más sencilla de mantener?
|
||||
|
||||
### Al finalizar
|
||||
|
||||
- [ ] ¿La solución satisface los requisitos?
|
||||
- [ ] ¿Se han ejecutado las verificaciones pertinentes?
|
||||
- [ ] ¿El código expresa claramente su intención?
|
||||
- [ ] ¿Se han evitado dependencias innecesarias?
|
||||
- [ ] ¿Los contratos afectados mantienen la compatibilidad requerida?
|
||||
- [ ] ¿He identificado limitaciones o deuda técnica relevante?
|
||||
- [ ] ¿Otro desarrollador podría comprender y modificar la solución razonablemente?
|
||||
|
||||
---
|
||||
|
||||
## Principio final
|
||||
|
||||
> "La complejidad que pertenece al problema debe modelarse correctamente. La complejidad que introducimos nosotros debe justificarse."
|
||||
|
||||
Aplica KISS, YAGNI, DRY y los valores de Extreme Programming como herramientas para tomar mejores decisiones, no como restricciones mecánicas.
|
||||
|
||||
Evita tanto la sobreingeniería como las simplificaciones que comprometan requisitos, contratos o seguridad.
|
||||
|
||||
**La solución preferible es aquella que satisface correctamente las necesidades actuales y los requisitos definidos, introduce la menor complejidad accidental razonable y permite evolucionar el sistema de forma segura y mantenible.**
|
||||
930
.agents/skills/rdx-solid/referencias/diseño-de-objetos.md
Normal file
930
.agents/skills/rdx-solid/referencias/diseño-de-objetos.md
Normal file
@ -0,0 +1,930 @@
|
||||
# Diseño orientado a objetos (Object-Oriented Design)
|
||||
|
||||
El diseño orientado a objetos permite organizar el software mediante componentes que encapsulan estado, comportamiento y responsabilidades.
|
||||
|
||||
Su objetivo es facilitar la comprensión, evolución y mantenimiento del sistema mediante objetos cohesionados, contratos claros y dependencias controladas.
|
||||
|
||||
Este documento desarrolla los principios establecidos en el `SKILL.md` principal y debe interpretarse siguiendo sus criterios de aplicación.
|
||||
|
||||
**El objetivo no es maximizar el número de clases, interfaces o patrones, sino encontrar una organización sencilla y coherente con el dominio y los requisitos reales.**
|
||||
|
||||
## 1. Diseño guiado por responsabilidades (Responsibility-Driven Design, RDD)
|
||||
|
||||
La idea fundamental del diseño guiado por responsabilidades es:
|
||||
|
||||
> "Los objetos se definen principalmente por sus responsabilidades, no por los datos que almacenan."
|
||||
|
||||
Una responsabilidad describe aquello que un objeto conoce, hace o decide dentro del sistema.
|
||||
|
||||
### 1.1. Identificación de objetos
|
||||
|
||||
Al analizar requisitos, utiliza las siguientes pistas:
|
||||
|
||||
1. **Sustantivos:** pueden sugerir entidades, objetos de valor o conceptos del dominio.
|
||||
2. **Verbos:** pueden sugerir operaciones, comportamientos o casos de uso.
|
||||
3. **Conceptos del dominio:** pueden justificar objetos con identidad, objetos de valor o servicios especializados.
|
||||
|
||||
Por ejemplo, ante el requisito:
|
||||
|
||||
"Un cliente puede realizar un pedido que contiene productos y al que se pueden aplicar descuentos."
|
||||
|
||||
Podrían identificarse:
|
||||
|
||||
- `Customer`: cliente.
|
||||
- `Order`: pedido.
|
||||
- `OrderItem`: línea de pedido.
|
||||
- `Product`: producto.
|
||||
- `DiscountPolicy`: política de descuentos.
|
||||
|
||||
Sin embargo, **no conviertas automáticamente cada sustantivo en una clase ni cada verbo en un método**.
|
||||
|
||||
Comprueba que los elementos identificados representan conceptos reales y responsabilidades necesarias.
|
||||
|
||||
### 1.2. Identificación de responsabilidades
|
||||
|
||||
Para cada objeto, considera:
|
||||
|
||||
- **¿Qué conoce?** Información o estado del que es responsable.
|
||||
- **¿Qué hace?** Operaciones o comportamientos que proporciona.
|
||||
- **¿Qué decide?** Reglas o decisiones que le corresponden.
|
||||
- **¿Qué invariantes protege?** Condiciones que deben mantenerse válidas.
|
||||
- **¿De quién depende?** Colaboradores necesarios para cumplir su responsabilidad.
|
||||
|
||||
El objetivo es asignar cada comportamiento al componente que tenga la información y responsabilidad adecuadas.
|
||||
|
||||
### 1.3. Estereotipos de objetos
|
||||
|
||||
Los estereotipos permiten identificar responsabilidades habituales.
|
||||
|
||||
| Estereotipo | Propósito | Ejemplo |
|
||||
|---|---|---|
|
||||
| **Information Holder (Contenedor de información)** | Mantiene información o estado del dominio. | `User`, `Product`, `Address` |
|
||||
| **Structurer (Estructurador)** | Gestiona relaciones entre objetos. | `OrderItems`, `UserGroup` |
|
||||
| **Service Provider (Proveedor de servicios)** | Ejecuta operaciones o proporciona capacidades específicas. | `PaymentProcessor`, `EmailSender` |
|
||||
| **Coordinator (Coordinador)** | Orquesta procesos y la colaboración entre componentes. | `OrderFulfillmentService` |
|
||||
| **Controller (Controlador)** | Recibe solicitudes, gestiona decisiones de control y delega operaciones. | `CheckoutController` |
|
||||
| **Interfacer (Adaptador de interfaces)** | Adapta información o contratos entre sistemas. | `UserApiAdapter`, `DatabaseMapper` |
|
||||
|
||||
Estos estereotipos son orientativos.
|
||||
|
||||
Una clase puede combinar responsabilidades compatibles sin que ello implique necesariamente un problema de diseño.
|
||||
|
||||
Tampoco es obligatorio que toda clase encaje perfectamente en una de estas categorías.
|
||||
|
||||
### 1.4. Preguntas fundamentales
|
||||
|
||||
Para cada clase, módulo o componente, pregúntate:
|
||||
|
||||
1. ¿Cuál es su responsabilidad principal?
|
||||
2. ¿Qué motivos justificarían modificarlo?
|
||||
3. ¿Está asumiendo responsabilidades independientes?
|
||||
4. ¿Sus dependencias son necesarias?
|
||||
5. ¿Existe una organización más sencilla?
|
||||
6. ¿Representa correctamente un concepto o comportamiento del dominio?
|
||||
|
||||
Si las respuestas no resultan claras, revisa el diseño.
|
||||
|
||||
**No refactorices automáticamente una clase porque no corresponda a un patrón conocido o porque supere una métrica numérica.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Tell, Don't Ask (Ordena, no preguntes)
|
||||
|
||||
Este principio propone delegar el comportamiento en los objetos responsables, en lugar de consultar su estado interno y tomar decisiones desde el exterior.
|
||||
|
||||
### Ejemplo: retirada de dinero
|
||||
|
||||
**Menos adecuado: lógica de negocio fuera del objeto**
|
||||
|
||||
```typescript
|
||||
// MAL: El consumidor modifica directamente el saldo.
|
||||
if (account.getBalance() >= amount) {
|
||||
account.setBalance(account.getBalance() - amount);
|
||||
}
|
||||
```
|
||||
|
||||
Este enfoque permite que distintos consumidores implementen reglas diferentes y puede comprometer las invariantes de la cuenta.
|
||||
|
||||
**Más adecuado: comportamiento encapsulado**
|
||||
|
||||
```typescript
|
||||
const result = account.withdraw(amount);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
// Continuar con la operación.
|
||||
}
|
||||
```
|
||||
|
||||
La cuenta es responsable de comprobar si la retirada está permitida y actualizar su estado correctamente.
|
||||
|
||||
### Criterios de aplicación
|
||||
|
||||
- Encapsula las reglas de negocio junto al estado que deben proteger.
|
||||
- Evita que los consumidores reproduzcan decisiones propias de un objeto.
|
||||
- Expón operaciones que expresen intenciones del dominio.
|
||||
- Reduce la necesidad de consultar y modificar repetidamente el estado interno.
|
||||
|
||||
### Excepciones legítimas
|
||||
|
||||
No apliques *Tell, Don't Ask* indiscriminadamente.
|
||||
|
||||
Consultar datos es adecuado cuando se trabaja con:
|
||||
|
||||
- DTO.
|
||||
- Modelos de presentación.
|
||||
- Respuestas de API.
|
||||
- Objetos de configuración.
|
||||
- Estructuras destinadas al transporte de datos.
|
||||
- Consultas o proyecciones de solo lectura.
|
||||
|
||||
**El principio debe proteger el encapsulamiento y las invariantes, no impedir el acceso legítimo a la información.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Diseño por contrato (Design by Contract, DbC)
|
||||
|
||||
El diseño por contrato establece las condiciones que deben cumplirse antes, durante y después de ejecutar una operación.
|
||||
|
||||
### Elementos de un contrato
|
||||
|
||||
**Precondiciones:** condiciones que deben cumplirse antes de ejecutar una operación.
|
||||
|
||||
**Postcondiciones:** garantías que proporciona una operación al completarse correctamente.
|
||||
|
||||
**Invariantes:** condiciones que deben mantenerse válidas durante la vida observable del objeto.
|
||||
|
||||
### Ejemplo
|
||||
|
||||
```typescript
|
||||
class BankAccount {
|
||||
constructor(private balance: Money) {
|
||||
if (balance.isNegative()) {
|
||||
throw new InvalidInitialBalanceError();
|
||||
}
|
||||
}
|
||||
|
||||
withdraw(amount: Money): WithdrawResult {
|
||||
if (!amount.isPositive()) {
|
||||
return WithdrawResult.invalidAmount();
|
||||
}
|
||||
|
||||
if (this.balance.isLessThan(amount)) {
|
||||
return WithdrawResult.insufficientFunds();
|
||||
}
|
||||
|
||||
this.balance = this.balance.subtract(amount);
|
||||
|
||||
return WithdrawResult.success(this.balance);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
En este ejemplo:
|
||||
|
||||
- **Invariante:** el saldo nunca puede ser negativo.
|
||||
- **Precondición de éxito:** el importe debe ser positivo y no superar el saldo disponible.
|
||||
- **Postcondición de éxito:** el saldo disminuye exactamente en el importe retirado.
|
||||
- **Resultado de rechazo:** se devuelve un resultado que identifica el motivo sin modificar el saldo.
|
||||
|
||||
El ejemplo presupone que `Money` impide operaciones entre monedas incompatibles y utiliza una representación monetaria adecuada.
|
||||
|
||||
### Criterios de diseño por contrato
|
||||
|
||||
- Define las invariantes relevantes del dominio.
|
||||
- Valida las precondiciones en los límites apropiados.
|
||||
- Evita que las operaciones dejen objetos en estados inválidos.
|
||||
- Expresa claramente los resultados esperados.
|
||||
- Utiliza tipos para representar restricciones cuando sea apropiado.
|
||||
- No dupliques validaciones innecesariamente en todas las capas.
|
||||
- Distingue entre errores excepcionales y rechazos de negocio esperados.
|
||||
|
||||
No es obligatorio documentar expresamente cada precondición y postcondición cuando el contrato resulte evidente mediante los tipos, nombres y pruebas.
|
||||
|
||||
### Contratos y compatibilidad
|
||||
|
||||
Al modificar una operación existente:
|
||||
|
||||
- Identifica sus consumidores.
|
||||
- Mantén las garantías previamente establecidas.
|
||||
- No debilites invariantes sin una justificación funcional.
|
||||
- Evita cambios silenciosos en el tratamiento de errores.
|
||||
- Comprueba el principio de sustitución de Liskov cuando exista herencia o implementación de interfaces.
|
||||
|
||||
---
|
||||
|
||||
## 4. Composición frente a herencia (Composition Over Inheritance)
|
||||
|
||||
La composición permite construir comportamientos mediante la colaboración entre objetos.
|
||||
|
||||
La herencia permite especializar tipos y reutilizar contratos o comportamientos comunes.
|
||||
|
||||
**Prefiere composición cuando aporte mayor flexibilidad, menor acoplamiento o una representación más adecuada del dominio.**
|
||||
|
||||
### 4.1. Riesgos de la herencia
|
||||
|
||||
- Acoplamiento entre clases base y derivadas.
|
||||
- Dependencia de detalles de implementación heredados.
|
||||
- Dificultad para modificar clases base sin afectar a sus derivados.
|
||||
- Jerarquías difíciles de comprender.
|
||||
- Relaciones artificiales de tipo "es un".
|
||||
- Problemas de sustitución cuando las clases derivadas alteran contratos.
|
||||
|
||||
Estos riesgos no implican que la herencia sea incorrecta por definición.
|
||||
|
||||
### 4.2. Cuándo utilizar herencia
|
||||
|
||||
Puede ser apropiada cuando:
|
||||
|
||||
- Existe una relación conceptual real de especialización.
|
||||
- Las clases derivadas cumplen el contrato del tipo base.
|
||||
- El framework requiere una jerarquía concreta.
|
||||
- Se utiliza deliberadamente un patrón como *Template Method*.
|
||||
- La reutilización y extensión del comportamiento justifican la estructura.
|
||||
|
||||
No utilices herencia únicamente para compartir unas pocas líneas de implementación.
|
||||
|
||||
### 4.3. Composición mediante políticas
|
||||
|
||||
**Ejemplo basado en herencia:**
|
||||
|
||||
```typescript
|
||||
class PremiumUser extends User {
|
||||
getDiscount(): number {
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Este diseño puede resultar válido, pero vincula el comportamiento del descuento con la jerarquía de usuarios.
|
||||
|
||||
**Alternativa mediante composición:**
|
||||
|
||||
```typescript
|
||||
interface DiscountPolicy {
|
||||
calculate(): number;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
class User {
|
||||
constructor(
|
||||
private readonly discountPolicy: DiscountPolicy,
|
||||
) {}
|
||||
|
||||
getDiscount(): number {
|
||||
return this.discountPolicy.calculate();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
class PremiumDiscount implements DiscountPolicy {
|
||||
calculate(): number {
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
|
||||
class StandardDiscount implements DiscountPolicy {
|
||||
calculate(): number {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
class NoDiscount implements DiscountPolicy {
|
||||
calculate(): number {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
const premiumUser = new User(new PremiumDiscount());
|
||||
const standardUser = new User(new StandardDiscount());
|
||||
const regularUser = new User(new NoDiscount());
|
||||
```
|
||||
|
||||
La composición permite variar la política sin crear nuevas subclases de usuario.
|
||||
|
||||
### Criterios para elegir
|
||||
|
||||
- Utiliza composición cuando necesites combinar o sustituir comportamientos independientes.
|
||||
- Utiliza herencia cuando exista una especialización legítima con sustitución segura.
|
||||
- No introduzcas una interfaz y varias clases si un condicional sencillo resuelve adecuadamente un comportamiento estable.
|
||||
- Evita crear estrategias intercambiables para variaciones hipotéticas.
|
||||
- Considera funciones y objetos sencillos como alternativas a jerarquías complejas.
|
||||
|
||||
**La composición es una preferencia de diseño, no una prohibición de utilizar herencia.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Ley de Deméter (Law of Demeter)
|
||||
|
||||
La Ley de Deméter, también conocida como principio de mínimo conocimiento, propone limitar las dependencias de un componente respecto a las estructuras internas de otros.
|
||||
|
||||
De manera orientativa, un método debería colaborar principalmente con:
|
||||
|
||||
1. El propio objeto (`this`).
|
||||
2. Los parámetros recibidos.
|
||||
3. Los objetos que crea.
|
||||
4. Sus colaboradores directos.
|
||||
|
||||
### Ejemplo
|
||||
|
||||
**Acoplamiento a estructuras internas:**
|
||||
|
||||
```typescript
|
||||
const city = order.getCustomer().getAddress().getCity();
|
||||
```
|
||||
|
||||
Este código conoce la organización interna del pedido, el cliente y su dirección.
|
||||
|
||||
**Alternativa con una operación de dominio:**
|
||||
|
||||
```typescript
|
||||
const city = order.getShippingCity();
|
||||
```
|
||||
|
||||
La segunda alternativa puede reducir el acoplamiento cuando la ciudad de envío sea una responsabilidad del pedido.
|
||||
|
||||
### Interpretación correcta
|
||||
|
||||
La Ley de Deméter no significa que deba existir un único punto por línea.
|
||||
|
||||
No prohíbe:
|
||||
|
||||
- APIs fluidas.
|
||||
- Encadenamiento de operaciones sobre colecciones.
|
||||
- Acceso a estructuras de datos públicas.
|
||||
- Navegación legítima por DTO.
|
||||
- Composición de operaciones sobre valores inmutables.
|
||||
|
||||
### Criterios de aplicación
|
||||
|
||||
- Evita que una clase dependa de detalles internos innecesarios de sus colaboradores.
|
||||
- Prefiere operaciones que expresen comportamientos del dominio.
|
||||
- No añadas métodos delegadores triviales únicamente para ocultar una cadena de acceso.
|
||||
- Evalúa si la navegación introduce un acoplamiento real.
|
||||
- Prioriza la claridad y la estabilidad de los contratos.
|
||||
|
||||
---
|
||||
|
||||
## 6. Encapsulamiento (Encapsulation)
|
||||
|
||||
El encapsulamiento consiste en controlar el acceso al estado y a los detalles internos de un componente, exponiendo únicamente los contratos necesarios.
|
||||
|
||||
### 6.1. Niveles de encapsulamiento
|
||||
|
||||
1. **Datos:** controlar el acceso y modificación del estado.
|
||||
2. **Implementación:** ocultar los mecanismos internos.
|
||||
3. **Tipos:** desacoplar consumidores de implementaciones concretas cuando sea necesario.
|
||||
4. **Diseño:** ocultar decisiones arquitectónicas que no deban afectar a los consumidores.
|
||||
|
||||
No todos los componentes necesitan aplicar todos estos niveles.
|
||||
|
||||
### 6.2. Protección de invariantes
|
||||
|
||||
**Ejemplo problemático:**
|
||||
|
||||
```typescript
|
||||
class Order {
|
||||
public items: Item[] = [];
|
||||
public total = 0;
|
||||
}
|
||||
```
|
||||
|
||||
Un consumidor puede modificar el estado sin respetar las reglas del pedido.
|
||||
|
||||
```typescript
|
||||
order.items.push(item);
|
||||
order.total = -999;
|
||||
```
|
||||
|
||||
**Alternativa encapsulada:**
|
||||
|
||||
```typescript
|
||||
class Order {
|
||||
private readonly items: Item[] = [];
|
||||
private total: Money;
|
||||
|
||||
constructor() {
|
||||
this.total = Money.zero('EUR');
|
||||
}
|
||||
|
||||
addItem(item: Item): void {
|
||||
const nextTotal = this.total.add(item.total);
|
||||
|
||||
this.validateTotal(nextTotal);
|
||||
|
||||
this.items.push(item);
|
||||
this.total = nextTotal;
|
||||
}
|
||||
|
||||
getTotal(): Money {
|
||||
return this.total;
|
||||
}
|
||||
|
||||
private validateTotal(total: Money): void {
|
||||
if (total.isNegative()) {
|
||||
throw new InvalidOrderTotalError();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Este ejemplo presupone que `Money` es inmutable y que las operaciones utilizadas preservan sus invariantes.
|
||||
|
||||
### Consideraciones
|
||||
|
||||
- Mantén privadas las estructuras internas cuyo acceso pueda comprometer invariantes.
|
||||
- Expón operaciones de dominio para modificar estados protegidos.
|
||||
- Utiliza propiedades de solo lectura cuando sea adecuado.
|
||||
- Evita devolver referencias mutables que permitan modificar indirectamente el estado.
|
||||
- No introduzcas getters o métodos delegadores innecesarios.
|
||||
- No encapsules indiscriminadamente datos cuyo propósito legítimo sea ser públicos.
|
||||
|
||||
### Encapsulamiento e inmutabilidad
|
||||
|
||||
La inmutabilidad facilita razonar sobre el estado y evita modificaciones inesperadas.
|
||||
|
||||
Prefiere valores inmutables cuando resulte apropiado, especialmente para objetos de valor.
|
||||
|
||||
No obstante, las entidades y otros objetos pueden mantener estado mutable siempre que las modificaciones estén controladas.
|
||||
|
||||
---
|
||||
|
||||
## 7. Polimorfismo (Polymorphism)
|
||||
|
||||
El polimorfismo permite utilizar distintas implementaciones mediante un contrato común.
|
||||
|
||||
Puede mejorar la extensibilidad cuando existen comportamientos alternativos bien definidos.
|
||||
|
||||
**No debe utilizarse indiscriminadamente para sustituir todos los condicionales.**
|
||||
|
||||
### 7.1. Implementación mediante condicionales
|
||||
|
||||
```typescript
|
||||
type ShippingType = 'standard' | 'express' | 'overnight';
|
||||
|
||||
function calculateShipping(
|
||||
method: ShippingType,
|
||||
orderValue: number,
|
||||
): number {
|
||||
if (method === 'standard') {
|
||||
return orderValue < 50 ? 5 : 0;
|
||||
}
|
||||
|
||||
if (method === 'express') {
|
||||
return 15;
|
||||
}
|
||||
|
||||
return 25;
|
||||
}
|
||||
```
|
||||
|
||||
Esta implementación es sencilla y puede ser perfectamente adecuada cuando los métodos de envío son pocos y estables.
|
||||
|
||||
### 7.2. Alternativa polimórfica
|
||||
|
||||
Cuando los métodos de envío incorporan reglas propias y evolucionan independientemente, puede resultar útil definir un contrato. Las implementaciones canónicas de `StandardShipping`, `ExpressShipping` y el consumidor `calculateShipping` se mantienen en [principios-solid.md](principios-solid.md), sección 2.3. Aquí se muestra únicamente una variante adicional para evitar mantener copias del mismo código.
|
||||
|
||||
```typescript
|
||||
interface ShippingMethod {
|
||||
calculateCost(orderValue: number): number;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
class OvernightShipping implements ShippingMethod {
|
||||
calculateCost(_orderValue: number): number {
|
||||
return 25;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3. Cuándo utilizar polimorfismo
|
||||
|
||||
Considera este enfoque cuando:
|
||||
|
||||
- Existan varias implementaciones con comportamiento significativo.
|
||||
- Cada alternativa tenga reglas propias.
|
||||
- Sea necesario incorporar nuevos comportamientos sin modificar consumidores.
|
||||
- Los condicionales relacionados aparezcan repetidamente.
|
||||
- Las implementaciones deban probarse o evolucionar de forma independiente.
|
||||
|
||||
Evítalo cuando:
|
||||
|
||||
- El comportamiento sea muy sencillo.
|
||||
- Las alternativas sean pocas y estables.
|
||||
- La jerarquía incremente innecesariamente el número de componentes.
|
||||
- El contrato común sea artificial.
|
||||
- Una función o un mapa de operaciones proporcione una solución más clara.
|
||||
|
||||
**El polimorfismo debe reducir el acoplamiento o simplificar la evolución, no limitarse a eliminar estructuras `if` o `switch`.**
|
||||
|
||||
---
|
||||
|
||||
## 8. Objetos de valor y entidades (Value Objects vs Entities)
|
||||
|
||||
Los objetos de valor y las entidades representan conceptos diferentes dentro del modelo de dominio.
|
||||
|
||||
### 8.1. Objetos de valor (Value Objects)
|
||||
|
||||
Los objetos de valor:
|
||||
|
||||
- Se definen por sus atributos y significado.
|
||||
- No poseen una identidad independiente relevante para el dominio.
|
||||
- Deben ser inmutables desde la perspectiva de sus consumidores.
|
||||
- Se comparan por sus valores.
|
||||
- Pueden encapsular validaciones, invariantes y operaciones.
|
||||
|
||||
Ejemplos habituales:
|
||||
|
||||
- `Money`
|
||||
- `Email`
|
||||
- `Address`
|
||||
- `DateRange`
|
||||
|
||||
### Ejemplo: Money
|
||||
|
||||
```typescript
|
||||
class Money {
|
||||
constructor(
|
||||
private readonly amount: number,
|
||||
private readonly currency: string,
|
||||
) {
|
||||
if (!Number.isFinite(amount)) {
|
||||
throw new InvalidMoneyAmountError();
|
||||
}
|
||||
}
|
||||
|
||||
equals(other: Money): boolean {
|
||||
return (
|
||||
this.amount === other.amount &&
|
||||
this.currency === other.currency
|
||||
);
|
||||
}
|
||||
|
||||
add(other: Money): Money {
|
||||
if (this.currency !== other.currency) {
|
||||
throw new CurrencyMismatchError();
|
||||
}
|
||||
|
||||
return new Money(
|
||||
this.amount + other.amount,
|
||||
this.currency,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Este ejemplo ilustra la igualdad por valor y la inmutabilidad, pero no constituye una implementación monetaria completa.
|
||||
|
||||
En sistemas de producción, evita errores de precisión monetaria utilizando una representación adecuada, como unidades mínimas enteras o tipos decimales.
|
||||
|
||||
### Criterios para utilizar objetos de valor
|
||||
|
||||
- Introduce objetos de valor cuando exista semántica de dominio.
|
||||
- Encapsula validaciones e invariantes relevantes.
|
||||
- Evita utilizar tipos primitivos intercambiables cuando puedan provocar errores.
|
||||
- No crees objetos de valor para cualquier cadena o número.
|
||||
- Considera tipos nominales de TypeScript cuando únicamente necesites diferenciar identificadores.
|
||||
- No introduzcas abstracciones que no aporten seguridad, comportamiento o expresividad.
|
||||
|
||||
### 8.2. Entidades (Entities)
|
||||
|
||||
Las entidades:
|
||||
|
||||
- Poseen una identidad que permanece aunque cambien sus atributos.
|
||||
- Normalmente permiten modificar su estado mediante operaciones controladas.
|
||||
- Se comparan por identidad cuando corresponde.
|
||||
- Pueden encapsular reglas e invariantes del dominio.
|
||||
|
||||
Ejemplos habituales:
|
||||
|
||||
- `User`
|
||||
- `Order`
|
||||
- `Product`
|
||||
|
||||
### Ejemplo: User
|
||||
|
||||
```typescript
|
||||
class User {
|
||||
constructor(
|
||||
private readonly id: UserId,
|
||||
private email: Email,
|
||||
private name: Name,
|
||||
) {}
|
||||
|
||||
equals(other: User): boolean {
|
||||
return this.id.equals(other.id);
|
||||
}
|
||||
|
||||
changeEmail(newEmail: Email): void {
|
||||
this.email = newEmail;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El usuario conserva su identidad aunque cambie su dirección de correo electrónico.
|
||||
|
||||
La operación `changeEmail` podría incorporar reglas adicionales, como comprobar restricciones de negocio, según las responsabilidades del modelo.
|
||||
|
||||
### Diferencia fundamental
|
||||
|
||||
| Característica | Objeto de valor | Entidad |
|
||||
|---|---|---|
|
||||
| Identidad propia | No | Sí |
|
||||
| Comparación | Por valor | Por identidad |
|
||||
| Mutabilidad | Preferentemente inmutable | Puede modificar su estado |
|
||||
| Responsabilidad | Representar un concepto mediante sus valores | Representar un concepto con continuidad e identidad |
|
||||
| Ejemplos | `Money`, `Email` | `User`, `Order` |
|
||||
|
||||
No conviertas automáticamente todos los datos en entidades u objetos de valor.
|
||||
|
||||
Las estructuras de transporte de datos también tienen un papel legítimo dentro de una arquitectura.
|
||||
|
||||
---
|
||||
|
||||
## 9. Agregados (Aggregates)
|
||||
|
||||
Un agregado es un conjunto de entidades y objetos de valor que se trata como una unidad de consistencia dentro de un dominio.
|
||||
|
||||
Este concepto procede del diseño guiado por el dominio (*Domain-Driven Design*, DDD).
|
||||
|
||||
### 9.1. Elementos de un agregado
|
||||
|
||||
**Raíz del agregado (Aggregate Root):**
|
||||
|
||||
- Constituye el punto principal de acceso al agregado.
|
||||
- Protege sus invariantes.
|
||||
- Controla las modificaciones de los elementos internos.
|
||||
- Define los límites de consistencia.
|
||||
|
||||
**Elementos internos:**
|
||||
|
||||
- Pueden incluir entidades y objetos de valor.
|
||||
- Participan en las reglas del agregado.
|
||||
- No deberían modificarse desde el exterior eludiendo la raíz.
|
||||
|
||||
### 9.2. Ejemplo: pedido
|
||||
|
||||
Supongamos que un pedido tiene un importe máximo permitido.
|
||||
|
||||
La raíz del agregado debe impedir que una operación deje el pedido en un estado inválido.
|
||||
|
||||
```typescript
|
||||
class Order {
|
||||
private items: OrderItem[] = [];
|
||||
|
||||
addItem(product: Product, quantity: number): void {
|
||||
const item = new OrderItem(product, quantity);
|
||||
|
||||
const nextItems = [...this.items, item];
|
||||
const nextTotal = this.calculateTotal(nextItems);
|
||||
|
||||
this.validateTotal(nextTotal);
|
||||
|
||||
this.items = nextItems;
|
||||
}
|
||||
|
||||
removeItem(itemId: ItemId): void {
|
||||
const nextItems = this.items.filter(
|
||||
(item) => !item.id.equals(itemId),
|
||||
);
|
||||
|
||||
const nextTotal = this.calculateTotal(nextItems);
|
||||
|
||||
this.validateTotal(nextTotal);
|
||||
|
||||
this.items = nextItems;
|
||||
}
|
||||
|
||||
private calculateTotal(items: OrderItem[]): Money {
|
||||
return items.reduce(
|
||||
(total, item) => total.add(item.total),
|
||||
Money.zero('EUR'),
|
||||
);
|
||||
}
|
||||
|
||||
private validateTotal(total: Money): void {
|
||||
if (total.isGreaterThan(MAX_ORDER_VALUE)) {
|
||||
throw new OrderTotalExceededError();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El ejemplo presupone que los importes utilizan una moneda compatible y que `OrderItem` valida la cantidad y calcula correctamente su importe.
|
||||
|
||||
### 9.3. Preservación de invariantes
|
||||
|
||||
Observa que el ejemplo calcula el nuevo estado antes de modificar la colección.
|
||||
|
||||
El orden es importante:
|
||||
|
||||
1. Preparar el estado propuesto.
|
||||
2. Calcular sus consecuencias.
|
||||
3. Validar las invariantes.
|
||||
4. Aplicar la modificación.
|
||||
|
||||
De este modo, si la validación falla, el estado del pedido permanece sin cambios.
|
||||
|
||||
**No modifiques primero el agregado para validar después, salvo que exista un mecanismo fiable que garantice la reversión del estado.**
|
||||
|
||||
### 9.4. Acceso a través de la raíz
|
||||
|
||||
**Incorrecto: modificación externa que evita las reglas**
|
||||
|
||||
```typescript
|
||||
order.items.push(new OrderItem(product, 2));
|
||||
```
|
||||
|
||||
Este acceso no debe estar disponible si permite incumplir las invariantes del agregado.
|
||||
|
||||
**Correcto: operación controlada**
|
||||
|
||||
```typescript
|
||||
order.addItem(product, 2);
|
||||
```
|
||||
|
||||
La raíz aplica las reglas necesarias antes de modificar el estado.
|
||||
|
||||
### 9.5. Criterios para diseñar agregados
|
||||
|
||||
- Define agregados alrededor de invariantes que deban mantenerse consistentes.
|
||||
- Utiliza raíces de agregado cuando exista una necesidad real de controlar modificaciones conjuntas.
|
||||
- Evita agregados excesivamente grandes.
|
||||
- No incluyas entidades dentro de un mismo agregado únicamente porque estén relacionadas.
|
||||
- Considera el coste transaccional y la concurrencia.
|
||||
- Mantén referencias entre agregados mediante identificadores cuando sea apropiado.
|
||||
- Distingue las operaciones que necesitan consistencia inmediata de las que admiten consistencia eventual.
|
||||
- No introduzcas agregados DDD para modelos sencillos que no necesiten este nivel de organización.
|
||||
|
||||
### 9.6. Agregados y persistencia
|
||||
|
||||
La raíz del agregado controla las invariantes de dominio, pero no garantiza por sí sola la atomicidad de la persistencia.
|
||||
|
||||
Cuando sea necesario:
|
||||
|
||||
- Utiliza transacciones.
|
||||
- Considera mecanismos de control de concurrencia.
|
||||
- Evita modificaciones parciales de datos persistidos.
|
||||
- Define explícitamente los límites de consistencia.
|
||||
- Gestiona los conflictos entre operaciones concurrentes.
|
||||
|
||||
**La consistencia del agregado requiere tanto un modelo de dominio correcto como una estrategia de persistencia adecuada.**
|
||||
|
||||
---
|
||||
|
||||
## 10. Responsabilidades y servicios
|
||||
|
||||
No todas las operaciones pertenecen naturalmente a una entidad.
|
||||
|
||||
Algunas responsabilidades necesitan coordinar varios objetos o interactuar con servicios externos.
|
||||
|
||||
### Servicios de dominio
|
||||
|
||||
Representan operaciones del dominio que no pertenecen claramente a una única entidad u objeto de valor.
|
||||
|
||||
Deben contener comportamiento de negocio y evitar dependencias innecesarias de infraestructura.
|
||||
|
||||
### Servicios de aplicación
|
||||
|
||||
Orquestan casos de uso.
|
||||
|
||||
Pueden encargarse de:
|
||||
|
||||
- Coordinar entidades y servicios de dominio.
|
||||
- Controlar transacciones.
|
||||
- Invocar repositorios.
|
||||
- Gestionar operaciones externas.
|
||||
- Aplicar políticas de autorización cuando corresponda.
|
||||
|
||||
Evita trasladar todas las reglas de negocio a los servicios de aplicación si pertenecen naturalmente a las entidades u objetos de valor.
|
||||
|
||||
### Servicios de infraestructura
|
||||
|
||||
Implementan capacidades técnicas como:
|
||||
|
||||
- Persistencia.
|
||||
- Correo electrónico.
|
||||
- Acceso a archivos.
|
||||
- Comunicación con APIs externas.
|
||||
- Servicios de mensajería.
|
||||
|
||||
Cuando resulte apropiado, utiliza contratos que permitan mantener desacopladas las políticas de dominio de los detalles de infraestructura.
|
||||
|
||||
### Evitar servicios todopoderosos
|
||||
|
||||
Una clase denominada `ApplicationManager`, `BusinessService` o `DataProcessor` puede estar acumulando responsabilidades independientes.
|
||||
|
||||
Revisa:
|
||||
|
||||
- Si contiene operaciones sin relación clara.
|
||||
- Si necesita demasiadas dependencias.
|
||||
- Si cambia por motivos muy diferentes.
|
||||
- Si concentra reglas que deberían pertenecer a otros objetos.
|
||||
|
||||
No dividas automáticamente los servicios grandes sin identificar previamente responsabilidades coherentes.
|
||||
|
||||
---
|
||||
|
||||
## 11. Criterios para evitar la sobreingeniería
|
||||
|
||||
Un diseño orientado a objetos no es necesariamente mejor por incorporar más clases, interfaces o patrones.
|
||||
|
||||
Antes de introducir una abstracción, considera:
|
||||
|
||||
1. ¿Qué problema concreto resuelve?
|
||||
2. ¿Qué responsabilidad representa?
|
||||
3. ¿Reduce realmente el acoplamiento?
|
||||
4. ¿Simplifica las pruebas o el mantenimiento?
|
||||
5. ¿Existe una alternativa más directa?
|
||||
6. ¿Su coste estructural está justificado?
|
||||
|
||||
### Evita especialmente
|
||||
|
||||
- Interfaces con una única implementación creadas sin necesidad de desacoplamiento.
|
||||
- Jerarquías de herencia destinadas únicamente a compartir código trivial.
|
||||
- Factorías que solo encapsulan constructores sencillos.
|
||||
- Servicios que únicamente delegan llamadas.
|
||||
- Objetos de valor sin semántica propia.
|
||||
- Agregados sin invariantes relevantes.
|
||||
- Patrones introducidos para necesidades hipotéticas.
|
||||
- Refactorizaciones destinadas exclusivamente a cumplir límites numéricos.
|
||||
|
||||
### Favorece
|
||||
|
||||
- Responsabilidades claras.
|
||||
- Contratos comprensibles.
|
||||
- Dependencias controladas.
|
||||
- Composición cuando aporte flexibilidad.
|
||||
- Encapsulamiento de invariantes.
|
||||
- Simplicidad estructural.
|
||||
- Evolución incremental del diseño.
|
||||
|
||||
---
|
||||
|
||||
## 12. Lista de comprobación de diseño orientado a objetos
|
||||
|
||||
### Responsabilidades
|
||||
|
||||
- [ ] ¿Cada objeto tiene una responsabilidad identificable?
|
||||
- [ ] ¿Sus comportamientos pertenecen al concepto que representa?
|
||||
- [ ] ¿He evitado clases que acumulen responsabilidades independientes?
|
||||
- [ ] ¿Las dependencias son necesarias?
|
||||
- [ ] ¿La organización facilita la evolución del sistema?
|
||||
|
||||
### Encapsulamiento
|
||||
|
||||
- [ ] ¿Las invariantes están protegidas?
|
||||
- [ ] ¿El estado mutable se modifica mediante operaciones controladas?
|
||||
- [ ] ¿He evitado exponer referencias mutables que comprometan el estado?
|
||||
- [ ] ¿Los contratos públicos son claros?
|
||||
- [ ] ¿Los datos se exponen cuando su finalidad lo justifica?
|
||||
|
||||
### Composición y herencia
|
||||
|
||||
- [ ] ¿La composición mejora el diseño en este caso?
|
||||
- [ ] ¿Las relaciones de herencia representan especializaciones legítimas?
|
||||
- [ ] ¿Los subtipos respetan los contratos del tipo base?
|
||||
- [ ] ¿He evitado jerarquías artificiales?
|
||||
- [ ] ¿Las abstracciones responden a necesidades reales?
|
||||
|
||||
### Objetos de valor y entidades
|
||||
|
||||
- [ ] ¿He distinguido correctamente identidad y valor?
|
||||
- [ ] ¿Los objetos de valor protegen sus invariantes?
|
||||
- [ ] ¿Las entidades conservan su identidad al modificar sus atributos?
|
||||
- [ ] ¿He evitado encapsular primitivas innecesariamente?
|
||||
- [ ] ¿Las operaciones de dominio están correctamente ubicadas?
|
||||
|
||||
### Agregados
|
||||
|
||||
- [ ] ¿Existe una necesidad real de utilizar un agregado?
|
||||
- [ ] ¿La raíz protege las invariantes?
|
||||
- [ ] ¿Las modificaciones se realizan mediante operaciones controladas?
|
||||
- [ ] ¿Los límites de consistencia están correctamente definidos?
|
||||
- [ ] ¿He considerado transacciones y concurrencia cuando corresponde?
|
||||
- [ ] ¿He evitado agregados excesivamente grandes?
|
||||
|
||||
### Complejidad
|
||||
|
||||
- [ ] ¿La solución es suficientemente sencilla?
|
||||
- [ ] ¿He evitado patrones innecesarios?
|
||||
- [ ] ¿Existe una alternativa más directa?
|
||||
- [ ] ¿La estructura facilita las pruebas?
|
||||
- [ ] ¿He respetado la arquitectura existente?
|
||||
- [ ] ¿La modificación mantiene el comportamiento y los contratos requeridos?
|
||||
|
||||
---
|
||||
|
||||
## Principio final
|
||||
|
||||
> "Los objetos deben organizarse alrededor de responsabilidades reales y proteger las reglas del dominio, no existir únicamente para satisfacer patrones de diseño."
|
||||
|
||||
El diseño orientado a objetos debe facilitar la comprensión, la evolución y la verificación del software.
|
||||
|
||||
Utiliza entidades, objetos de valor, servicios, composición, herencia y agregados cuando aporten ventajas concretas.
|
||||
|
||||
**Una arquitectura sencilla, cohesionada y con contratos bien definidos es preferible a una estructura artificialmente compleja que únicamente aparenta seguir principios de diseño orientado a objetos.**
|
||||
1284
.agents/skills/rdx-solid/referencias/patrones-de-diseño.md
Normal file
1284
.agents/skills/rdx-solid/referencias/patrones-de-diseño.md
Normal file
File diff suppressed because it is too large
Load Diff
804
.agents/skills/rdx-solid/referencias/principios-solid.md
Normal file
804
.agents/skills/rdx-solid/referencias/principios-solid.md
Normal file
@ -0,0 +1,804 @@
|
||||
# Principios SOLID
|
||||
|
||||
## Introducción
|
||||
|
||||
Los principios SOLID constituyen un conjunto de criterios de diseño orientados a desarrollar software flexible, mantenible, comprensible y verificable.
|
||||
|
||||
Su aplicación adecuada permite:
|
||||
|
||||
- Reducir el acoplamiento innecesario.
|
||||
- Mejorar la cohesión de clases y módulos.
|
||||
- Facilitar la evolución de funcionalidades.
|
||||
- Proteger contratos y comportamientos existentes.
|
||||
- Simplificar las pruebas y el mantenimiento.
|
||||
- Controlar el impacto de los cambios.
|
||||
|
||||
Estos principios deben aplicarse con criterio profesional y teniendo en cuenta las necesidades reales del sistema.
|
||||
|
||||
**SOLID no exige utilizar siempre interfaces, herencia, patrones de diseño ni arquitecturas complejas.**
|
||||
|
||||
El objetivo es mejorar la estructura del software, no maximizar el número de abstracciones.
|
||||
|
||||
Este documento desarrolla los criterios establecidos en el `SKILL.md` principal.
|
||||
|
||||
---
|
||||
|
||||
## 1. S — Principio de responsabilidad única (Single Responsibility Principle, SRP)
|
||||
|
||||
> "Una clase debería tener una, y solo una, razón para cambiar."
|
||||
|
||||
### 1.1. Qué problema resuelve
|
||||
|
||||
SRP ayuda a evitar clases o módulos que acumulan responsabilidades independientes.
|
||||
|
||||
Estas estructuras suelen presentar:
|
||||
|
||||
- Dificultad para comprender su comportamiento.
|
||||
- Cambios frecuentes por motivos diferentes.
|
||||
- Pruebas complejas.
|
||||
- Dependencias excesivas.
|
||||
- Mayor riesgo de regresiones.
|
||||
- Acoplamiento entre funcionalidades que deberían evolucionar por separado.
|
||||
|
||||
### 1.2. Cómo aplicarlo
|
||||
|
||||
Cada clase o módulo debe representar una responsabilidad coherente.
|
||||
|
||||
Una responsabilidad no equivale necesariamente a un único método, operación o dato.
|
||||
|
||||
Una clase puede realizar varias operaciones relacionadas y seguir cumpliendo SRP.
|
||||
|
||||
**Ejemplo: responsabilidades mezcladas**
|
||||
|
||||
```typescript id="lzj3ab"
|
||||
// MAL: Mezcla reglas de negocio, persistencia y generación de documentos.
|
||||
class Order {
|
||||
calculateTotal(): Money {
|
||||
// Calcular el importe del pedido.
|
||||
}
|
||||
|
||||
async saveToDatabase(): Promise<void> {
|
||||
// Persistir el pedido.
|
||||
}
|
||||
|
||||
generateInvoice(): Invoice {
|
||||
// Generar la factura.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
En este caso, la clase puede cambiar por motivos independientes:
|
||||
|
||||
- Modificación de reglas de cálculo.
|
||||
- Cambios en la persistencia.
|
||||
- Cambios en la generación de facturas.
|
||||
|
||||
**Alternativa: responsabilidades diferenciadas**
|
||||
|
||||
```typescript id="u0b51p"
|
||||
class Order {
|
||||
private readonly items: OrderItem[] = [];
|
||||
|
||||
addItem(item: OrderItem): void {
|
||||
this.items.push(item);
|
||||
}
|
||||
|
||||
calculateTotal(): Money {
|
||||
return calculateOrderTotal(this.items);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript id="9drm12"
|
||||
class OrderRepository {
|
||||
async save(order: Order): Promise<void> {
|
||||
// Persistir el pedido.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript id="e1iz3a"
|
||||
class InvoiceGenerator {
|
||||
generate(order: Order): Invoice {
|
||||
// Generar una factura a partir del pedido.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La entidad `Order` mantiene las operaciones relacionadas con sus reglas de negocio, mientras que la persistencia y la generación de documentos pertenecen a responsabilidades diferenciadas.
|
||||
|
||||
Los ejemplos son ilustrativos y presuponen los tipos y colaboradores necesarios.
|
||||
|
||||
### 1.3. Preguntas para detectar problemas
|
||||
|
||||
- ¿Tiene esta clase varios motivos independientes para cambiar?
|
||||
- ¿Sus operaciones responden a una responsabilidad coherente?
|
||||
- ¿Distintos requisitos obligan a modificar partes no relacionadas?
|
||||
- ¿La clase depende de demasiados componentes externos?
|
||||
- ¿Sería más sencillo probar y mantener determinadas responsabilidades por separado?
|
||||
|
||||
### 1.4. Evitar una interpretación excesivamente estricta
|
||||
|
||||
No dividas una clase únicamente porque su descripción incluya la palabra «y».
|
||||
|
||||
Por ejemplo, una entidad `Order` puede:
|
||||
|
||||
- Añadir artículos.
|
||||
- Eliminar artículos.
|
||||
- Calcular importes.
|
||||
- Comprobar restricciones.
|
||||
- Cambiar de estado.
|
||||
|
||||
Todas estas operaciones pueden pertenecer a una única responsabilidad: gestionar el comportamiento del pedido.
|
||||
|
||||
**SRP busca separar motivos de cambio independientes, no reducir todas las clases a una única operación.**
|
||||
|
||||
---
|
||||
|
||||
## 2. O — Principio abierto/cerrado (Open/Closed Principle, OCP)
|
||||
|
||||
> "Las entidades de software deberían estar abiertas a la extensión, pero cerradas a la modificación."
|
||||
|
||||
### 2.1. Qué problema resuelve
|
||||
|
||||
OCP busca reducir la necesidad de modificar código estable cada vez que se incorpora una variante de comportamiento.
|
||||
|
||||
Esto resulta especialmente útil cuando:
|
||||
|
||||
- Existen múltiples implementaciones de una capacidad.
|
||||
- Se incorporan nuevas variantes con frecuencia.
|
||||
- Los componentes tienen contratos estables.
|
||||
- Modificar lógica existente supone un riesgo elevado.
|
||||
|
||||
### 2.2. Cómo aplicarlo
|
||||
|
||||
Cuando exista una necesidad real de extensión, diseña contratos que permitan incorporar nuevos comportamientos sin modificar innecesariamente los consumidores.
|
||||
|
||||
**Ejemplo: cálculo de gastos de envío**
|
||||
|
||||
```typescript id="e33kiw"
|
||||
// Implementación sencilla basada en condicionales.
|
||||
type ShippingType = 'standard' | 'express';
|
||||
|
||||
class ShippingCalculator {
|
||||
calculate(type: ShippingType, orderValue: number): number {
|
||||
if (type === 'standard') {
|
||||
return orderValue < 50 ? 5 : 0;
|
||||
}
|
||||
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Esta implementación no es necesariamente incorrecta.
|
||||
|
||||
Si los tipos de envío son pocos y estables, puede ser la solución más sencilla y mantenible.
|
||||
|
||||
Sin embargo, cuando las políticas de envío evolucionan independientemente, puede resultar conveniente introducir abstracciones.
|
||||
|
||||
### 2.3. Alternativa extensible
|
||||
|
||||
```typescript id="0kb5qt"
|
||||
interface ShippingMethod {
|
||||
calculateCost(orderValue: number): number;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript id="mky8hb"
|
||||
class StandardShipping implements ShippingMethod {
|
||||
calculateCost(orderValue: number): number {
|
||||
return orderValue < 50 ? 5 : 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript id="p9o7s1"
|
||||
class ExpressShipping implements ShippingMethod {
|
||||
calculateCost(_orderValue: number): number {
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Para incorporar un nuevo método:
|
||||
|
||||
```typescript id="bw5t7r"
|
||||
class SameDayShipping implements ShippingMethod {
|
||||
calculateCost(_orderValue: number): number {
|
||||
return 25;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Un consumidor puede utilizar el contrato común:
|
||||
|
||||
```typescript id="d8t4vk"
|
||||
function calculateShipping(
|
||||
method: ShippingMethod,
|
||||
orderValue: number,
|
||||
): number {
|
||||
return method.calculateCost(orderValue);
|
||||
}
|
||||
```
|
||||
|
||||
Este diseño permite incorporar nuevas políticas sin modificar el cálculo del consumidor.
|
||||
|
||||
No obstante, el mecanismo que selecciona o registra las implementaciones puede necesitar cambios según la arquitectura utilizada.
|
||||
|
||||
### 2.4. Aplicación pragmática
|
||||
|
||||
- Introduce puntos de extensión cuando exista una necesidad identificada.
|
||||
- Mantén estables los contratos que tengan consumidores.
|
||||
- Favorece composición y polimorfismo cuando reduzcan el acoplamiento.
|
||||
- Evita crear interfaces para comportamientos que probablemente no variarán.
|
||||
- No introduzcas extensibilidad hipotética.
|
||||
- Acepta modificaciones de código existente cuando sean la solución más clara.
|
||||
|
||||
### 2.5. OCP a nivel arquitectónico
|
||||
|
||||
A nivel de arquitectura, OCP favorece la existencia de módulos con responsabilidades claras y contratos estables.
|
||||
|
||||
Esto permite incorporar funcionalidades nuevas con un impacto controlado.
|
||||
|
||||
Sin embargo, **no significa que toda funcionalidad nueva deba implementarse exclusivamente añadiendo archivos o clases**.
|
||||
|
||||
Modificar código existente es legítimo cuando cambian sus requisitos o responsabilidades.
|
||||
|
||||
El objetivo es minimizar modificaciones innecesarias en componentes estables, no prohibirlas.
|
||||
|
||||
---
|
||||
|
||||
## 3. L — Principio de sustitución de Liskov (Liskov Substitution Principle, LSP)
|
||||
|
||||
> "Los subtipos deben poder sustituir a sus tipos base sin alterar la corrección del programa."
|
||||
|
||||
### 3.1. Qué problema resuelve
|
||||
|
||||
LSP evita jerarquías o implementaciones que incumplen las expectativas establecidas por un contrato común.
|
||||
|
||||
Cuando se vulnera este principio, suelen aparecer:
|
||||
|
||||
- Comprobaciones del tipo concreto en los consumidores.
|
||||
- Condicionales especiales para determinadas implementaciones.
|
||||
- Errores inesperados al sustituir componentes.
|
||||
- Contratos inconsistentes.
|
||||
- Dependencias del comportamiento interno de cada implementación.
|
||||
|
||||
### 3.2. El contrato es más que una firma
|
||||
|
||||
Dos implementaciones no son necesariamente sustituibles por tener los mismos métodos.
|
||||
|
||||
La sustitución requiere respetar el comportamiento esperado, incluyendo:
|
||||
|
||||
- Precondiciones.
|
||||
- Postcondiciones.
|
||||
- Invariantes.
|
||||
- Semántica de los resultados.
|
||||
- Comportamiento ante errores.
|
||||
- Garantías relevantes para los consumidores.
|
||||
|
||||
### 3.3. Ejemplo de incumplimiento
|
||||
|
||||
Supongamos que el contrato establece que un descuento nunca puede ser negativo.
|
||||
|
||||
```typescript id="j3zfk5"
|
||||
interface DiscountPolicy {
|
||||
getDiscount(orderValue: number): number;
|
||||
}
|
||||
```
|
||||
|
||||
Una implementación correcta:
|
||||
|
||||
```typescript id="tr7txy"
|
||||
class StandardDiscount implements DiscountPolicy {
|
||||
getDiscount(_orderValue: number): number {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Una implementación incorrecta:
|
||||
|
||||
```typescript id="q0laxd"
|
||||
class InvalidDiscount implements DiscountPolicy {
|
||||
getDiscount(_orderValue: number): number {
|
||||
return -5;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Aunque ambas cumplen la interfaz TypeScript, la segunda incumple el contrato semántico si este exige descuentos no negativos.
|
||||
|
||||
### 3.4. Protección del contrato
|
||||
|
||||
El contrato puede expresarse mediante documentación, tipos, validaciones y pruebas.
|
||||
|
||||
```typescript id="e1iu3o"
|
||||
class FixedDiscount implements DiscountPolicy {
|
||||
constructor(private readonly amount: number) {
|
||||
if (!Number.isFinite(amount) || amount < 0) {
|
||||
throw new InvalidDiscountError();
|
||||
}
|
||||
}
|
||||
|
||||
getDiscount(_orderValue: number): number {
|
||||
return this.amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La validación impide crear una política de descuento fijo con un importe negativo.
|
||||
|
||||
No obstante, validar únicamente esta clase no garantiza que todas las implementaciones de `DiscountPolicy` cumplan el contrato.
|
||||
|
||||
Para ello pueden utilizarse pruebas de contrato compartidas y validaciones en los límites apropiados.
|
||||
|
||||
### 3.5. Reglas de sustitución
|
||||
|
||||
Una implementación sustitutiva debe:
|
||||
|
||||
- Respetar las invariantes del contrato.
|
||||
- No exigir precondiciones más estrictas que las comprometidas por el tipo base.
|
||||
- No proporcionar postcondiciones más débiles.
|
||||
- Mantener el significado de los resultados.
|
||||
- Respetar las garantías de error relevantes.
|
||||
- No introducir efectos secundarios incompatibles con el contrato.
|
||||
|
||||
### 3.6. Ejemplo con repositorios
|
||||
|
||||
```typescript id="60hovx"
|
||||
interface UserRepository {
|
||||
findById(id: UserId): Promise<User | null>;
|
||||
|
||||
save(user: User): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
Las implementaciones:
|
||||
|
||||
```typescript id="kp45w8"
|
||||
class InMemoryUserRepository implements UserRepository {
|
||||
// Implementación en memoria.
|
||||
}
|
||||
```
|
||||
|
||||
```typescript id="q2v45i"
|
||||
class PostgresUserRepository implements UserRepository {
|
||||
// Implementación sobre PostgreSQL.
|
||||
}
|
||||
```
|
||||
|
||||
Pueden sustituirse cuando respetan el mismo contrato funcional.
|
||||
|
||||
Esto incluye:
|
||||
|
||||
- Significado de `null` cuando no existe el usuario.
|
||||
- Garantías de persistencia.
|
||||
- Restricciones de integridad.
|
||||
- Tratamiento de errores.
|
||||
- Comportamientos relevantes para los consumidores.
|
||||
|
||||
**Compartir una interfaz no garantiza automáticamente la sustituibilidad.**
|
||||
|
||||
---
|
||||
|
||||
## 4. I — Principio de segregación de interfaces (Interface Segregation Principle, ISP)
|
||||
|
||||
> "Los clientes no deberían verse obligados a depender de métodos que no utilizan."
|
||||
|
||||
### 4.1. Qué problema resuelve
|
||||
|
||||
ISP evita interfaces excesivamente amplias que obligan a los consumidores o implementaciones a conocer capacidades innecesarias.
|
||||
|
||||
Los síntomas habituales incluyen:
|
||||
|
||||
- Interfaces con numerosas operaciones independientes.
|
||||
- Implementaciones parciales.
|
||||
- Métodos vacíos.
|
||||
- Excepciones de funcionalidad no soportada.
|
||||
- Dependencias innecesarias.
|
||||
- Cambios en interfaces que afectan a consumidores no relacionados.
|
||||
|
||||
### 4.2. Ejemplo problemático
|
||||
|
||||
```typescript id="d9qvm1"
|
||||
interface WarehouseDevice {
|
||||
printLabel(orderId: string): void;
|
||||
scanBarcode(): string;
|
||||
packageItem(orderId: string): void;
|
||||
}
|
||||
```
|
||||
|
||||
Una impresora sencilla no debería verse obligada a implementar operaciones que no admite.
|
||||
|
||||
```typescript id="z83zrv"
|
||||
class BasicPrinter implements WarehouseDevice {
|
||||
printLabel(_orderId: string): void {
|
||||
// Imprimir etiqueta.
|
||||
}
|
||||
|
||||
scanBarcode(): string {
|
||||
throw new Error('Operación no soportada');
|
||||
}
|
||||
|
||||
packageItem(_orderId: string): void {
|
||||
throw new Error('Operación no soportada');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3. Alternativa con interfaces cohesionadas
|
||||
|
||||
```typescript id="80gu9m"
|
||||
interface LabelPrinter {
|
||||
printLabel(orderId: string): void;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript id="e4lqq1"
|
||||
interface BarcodeScanner {
|
||||
scanBarcode(): string;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript id="a113zj"
|
||||
interface ItemPackager {
|
||||
packageItem(orderId: string): void;
|
||||
}
|
||||
```
|
||||
|
||||
Una impresora implementa únicamente su capacidad real:
|
||||
|
||||
```typescript id="b9ksk8"
|
||||
class BasicPrinter implements LabelPrinter {
|
||||
printLabel(_orderId: string): void {
|
||||
// Imprimir etiqueta.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Los consumidores pueden depender de las capacidades que necesitan.
|
||||
|
||||
### 4.4. Aplicación pragmática
|
||||
|
||||
- Diseña interfaces alrededor de necesidades reales de los consumidores.
|
||||
- Agrupa operaciones que representen una responsabilidad coherente.
|
||||
- Divide interfaces cuando obliguen a depender de capacidades no utilizadas.
|
||||
- Evita crear una interfaz independiente para cada método sin una razón clara.
|
||||
- No fragmentes contratos cohesionados únicamente para reducir su tamaño.
|
||||
|
||||
### 4.5. Señales de revisión
|
||||
|
||||
Revisa una interfaz cuando:
|
||||
|
||||
- Varias implementaciones contienen métodos vacíos.
|
||||
- Algunas operaciones lanzan excepciones de no implementación de forma sistemática.
|
||||
- Los consumidores utilizan subconjuntos completamente independientes.
|
||||
- Los cambios afectan a componentes que no necesitan la funcionalidad modificada.
|
||||
|
||||
Sin embargo, un método opcional o una operación no soportada no demuestra por sí solo una vulneración de ISP.
|
||||
|
||||
Evalúa el contrato y el contexto antes de modificarlo.
|
||||
|
||||
**ISP trata de reducir dependencias innecesarias, no de maximizar el número de interfaces.**
|
||||
|
||||
---
|
||||
|
||||
## 5. D — Principio de inversión de dependencias (Dependency Inversion Principle, DIP)
|
||||
|
||||
> "Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones."
|
||||
|
||||
### 5.1. Qué problema resuelve
|
||||
|
||||
DIP busca evitar que las reglas de negocio queden acopladas innecesariamente a detalles técnicos.
|
||||
|
||||
Ejemplos:
|
||||
|
||||
- Bases de datos concretas.
|
||||
- Proveedores de correo electrónico.
|
||||
- APIs externas.
|
||||
- Sistemas de almacenamiento.
|
||||
- Frameworks.
|
||||
- Implementaciones de infraestructura.
|
||||
|
||||
Este acoplamiento puede dificultar las pruebas, la evolución o la sustitución de componentes.
|
||||
|
||||
### 5.2. Ejemplo de dependencia concreta
|
||||
|
||||
```typescript id="r5k4a2"
|
||||
class OrderService {
|
||||
private readonly emailService = new SendGridEmailService();
|
||||
|
||||
async confirmOrder(email: string): Promise<void> {
|
||||
await this.emailService.send(email, 'Pedido confirmado');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La clase conoce directamente al proveedor de correo electrónico.
|
||||
|
||||
Si cambia el proveedor, puede ser necesario modificar el servicio de aplicación.
|
||||
|
||||
### 5.3. Dependencia de una abstracción
|
||||
|
||||
Define un contrato que represente la capacidad necesaria.
|
||||
|
||||
```typescript id="k8p2ag"
|
||||
interface EmailService {
|
||||
send(to: string, message: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
El servicio depende del contrato:
|
||||
|
||||
```typescript id="5g7pvd"
|
||||
class OrderService {
|
||||
constructor(
|
||||
private readonly emailService: EmailService,
|
||||
) {}
|
||||
|
||||
async confirmOrder(email: string): Promise<void> {
|
||||
await this.emailService.send(email, 'Pedido confirmado');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Las implementaciones concretas pueden variar:
|
||||
|
||||
```typescript id="gcl3yt"
|
||||
const sendGridOrderService = new OrderService(
|
||||
new SendGridEmailService(),
|
||||
);
|
||||
```
|
||||
|
||||
```typescript id="k85qhq"
|
||||
const sesOrderService = new OrderService(
|
||||
new SesEmailService(),
|
||||
);
|
||||
```
|
||||
|
||||
En una prueba puede utilizarse una implementación controlada:
|
||||
|
||||
```typescript id="f4yb6z"
|
||||
const testOrderService = new OrderService(
|
||||
new FakeEmailService(),
|
||||
);
|
||||
```
|
||||
|
||||
El servicio de aplicación no necesita conocer los detalles internos de cada proveedor.
|
||||
|
||||
### 5.4. Inversión de dependencias e inyección de dependencias
|
||||
|
||||
Son conceptos relacionados, pero diferentes.
|
||||
|
||||
**Inversión de dependencias (DIP):**
|
||||
|
||||
Principio de diseño que establece cómo deben organizarse las dependencias entre políticas y detalles.
|
||||
|
||||
**Inyección de dependencias (DI):**
|
||||
|
||||
Técnica mediante la cual un componente recibe sus colaboradores desde el exterior.
|
||||
|
||||
Puede realizarse mediante:
|
||||
|
||||
- Constructores.
|
||||
- Parámetros.
|
||||
- Factorías.
|
||||
- Composición explícita.
|
||||
- Contenedores de dependencias.
|
||||
|
||||
**Aplicar DIP no exige utilizar un contenedor de inyección de dependencias.**
|
||||
|
||||
### 5.5. Evitar abstracciones innecesarias
|
||||
|
||||
No es obligatorio crear una interfaz para cada clase.
|
||||
|
||||
Una dependencia concreta puede resultar apropiada cuando:
|
||||
|
||||
- El componente es sencillo y estable.
|
||||
- No existe una necesidad real de sustitución.
|
||||
- No introduce acoplamiento arquitectónico problemático.
|
||||
- La abstracción adicional no aporta claridad ni capacidad de evolución.
|
||||
- Se trata de un objeto de valor, una utilidad pura u otro componente concreto cuyo contrato es suficientemente estable.
|
||||
|
||||
La dependencia de abstracciones debe justificarse por su utilidad, no por el cumplimiento mecánico de DIP.
|
||||
|
||||
### 5.6. Regla de dependencias arquitectónicas
|
||||
|
||||
En arquitecturas limpias, hexagonales o equivalentes, las dependencias del código fuente deben orientarse hacia las políticas de alto nivel.
|
||||
|
||||
Representación simplificada:
|
||||
|
||||
```text id="y99n58"
|
||||
Infraestructura → Aplicación → Dominio
|
||||
exterior medio interior
|
||||
```
|
||||
|
||||
Esta representación expresa la dirección deseada de las dependencias en una arquitectura organizada de esa forma.
|
||||
|
||||
En la práctica, la infraestructura puede implementar contratos definidos en la aplicación o el dominio.
|
||||
|
||||
Lo importante es que las reglas de negocio no dependan innecesariamente de los detalles de infraestructura.
|
||||
|
||||
### 5.7. Aplicación pragmática
|
||||
|
||||
- Mantén independientes las reglas de negocio respecto a detalles técnicos cuando resulte necesario.
|
||||
- Define contratos en los límites donde aporten desacoplamiento.
|
||||
- Utiliza inyección de dependencias cuando facilite la composición o las pruebas.
|
||||
- No introduzcas interfaces y adaptadores para todas las operaciones.
|
||||
- Evita contenedores complejos en aplicaciones que puedan resolverse mediante composición directa.
|
||||
- Respeta la arquitectura existente salvo que exista una razón técnica o funcional para modificarla.
|
||||
|
||||
---
|
||||
|
||||
## 6. Aplicación de SOLID a nivel arquitectónico
|
||||
|
||||
Los principios SOLID pueden orientar el diseño de módulos, subsistemas y límites arquitectónicos.
|
||||
|
||||
No obstante, su aplicación a nivel de arquitectura requiere interpretar correctamente cada principio.
|
||||
|
||||
| Principio | Aplicación arquitectónica |
|
||||
|---|---|
|
||||
| **SRP** | Módulos y subsistemas con responsabilidades cohesionadas y motivos de cambio relacionados. |
|
||||
| **OCP** | Contratos y puntos de extensión que permiten incorporar capacidades sin modificar innecesariamente componentes estables. |
|
||||
| **LSP** | Implementaciones sustituibles que respetan los contratos funcionales y no funcionales relevantes. |
|
||||
| **ISP** | Interfaces y contratos entre módulos adaptados a las necesidades reales de sus consumidores. |
|
||||
| **DIP** | Separación entre políticas de negocio y detalles técnicos mediante dependencias orientadas adecuadamente. |
|
||||
|
||||
### 6.1. SRP en módulos y subsistemas
|
||||
|
||||
Un módulo puede contener numerosas clases y operaciones siempre que representen una responsabilidad coherente.
|
||||
|
||||
No debe interpretarse que cada módulo o contexto delimitado (*bounded context*) tenga que realizar una única operación.
|
||||
|
||||
### 6.2. OCP en arquitectura
|
||||
|
||||
Favorece módulos estables que puedan ampliarse mediante nuevos componentes cuando exista una necesidad real de extensión.
|
||||
|
||||
No significa que las funcionalidades nuevas deban implementarse exclusivamente añadiendo módulos.
|
||||
|
||||
### 6.3. LSP en contratos distribuidos
|
||||
|
||||
En sistemas distribuidos, compartir un contrato técnico no garantiza sustituibilidad.
|
||||
|
||||
Deben considerarse también:
|
||||
|
||||
- Semántica de las operaciones.
|
||||
- Garantías de consistencia.
|
||||
- Tratamiento de errores.
|
||||
- Idempotencia cuando corresponda.
|
||||
- Compatibilidad de versiones.
|
||||
- Restricciones operativas relevantes.
|
||||
|
||||
Dos microservicios que exponen la misma API no son necesariamente intercambiables.
|
||||
|
||||
### 6.4. ISP entre módulos
|
||||
|
||||
Los contratos deben exponer las capacidades necesarias sin obligar a los consumidores a depender de funcionalidades ajenas.
|
||||
|
||||
Esto no exige crear un servicio o API independiente para cada operación.
|
||||
|
||||
### 6.5. DIP en arquitectura
|
||||
|
||||
Las reglas de negocio deben mantenerse desacopladas de detalles de infraestructura cuando esa separación facilite la evolución del sistema.
|
||||
|
||||
La arquitectura concreta debe adaptarse a los requisitos y complejidad reales.
|
||||
|
||||
**SOLID no obliga a utilizar Clean Architecture, arquitectura hexagonal o microservicios en cualquier proyecto.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Errores habituales al aplicar SOLID
|
||||
|
||||
### Aplicar SRP como "una clase, un método"
|
||||
|
||||
Una responsabilidad puede incluir numerosas operaciones relacionadas.
|
||||
|
||||
Dividirlas indiscriminadamente puede reducir la cohesión.
|
||||
|
||||
### Aplicar OCP mediante abstracciones especulativas
|
||||
|
||||
No crees extensibilidad para requisitos hipotéticos.
|
||||
|
||||
Introduce puntos de extensión cuando exista una necesidad identificada.
|
||||
|
||||
### Considerar LSP únicamente como compatibilidad de tipos
|
||||
|
||||
Compartir una interfaz no garantiza cumplir el mismo contrato semántico.
|
||||
|
||||
Comprueba invariantes, resultados y expectativas.
|
||||
|
||||
### Aplicar ISP creando demasiadas interfaces
|
||||
|
||||
Las interfaces deben representar capacidades coherentes.
|
||||
|
||||
No dividas contratos únicamente para reducir el número de métodos.
|
||||
|
||||
### Aplicar DIP mediante interfaces para todas las clases
|
||||
|
||||
DIP no consiste en prohibir dependencias concretas.
|
||||
|
||||
Consiste en evitar que las políticas importantes dependan innecesariamente de detalles técnicos.
|
||||
|
||||
### Introducir patrones sin necesidad
|
||||
|
||||
SOLID no exige utilizar automáticamente:
|
||||
|
||||
- Repositorios.
|
||||
- Factorías.
|
||||
- Estrategias.
|
||||
- Adaptadores.
|
||||
- Contenedores de dependencias.
|
||||
- Jerarquías de herencia.
|
||||
|
||||
Utiliza estos mecanismos cuando resuelvan un problema concreto.
|
||||
|
||||
### Refactorizar código estable sin justificación
|
||||
|
||||
No modifiques una implementación que funciona correctamente únicamente para ajustarla a una interpretación más rígida de SOLID.
|
||||
|
||||
Evalúa el beneficio esperado, el riesgo y el alcance de la tarea.
|
||||
|
||||
---
|
||||
|
||||
## 8. Referencia rápida
|
||||
|
||||
| Principio | Idea fundamental | Señal de revisión |
|
||||
|---|---|---|
|
||||
| **SRP** | Una responsabilidad coherente y motivos de cambio relacionados. | Clase que acumula responsabilidades independientes. |
|
||||
| **OCP** | Permitir extensiones justificadas sin modificar innecesariamente código estable. | Variantes frecuentes que obligan a cambiar múltiples componentes. |
|
||||
| **LSP** | Las implementaciones deben respetar el contrato común. | Comportamientos incompatibles o comprobaciones especiales por tipo. |
|
||||
| **ISP** | Los consumidores dependen únicamente de las capacidades necesarias. | Interfaces amplias con operaciones irrelevantes o implementaciones parciales. |
|
||||
| **DIP** | Evitar dependencias innecesarias entre políticas de alto nivel y detalles técnicos. | Reglas de negocio fuertemente acopladas a infraestructura concreta. |
|
||||
|
||||
---
|
||||
|
||||
## 9. Lista de comprobación SOLID
|
||||
|
||||
### Responsabilidad única (SRP)
|
||||
|
||||
- [ ] ¿Cada clase o módulo tiene una responsabilidad coherente?
|
||||
- [ ] ¿Existen motivos de cambio independientes que justifiquen una separación?
|
||||
- [ ] ¿He evitado fragmentar responsabilidades relacionadas?
|
||||
- [ ] ¿La organización facilita comprender y mantener el código?
|
||||
|
||||
### Abierto/cerrado (OCP)
|
||||
|
||||
- [ ] ¿Existe una necesidad real de extensión?
|
||||
- [ ] ¿Puedo incorporar variantes sin modificar innecesariamente código estable?
|
||||
- [ ] ¿Las abstracciones introducidas están justificadas?
|
||||
- [ ] ¿He evitado diseñar para requisitos hipotéticos?
|
||||
|
||||
### Sustitución de Liskov (LSP)
|
||||
|
||||
- [ ] ¿Las implementaciones respetan el contrato común?
|
||||
- [ ] ¿Se mantienen las invariantes?
|
||||
- [ ] ¿Las precondiciones y postcondiciones son compatibles?
|
||||
- [ ] ¿Los consumidores pueden utilizar otra implementación sin tratamientos especiales?
|
||||
- [ ] ¿Las diferencias de comportamiento están correctamente definidas?
|
||||
|
||||
### Segregación de interfaces (ISP)
|
||||
|
||||
- [ ] ¿Cada interfaz representa capacidades coherentes?
|
||||
- [ ] ¿Los consumidores dependen de operaciones que realmente necesitan?
|
||||
- [ ] ¿Existen implementaciones forzadas a soportar operaciones irrelevantes?
|
||||
- [ ] ¿He evitado fragmentar interfaces artificialmente?
|
||||
|
||||
### Inversión de dependencias (DIP)
|
||||
|
||||
- [ ] ¿Las reglas de negocio dependen innecesariamente de detalles técnicos?
|
||||
- [ ] ¿Las abstracciones están situadas en límites apropiados?
|
||||
- [ ] ¿La inyección de dependencias aporta ventajas concretas?
|
||||
- [ ] ¿He evitado interfaces y adaptadores innecesarios?
|
||||
- [ ] ¿Se respeta la arquitectura existente y sus contratos?
|
||||
|
||||
---
|
||||
|
||||
## Principio final
|
||||
|
||||
> "Los principios SOLID deben facilitar el cambio, reducir el acoplamiento y mejorar la comprensión del software, no convertirse en una obligación de introducir más clases e interfaces."
|
||||
|
||||
Aplica SOLID con criterio profesional, considerando las necesidades del dominio, el tamaño del sistema, sus requisitos de evolución y el coste de mantenimiento.
|
||||
|
||||
**Una solución sencilla, cohesionada y correctamente desacoplada es preferible a una arquitectura artificialmente compleja que únicamente aparenta cumplir SOLID.**
|
||||
567
.agents/skills/rdx-solid/referencias/tdd.md
Normal file
567
.agents/skills/rdx-solid/referencias/tdd.md
Normal file
@ -0,0 +1,567 @@
|
||||
# Desarrollo guiado por pruebas (Test-Driven Development)
|
||||
|
||||
El desarrollo guiado por pruebas (TDD) es una metodología que utiliza pruebas automatizadas para orientar la implementación y evolución del software.
|
||||
|
||||
El objetivo no es simplemente aumentar el número de pruebas, sino diseñar código verificable, expresar claramente los comportamientos esperados y reducir el riesgo de introducir regresiones.
|
||||
|
||||
Este documento desarrolla los principios definidos en el `SKILL.md` principal.
|
||||
|
||||
**TDD es la metodología preferente cuando resulta apropiada, pero no debe imponerse de forma mecánica si existe una alternativa de verificación más adecuada.**
|
||||
|
||||
## 1. El ciclo fundamental de TDD
|
||||
|
||||
```text
|
||||
ROJO → VERDE → REFACTORIZAR → ROJO → ...
|
||||
```
|
||||
|
||||
Cada iteración debe ser pequeña y centrarse en un comportamiento concreto.
|
||||
|
||||
### 1.1. Fase ROJO (RED)
|
||||
|
||||
Escribe una prueba que falle y describa el comportamiento que deseas implementar.
|
||||
|
||||
La prueba debe:
|
||||
|
||||
- Utilizar lenguaje del dominio, evitando detalles técnicos innecesarios.
|
||||
- Describir **QUÉ** debe ocurrir, no **CÓMO** debe implementarse.
|
||||
- Representar un ejemplo concreto y verificable.
|
||||
- Fallar por la ausencia del comportamiento esperado, no por errores accidentales de configuración.
|
||||
- Expresar claramente el resultado esperado.
|
||||
|
||||
```typescript
|
||||
// MAL: Descripción demasiado abstracta.
|
||||
it('can add numbers', () => {
|
||||
expect(add(2, 3)).toBe(5);
|
||||
});
|
||||
|
||||
// BIEN: Ejemplo concreto.
|
||||
it('returns 5 when adding 2 and 3', () => {
|
||||
expect(add(2, 3)).toBe(5);
|
||||
});
|
||||
```
|
||||
|
||||
Aunque las descripciones de pruebas son cadenas de texto, sigue las convenciones del proyecto. Si se consideran documentación del comportamiento, pueden redactarse en castellano.
|
||||
|
||||
**Comprueba que la prueba falla por el motivo esperado antes de continuar.**
|
||||
|
||||
Una prueba que falla por un error de importación, una dependencia ausente o una configuración incorrecta no demuestra necesariamente que el comportamiento esté pendiente de implementar.
|
||||
|
||||
### 1.2. Fase VERDE (GREEN)
|
||||
|
||||
Escribe la implementación más sencilla que permita superar la prueba.
|
||||
|
||||
Evita introducir funcionalidades, configuraciones o abstracciones que todavía no sean necesarias.
|
||||
|
||||
Existen dos estrategias habituales.
|
||||
|
||||
**Estrategia 1: Fake It (simular inicialmente el resultado)**
|
||||
|
||||
Consiste en utilizar una implementación provisional mínima para superar un ejemplo concreto.
|
||||
|
||||
```typescript
|
||||
function add(a: number, b: number): number {
|
||||
return 5;
|
||||
}
|
||||
```
|
||||
|
||||
Esta implementación únicamente satisface un ejemplo específico.
|
||||
|
||||
Puede utilizarse como paso temporal durante el aprendizaje de TDD o cuando se busca descubrir una generalización mediante pruebas adicionales.
|
||||
|
||||
**No debe conservarse como implementación final si el comportamiento requerido exige una solución general.**
|
||||
|
||||
**Estrategia 2: Obvious Implementation (implementación evidente)**
|
||||
|
||||
Cuando la solución es sencilla y conocida, implementa directamente el comportamiento.
|
||||
|
||||
```typescript
|
||||
function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
No es necesario utilizar *Fake It* si la implementación correcta resulta evidente.
|
||||
|
||||
### Criterios para elegir la estrategia
|
||||
|
||||
- Utiliza *Fake It* cuando necesites avanzar incrementalmente hacia una generalización.
|
||||
- Utiliza *Obvious Implementation* cuando el comportamiento sea sencillo y conocido.
|
||||
- Evita construir una arquitectura completa a partir de un único ejemplo.
|
||||
- No introduzcas casos hipotéticos sin una necesidad identificada.
|
||||
- Asegúrate de que el conjunto de pruebas comprueba el comportamiento realmente requerido.
|
||||
|
||||
**La simplicidad debe evaluarse respecto al comportamiento esperado, no únicamente respecto a la cantidad de código.**
|
||||
|
||||
### 1.3. Fase REFACTORIZAR (REFACTOR)
|
||||
|
||||
Una vez que las pruebas pasan, mejora el diseño sin modificar el comportamiento observable.
|
||||
|
||||
Revisa:
|
||||
|
||||
- Duplicación de conocimiento o reglas de negocio.
|
||||
- Métodos o clases con responsabilidades mezcladas.
|
||||
- Nombres poco expresivos.
|
||||
- Condiciones difíciles de comprender.
|
||||
- Dependencias innecesarias.
|
||||
- Abstracciones prematuras.
|
||||
- Estructuras que dificulten el mantenimiento.
|
||||
|
||||
La refactorización debe realizarse en incrementos pequeños, verificando que las pruebas continúan pasando.
|
||||
|
||||
**No refactorices únicamente para satisfacer métricas arbitrarias de líneas, atributos o número de métodos.**
|
||||
|
||||
El diseño puede evolucionar durante la refactorización, pero también requiere decisiones previas cuando sea necesario definir contratos, límites entre módulos o responsabilidades arquitectónicas.
|
||||
|
||||
---
|
||||
|
||||
## 2. Las tres leyes del TDD
|
||||
|
||||
Durante la aplicación estricta del ciclo TDD:
|
||||
|
||||
1. **No escribas código de producción** salvo para conseguir que una prueba que falla pase correctamente.
|
||||
2. **No escribas más código de prueba** del necesario para demostrar el fallo del comportamiento esperado.
|
||||
3. **No escribas más código de producción** del necesario para superar la prueba que falla.
|
||||
|
||||
Estas leyes favorecen los incrementos pequeños y evitan implementar funcionalidades no solicitadas.
|
||||
|
||||
No deben interpretarse como una prohibición universal de escribir código cuando TDD no sea la metodología apropiada.
|
||||
|
||||
### Excepciones justificadas
|
||||
|
||||
Puede utilizarse otro procedimiento cuando:
|
||||
|
||||
- Se modifique configuración, documentación o infraestructura sin comportamiento directamente comprobable mediante pruebas unitarias.
|
||||
- Se trabaje con código legado sin infraestructura de pruebas adecuada.
|
||||
- Se realicen prototipos exploratorios.
|
||||
- Sea necesario corregir urgentemente un problema y no resulte viable escribir primero una prueba.
|
||||
- Se efectúen modificaciones estructurales cubiertas por pruebas existentes.
|
||||
|
||||
En estos casos:
|
||||
|
||||
1. Identifica el comportamiento que debe mantenerse o modificarse.
|
||||
2. Selecciona el mecanismo de verificación más adecuado.
|
||||
3. Realiza cambios pequeños y controlados.
|
||||
4. Ejecuta las comprobaciones disponibles.
|
||||
5. Añade pruebas de regresión cuando resulte técnicamente viable.
|
||||
|
||||
**No utilizar TDD no significa trabajar sin verificación.**
|
||||
|
||||
---
|
||||
|
||||
## 3. La regla de tres
|
||||
|
||||
La regla de tres es una heurística para identificar oportunidades de abstracción.
|
||||
|
||||
Tradicionalmente propone esperar hasta observar varias repeticiones antes de extraer código común.
|
||||
|
||||
Su propósito es evitar crear abstracciones incorrectas a partir de similitudes superficiales.
|
||||
|
||||
```typescript
|
||||
// Primera aparición: implementar directamente.
|
||||
|
||||
// Segunda aparición: comprobar si existe conocimiento compartido.
|
||||
|
||||
// Tercera aparición: reconsiderar si conviene extraer una abstracción.
|
||||
```
|
||||
|
||||
### Aplicación recomendada
|
||||
|
||||
- No extraigas automáticamente código después de la tercera repetición.
|
||||
- No esperes obligatoriamente a tres repeticiones cuando ya exista una regla de negocio compartida.
|
||||
- Comprueba que las implementaciones representen el mismo concepto.
|
||||
- Evita abstraer código que únicamente se parece sintácticamente.
|
||||
- Valora el coste de mantenimiento de la duplicación frente al coste de la abstracción.
|
||||
|
||||
**La duplicación de conocimiento puede ser más peligrosa que la duplicación de instrucciones.**
|
||||
|
||||
Por ejemplo, dos implementaciones independientes de una misma regla fiscal pueden justificar una extracción inmediata.
|
||||
|
||||
En cambio, tres operaciones similares pertenecientes a dominios diferentes pueden mantenerse separadas.
|
||||
|
||||
> "Un poco de duplicación es mejor que una abstracción equivocada."
|
||||
|
||||
---
|
||||
|
||||
## 4. Triangulación
|
||||
|
||||
La triangulación consiste en utilizar varios ejemplos concretos para orientar la implementación hacia una solución general.
|
||||
|
||||
Cada prueba adicional restringe las implementaciones posibles y ayuda a descubrir reglas que satisfacen todos los casos.
|
||||
|
||||
### Ejemplo
|
||||
|
||||
Primera prueba:
|
||||
|
||||
```typescript
|
||||
it('returns 5 when adding 2 and 3', () => {
|
||||
expect(add(2, 3)).toBe(5);
|
||||
});
|
||||
```
|
||||
|
||||
Una implementación provisional podría devolver siempre `5`.
|
||||
|
||||
Segunda prueba:
|
||||
|
||||
```typescript
|
||||
it('returns 9 when adding 4 and 5', () => {
|
||||
expect(add(4, 5)).toBe(9);
|
||||
});
|
||||
```
|
||||
|
||||
Esta prueba demuestra que devolver una constante no satisface el comportamiento general.
|
||||
|
||||
La implementación debe evolucionar:
|
||||
|
||||
```typescript
|
||||
function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
### Criterios de triangulación
|
||||
|
||||
- Añade ejemplos que aporten información nueva.
|
||||
- Incluye casos límite cuando sean relevantes.
|
||||
- Evita pruebas redundantes que no aumenten la confianza.
|
||||
- No generes combinaciones arbitrarias sin relación con los requisitos.
|
||||
- Utiliza la triangulación para descubrir comportamientos, no para retrasar artificialmente una solución evidente.
|
||||
|
||||
La triangulación es especialmente útil cuando las reglas del dominio son complejas o todavía no están completamente definidas.
|
||||
|
||||
---
|
||||
|
||||
## 5. Prioridad de las transformaciones (Transformation Priority Premise)
|
||||
|
||||
La *Transformation Priority Premise* propone avanzar desde implementaciones simples hacia otras más complejas mediante transformaciones pequeñas.
|
||||
|
||||
Es una herramienta de aprendizaje y razonamiento, no un algoritmo obligatorio de implementación.
|
||||
|
||||
| Prioridad orientativa | Transformación |
|
||||
|---|---|
|
||||
| 1 | Estructura vacía → ausencia de valor (`nil`/`null`) |
|
||||
| 2 | Ausencia de valor → constante |
|
||||
| 3 | Constante → variable |
|
||||
| 4 | Comportamiento incondicional → condicional |
|
||||
| 5 | Valor escalar → colección |
|
||||
| 6 | Instrucción → recursión |
|
||||
| 7 | Valor → valor modificado |
|
||||
|
||||
### Criterios de aplicación
|
||||
|
||||
- Prefiere transformaciones pequeñas cuando reduzcan el riesgo.
|
||||
- No introduzcas complejidad antes de que resulte necesaria.
|
||||
- No fuerces una secuencia concreta de transformaciones.
|
||||
- Utiliza directamente una implementación evidente cuando sea más clara.
|
||||
- Evalúa la transformación según las características del lenguaje y del dominio.
|
||||
|
||||
**La prioridad no debe interpretarse como una clasificación universal de complejidad o calidad.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Patrón Preparar-Actuar-Verificar (Arrange-Act-Assert)
|
||||
|
||||
La referencia canónica sobre AAA y nomenclatura es [testing.md](testing.md), secciones 3 y 4. Aquí se conserva su aplicación al ciclo TDD y al diseño de la prueba en orden inverso.
|
||||
|
||||
El patrón AAA estructura las pruebas en tres fases:
|
||||
|
||||
1. **Preparar (Arrange):** establecer el estado y los datos necesarios.
|
||||
2. **Actuar (Act):** ejecutar el comportamiento que se desea comprobar.
|
||||
3. **Verificar (Assert):** comprobar el resultado esperado.
|
||||
|
||||
```typescript
|
||||
it('applies a 10 percent discount to the order total', () => {
|
||||
// Preparar: Crear el pedido y el descuento.
|
||||
const order = new Order();
|
||||
order.addItem({ price: 100 });
|
||||
|
||||
const discount = new PercentDiscount(10);
|
||||
|
||||
// Actuar: Calcular el importe final.
|
||||
const total = order.calculateTotal(discount);
|
||||
|
||||
// Verificar: Comprobar el resultado.
|
||||
expect(total).toBe(90);
|
||||
});
|
||||
```
|
||||
|
||||
El ejemplo presupone que el dominio utiliza importes numéricos. En un sistema con reglas monetarias específicas puede ser preferible utilizar objetos de valor como `Money`.
|
||||
|
||||
### Buenas prácticas
|
||||
|
||||
- Mantén claramente identificable el comportamiento probado.
|
||||
- Evita configuraciones extensas que oculten la intención.
|
||||
- Utiliza funciones auxiliares o factorías de pruebas cuando simplifiquen la preparación.
|
||||
- No introduzcas abstracciones de pruebas innecesarias.
|
||||
- Mantén las verificaciones relacionadas con el comportamiento esperado.
|
||||
|
||||
No es obligatorio escribir comentarios para separar las tres fases cuando la estructura resulte evidente.
|
||||
|
||||
---
|
||||
|
||||
## 7. Escribir pruebas en orden inverso
|
||||
|
||||
En ocasiones resulta útil diseñar una prueba comenzando por la verificación.
|
||||
|
||||
El procedimiento consiste en:
|
||||
|
||||
1. **ASSERT:** definir qué resultado debe comprobarse.
|
||||
2. **ACT:** identificar qué operación produce ese resultado.
|
||||
3. **ARRANGE:** preparar únicamente los datos y dependencias necesarios.
|
||||
|
||||
### Ejemplo
|
||||
|
||||
Primero, define la expectativa:
|
||||
|
||||
```typescript
|
||||
expect(total).toBe(90);
|
||||
```
|
||||
|
||||
Después, identifica la operación:
|
||||
|
||||
```typescript
|
||||
const total = order.calculateTotal(discount);
|
||||
```
|
||||
|
||||
Por último, prepara el escenario:
|
||||
|
||||
```typescript
|
||||
const order = new Order();
|
||||
order.addItem({ price: 100 });
|
||||
|
||||
const discount = new PercentDiscount(10);
|
||||
```
|
||||
|
||||
Esta técnica ayuda a evitar configuraciones innecesarias y favorece pruebas centradas en resultados observables.
|
||||
|
||||
No es obligatorio utilizarla cuando el comportamiento ya esté suficientemente claro.
|
||||
|
||||
---
|
||||
|
||||
## 8. Nomenclatura de las pruebas
|
||||
|
||||
Los nombres deben describir comportamientos concretos utilizando el lenguaje del dominio.
|
||||
|
||||
### Principios
|
||||
|
||||
- Describe el comportamiento esperado.
|
||||
- Utiliza ejemplos concretos cuando aporten claridad.
|
||||
- Evita referencias innecesarias a detalles internos.
|
||||
- Mantén una relación clara entre el nombre y la verificación realizada.
|
||||
- Utiliza nombres coherentes con las convenciones del proyecto.
|
||||
- Facilita identificar el fallo sin necesidad de inspeccionar toda la prueba.
|
||||
|
||||
```typescript
|
||||
// MAL: Se centra en un detalle interno.
|
||||
it('sets the data property to 1', () => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// BIEN: Describe un comportamiento observable.
|
||||
it('recognizes "mom" as a palindrome', () => {
|
||||
expect(isPalindrome('mom')).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
### Una responsabilidad de prueba por caso
|
||||
|
||||
Como orientación, cada prueba debe verificar un comportamiento concreto.
|
||||
|
||||
Esto no significa limitarla a una única instrucción `expect`.
|
||||
|
||||
Una misma operación puede requerir comprobar varios efectos relacionados.
|
||||
|
||||
```typescript
|
||||
it('creates an order with the expected initial state', () => {
|
||||
const order = createOrder();
|
||||
|
||||
expect(order.status).toBe('pending');
|
||||
expect(order.items).toHaveLength(0);
|
||||
expect(order.total).toBe(0);
|
||||
});
|
||||
```
|
||||
|
||||
Estas verificaciones pertenecen al mismo comportamiento inicial.
|
||||
|
||||
Evita agrupar escenarios independientes dentro de una única prueba extensa.
|
||||
|
||||
---
|
||||
|
||||
## 9. TDD clásico y Mockist
|
||||
|
||||
Existen diferentes estilos de TDD. Ninguno debe imponerse universalmente.
|
||||
|
||||
### 9.1. TDD clásico (Detroit/Chicago)
|
||||
|
||||
Se centra principalmente en los estados y resultados observables.
|
||||
|
||||
Características:
|
||||
|
||||
- Prefiere utilizar objetos reales cuando resulta práctico.
|
||||
- Evita simulaciones innecesarias.
|
||||
- Comprueba resultados y cambios de estado.
|
||||
- Permite que el diseño evolucione mediante el comportamiento.
|
||||
|
||||
Resulta especialmente apropiado para:
|
||||
|
||||
- Funciones puras.
|
||||
- Objetos de dominio.
|
||||
- Algoritmos.
|
||||
- Reglas de negocio.
|
||||
- Componentes que pueden probarse sin infraestructura externa.
|
||||
|
||||
El uso de objetos reales no convierte automáticamente una prueba en una prueba de integración.
|
||||
|
||||
### 9.2. TDD Mockist (London)
|
||||
|
||||
Se centra especialmente en las interacciones y colaboración entre componentes.
|
||||
|
||||
Características:
|
||||
|
||||
- Utiliza dobles de prueba para sustituir determinadas dependencias.
|
||||
- Permite verificar colaboraciones relevantes.
|
||||
- Facilita aislar componentes.
|
||||
- Puede favorecer diseños basados en interfaces y responsabilidades explícitas.
|
||||
|
||||
Resulta útil cuando:
|
||||
|
||||
- Existen dependencias externas.
|
||||
- Deben verificarse interacciones relevantes.
|
||||
- Es necesario controlar respuestas o errores de colaboradores.
|
||||
- La infraestructura dificulta ejecutar pruebas rápidas y deterministas.
|
||||
|
||||
### Criterios para utilizar mocks
|
||||
|
||||
- No simules dependencias únicamente por costumbre.
|
||||
- Evita simular objetos de dominio sencillos que puedan utilizarse directamente.
|
||||
- Utiliza dobles de prueba en los límites con bases de datos, servicios externos, sistemas de archivos u otras dependencias cuando resulte apropiado.
|
||||
- No hagas que las pruebas dependan excesivamente de llamadas internas irrelevantes.
|
||||
- Comprueba que las simulaciones respetan los contratos reales de sus dependencias.
|
||||
- Complementa las pruebas aisladas con pruebas de integración cuando sea necesario.
|
||||
|
||||
**Una prueba que verifica demasiados detalles de implementación puede fallar ante una refactorización correcta, aunque el comportamiento observable no haya cambiado.**
|
||||
|
||||
Selecciona el enfoque que proporcione mayor confianza con un coste razonable de mantenimiento.
|
||||
|
||||
---
|
||||
|
||||
## 10. TDD en código legado
|
||||
|
||||
El código legado puede carecer de pruebas, presentar dependencias difíciles de aislar o contener comportamientos implícitos.
|
||||
|
||||
En estos escenarios, no siempre resulta apropiado comenzar con una prueba unitaria convencional.
|
||||
|
||||
### Procedimiento recomendado
|
||||
|
||||
1. Identifica el comportamiento que debe modificarse.
|
||||
2. Analiza sus consumidores, contratos y dependencias.
|
||||
3. Comprueba qué pruebas y herramientas de verificación existen.
|
||||
4. Cuando sea viable, escribe pruebas de caracterización para documentar el comportamiento actual.
|
||||
5. Introduce los cambios de forma incremental.
|
||||
6. Añade pruebas de regresión para el comportamiento corregido o incorporado.
|
||||
7. Ejecuta las verificaciones disponibles.
|
||||
|
||||
### Pruebas de caracterización
|
||||
|
||||
Una prueba de caracterización describe el comportamiento actual de un componente, aunque este no sea necesariamente el comportamiento ideal.
|
||||
|
||||
Su objetivo es detectar cambios accidentales durante una refactorización.
|
||||
|
||||
No debe utilizarse para convertir automáticamente un error conocido en un requisito permanente.
|
||||
|
||||
Cuando el objetivo sea corregir un error, distingue entre:
|
||||
|
||||
- Comportamiento existente que debe preservarse.
|
||||
- Comportamiento defectuoso que debe corregirse.
|
||||
- Comportamiento nuevo que debe incorporarse.
|
||||
|
||||
---
|
||||
|
||||
## 11. Pruebas de regresión
|
||||
|
||||
Al corregir un defecto reproducible, es recomendable incorporar una prueba que detecte su reaparición.
|
||||
|
||||
### Procedimiento
|
||||
|
||||
1. Reproduce el defecto.
|
||||
2. Identifica el comportamiento esperado.
|
||||
3. Escribe una prueba que falle por el defecto, cuando sea viable.
|
||||
4. Corrige la implementación.
|
||||
5. Comprueba que la prueba pasa.
|
||||
6. Ejecuta las pruebas relacionadas para detectar regresiones.
|
||||
|
||||
Evita modificar o eliminar pruebas existentes únicamente para conseguir que la nueva implementación pase.
|
||||
|
||||
Si el comportamiento anterior era incorrecto y las pruebas lo reflejaban, actualízalas justificando el cambio de requisito o la corrección del defecto.
|
||||
|
||||
---
|
||||
|
||||
## 12. Errores habituales al aplicar TDD
|
||||
|
||||
| Error | Criterio de corrección |
|
||||
|---|---|
|
||||
| Escribir código antes de una prueba durante un ciclo TDD | Recuperar el ciclo Rojo-Verde-Refactorizar. |
|
||||
| Escribir demasiadas pruebas antes de implementar | Trabajar en incrementos de comportamiento pequeños. |
|
||||
| Implementar más funcionalidad de la necesaria | Limitarse a los requisitos y ejemplos relevantes. |
|
||||
| Omitir sistemáticamente la refactorización | Revisar el diseño una vez superadas las pruebas. |
|
||||
| Probar detalles internos | Priorizar comportamientos y contratos observables. |
|
||||
| Utilizar nombres abstractos | Describir escenarios y resultados concretos. |
|
||||
| Extraer abstracciones demasiado pronto | Esperar a comprender la responsabilidad compartida. |
|
||||
| Aplicar literalmente la regla de tres | Evaluar duplicación de conocimiento y coste de mantenimiento. |
|
||||
| Abusar de mocks | Utilizar objetos reales cuando resulten más sencillos y fiables. |
|
||||
| Mantener implementaciones artificiales de *Fake It* | Generalizar cuando las pruebas y requisitos lo exijan. |
|
||||
| Confiar únicamente en pruebas unitarias | Incorporar pruebas de integración o E2E cuando corresponda. |
|
||||
| Modificar pruebas para ocultar defectos | Investigar el fallo y preservar los requisitos correctos. |
|
||||
| Exigir TDD en cualquier circunstancia | Seleccionar la estrategia de verificación adecuada al contexto. |
|
||||
|
||||
---
|
||||
|
||||
## 13. Lista de comprobación de TDD
|
||||
|
||||
### Antes de implementar
|
||||
|
||||
- [ ] ¿Comprendo el comportamiento esperado?
|
||||
- [ ] ¿Existen criterios de aceptación claros?
|
||||
- [ ] ¿Qué nivel de prueba resulta más apropiado?
|
||||
- [ ] ¿Puedo escribir primero una prueba que reproduzca el comportamiento pendiente?
|
||||
- [ ] ¿Conozco el comportamiento existente que debo preservar?
|
||||
- [ ] ¿Puedo utilizar la infraestructura de pruebas del proyecto?
|
||||
|
||||
### Durante la implementación
|
||||
|
||||
- [ ] ¿La prueba falla inicialmente por el motivo esperado?
|
||||
- [ ] ¿La implementación satisface el comportamiento requerido?
|
||||
- [ ] ¿Estoy evitando funcionalidades no solicitadas?
|
||||
- [ ] ¿Las pruebas siguen siendo deterministas?
|
||||
- [ ] ¿Estoy probando comportamientos en lugar de detalles internos?
|
||||
- [ ] ¿He evitado mocks y abstracciones innecesarias?
|
||||
|
||||
### Durante la refactorización
|
||||
|
||||
- [ ] ¿Las pruebas continúan pasando?
|
||||
- [ ] ¿Se conserva el comportamiento observable?
|
||||
- [ ] ¿Las abstracciones introducidas representan conceptos reales?
|
||||
- [ ] ¿He mejorado la comprensión sin fragmentar artificialmente el código?
|
||||
- [ ] ¿He evitado cambios ajenos al alcance de la tarea?
|
||||
|
||||
### Al finalizar
|
||||
|
||||
- [ ] ¿Se satisfacen los criterios de aceptación?
|
||||
- [ ] ¿Se han ejecutado las pruebas relevantes?
|
||||
- [ ] ¿Se han incorporado pruebas de regresión cuando corresponde?
|
||||
- [ ] ¿Se han verificado los contratos y comportamientos afectados?
|
||||
- [ ] ¿He identificado las pruebas que no pudieron ejecutarse?
|
||||
- [ ] ¿Puedo comunicar con precisión qué se ha verificado y qué permanece pendiente?
|
||||
|
||||
---
|
||||
|
||||
## Principio final
|
||||
|
||||
> "TDD es una herramienta para descubrir y verificar comportamientos, no un objetivo independiente de la calidad del software."
|
||||
|
||||
Utiliza el ciclo Rojo-Verde-Refactorizar cuando permita desarrollar de forma segura, incremental y comprensible.
|
||||
|
||||
No conviertas las reglas metodológicas en restricciones que impidan adoptar una solución técnicamente más apropiada.
|
||||
|
||||
**El objetivo es producir software correcto, verificable, sencillo y mantenible, utilizando las pruebas como una herramienta para mejorar tanto el diseño como la confianza en el comportamiento del sistema.**
|
||||
1200
.agents/skills/rdx-solid/referencias/testing.md
Normal file
1200
.agents/skills/rdx-solid/referencias/testing.md
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user