842 lines
28 KiB
Markdown
842 lines
28 KiB
Markdown
# 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.** |