925 lines
27 KiB
Markdown
925 lines
27 KiB
Markdown
|
|
# 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.**
|