Regla 0x2015h: No dupliques lógica: extraé una función
Funciones, contratos y modularizacion (0x20XX)
0x2015h: No dupliques lógica: extraé una función¶
Enunciado normativo¶
Si el mismo bloque de lógica (aunque sea con identifiers distintos) aparece dos o más veces, DEBE extraerse a una función con nombre propio y ser invocado desde cada lugar. NO DEBE copiarse y pegarse.
¿Por qué existe esta regla?¶
El problema¶
El código duplicado es una promesa de inconsistencia futura. Cuando el requisito cambie, habrá que localizar todas las copias y modificarlas igual; es inevitable olvidar alguna. Y el error que se corrige en una copia sobrevive en las otras.
Además, la lógica duplicada no tiene nombre, así que nadie puede reutilizarla ni probarla por separado. Al extraerla, gana un nombre que documenta su intención y un punto de prueba único.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Bug silencioso | Una copia se corrige y las otras no. |
| Mantenibilidad | Cada cambio exige buscar y reemplazar en varios lugares. |
| Testing | Hay que probar cada copia por separado. |
| Tamaño | La función que contiene las copias crece y se vuelve ilegible. |
Fundamento en la cátedra¶
Es el principio DRY (Don’t Repeat Yourself) aplicado a la escala del curso. Se apoya en 0x2008h: Los ejercicios deben ser resueltos mediante funciones, que ya exige resolver mediante funciones, y en 0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única), que pide una responsabilidad por función.
Alcance y excepciones¶
Dos bloques idénticos son siempre candidatos. Dos bloques parecidos pero con evolución divergente conocida pueden justificar no unificarlos, para no forzar una abstracción prematura. En el curso, ante la duda, extraer.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Validación copiada¶
if (edad >= 0 && edad <= 120) {
/* ... */
}
/* ... cincuenta líneas después ... */
if (edad2 >= 0 && edad2 <= 120) {
/* ... */
}
/* ... y en otra función ... */
if (otra_edad < 0 || otra_edad > 120) {
/* error */
}Por qué falla: la misma regla de negocio (rango válido de edad) aparece tres veces; si mañana el máximo cambia a 110, es casi seguro que una copia quede desactualizada.
❌ Contraejemplo 2 — Cálculo repetido¶
double area1 = 3.14159 * r1 * r1;
double longitud1 = 2 * 3.14159 * r1;
/* ... */
double area2 = 3.14159 * r2 * r2;
double longitud2 = 2 * 3.14159 * r2;Por qué falla: la constante y la fórmula se repiten; un cambio de precisión o de fórmula obliga a editar todas.
✅ Ejemplo conforme 1 — Función con nombre¶
static bool edad_valida(int edad)
{
return edad >= 0 && edad <= 120;
}if (edad_valida(edad)) {
/* ... */
}La regla vive en un único lugar, tiene nombre y se puede probar con assert.
✅ Ejemplo conforme 2 — Funciones de dominio¶
static double area_circulo(double radio)
{
return PI * radio * radio;
}
static double longitud_circulo(double radio)
{
return 2.0 * PI * radio;
}La constante PI se define una vez (0x0112h: Usá constantes simbólicas para todo literal con significado) y las fórmulas quedan
disponibles para todo el programa.
⚠️ Casos límite¶
Duplicación de una línea trivial: extraer una función para sumar dos números puede ser excesivo si no aporta nombre de dominio.
Similitud superficial: dos bloques que hoy coinciden pero modelan conceptos distintos (edad de una persona vs. duración de un proceso) no deben unificarse sólo por parecido.
Parámetros: al extraer, evite caer en funciones “navaja suiza” con parámetros bandera (0x7007h: Evitá los parámetros bandera de tipo bool).
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
weyl / dredd | detección de plagio / similitud | Bloques internos repetidos en el mismo archivo. |
giger | análisis de call graph | Funciones grandes con sub-bloques idénticos. |
| Revisión manual | — | Buscar el mismo if o la misma fórmula dos veces. |
Checklist de autocontrol¶
¿Escribí el mismo bloque más de una vez?
¿Ese bloque tiene un nombre de dominio posible?
¿Extraje una función y la llamé desde todos los lugares?
¿La lógica extraída se puede probar por separado?
Reglas relacionadas¶
0x2008h: Los ejercicios deben ser resueltos mediante funciones — resolver mediante funciones.
0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única) — responsabilidad única.
0x0112h: Usá constantes simbólicas para todo literal con significado — constantes simbólicas en lugar de literales repetidos.
0x7007h: Evitá los parámetros bandera de tipo bool — evitar parámetros bandera al generalizar.
Antipatrón: Ramas idénticas duplicadas en bifurcación if-else¶
Síntoma en el código del estudiante¶
Ambas ramas de un if / else contienen exactamente la misma sentencia:
if (x > 0)
{
total += x;
}
else
{
total += x;
}Diagnóstico¶
Mecanismo del defecto¶
Si las dos ramas tienen el mismo cuerpo, la condición no discrimina: el resultado es idéntico sin importar su valor. La estructura de control queda redundante. El caso típico es un error de copiado al preparar la rama alterna, o el residuo de una bifurcación que perdió sentido tras un refactor.
Consecuencia observable¶
El programa funciona, pero la intención no: el lector busca en vano la diferencia entre las ramas y sospecha de un bug. Si una de las ramas debía hacer otra cosa (por ejemplo, restar en lugar de sumar), el defecto es un bug lógico silencioso que ninguna herramienta señala por defecto.
Fundamento en el estándar C11¶
ISO/IEC 9899:2011 §6.8.4.1 permite cualquier par de sentencias en las ramas del
if; no es un error sintáctico. La cátedra lo trata como code smell y pide
eliminar la condición o corregir la rama divergente. Se relaciona con
0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única) (responsabilidad única) y con 0x1004h: Las condiciones complejas deben simplificarse o comentarse (condiciones con
intención).
Corrección idiomática¶
❌ Código con el antipatrón¶
if (x > 0)
{
total += x;
}
else
{
total += x;
}✅ Código refactorizado¶
total += x;Si la rama alterna debía hacer otra cosa, se corrige:
if (x > 0)
{
total += x;
}
else
{
total -= x;
}La condición recupera su propósito: cada rama hace algo distinto y el if deja
de ser decorativo.
Errores típicos al compilar o ejecutar¶
No hay advertencia del compilador por defecto. Con -Wall y optimización,
el compilador puede detectar ramas idénticas y colapsarlas:
warning: this 'if' clause does not guard... [-Wmisleading-indentation]
solo en variantes específicas; no es una detección garantizada.
El bug es lógico: si una rama debía restar, el total nunca decrece.Checklist de verificación¶
¿Las dos ramas del
ifhacen exactamente lo mismo?¿La condición aporta alguna diferencia de comportamiento?
¿Una de las ramas tenía un error de copiado?
¿Puedo eliminar el
ifsin cambiar el resultado?
Reglas relacionadas¶
0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única) — responsabilidad única; ramas idénticas suelen sobrar.
0x1004h: Las condiciones complejas deben simplificarse o comentarse — condiciones que expresan una intención clara.
0x1014h: Detector de operadores de incremento o decremento múltiples en una misma expresión — regla asociada a este antipatrón.