metis-desk/.agents/skills/rdx-solid/referencias/code-smells.md

925 lines
27 KiB
Markdown
Raw Normal View History

# 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.**