Introducción¶
La extracción de funciones es una de las refactorizaciones más poderosas y fundamentales en programación. Consiste en identificar fragmentos de código que realizan una tarea específica y encapsularlos en una función independiente con un nombre descriptivo.
Esta técnica no solo mejora la legibilidad del código, sino que también promueve la reutilización, facilita el testing, y ayuda a mantener funciones con un nivel de abstracción consistente.
En el marco de la cátedra, esta es la técnica que permite que los ejercicios se
resuelvan descomponiéndolos en funciones y no dentro de main (0x2008h: Los ejercicios deben ser resueltos mediante funciones).
Reglas de estilo que resuelve¶
La extracción de funciones no es una preferencia estética: es la herramienta que corrige, de forma verificable, los problemas que modelan las reglas de estilo de la materia. Cada fragmento extraído ataca un síntoma concreto y deja el código alineado con la regla correspondiente. La tabla siguiente vincula cada regla con el problema que modela y la manera en que esta refactorización lo resuelve.
| Regla | Problema que modela | Cómo lo resuelve esta refactorización |
|---|---|---|
| 0x2014h: Cada función debe caber en una sola idea y en 25 líneas | Funciones que crecen hasta no caber en una sola idea ni en 25 líneas. | Extraer cada bloque cohesivo en una función corta mantiene el cuerpo por debajo del umbral. |
| 0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única) | Una función que acumula validación, cálculo y presentación. | Cada nueva función recibe exactamente una responsabilidad y un nombre que la declara. |
| 0x2016h: Escribí el contrato de la función antes de implementarla | Implementar sin haber decidido antes precondiciones y postcondiciones. | Al definir la firma se redacta el contrato —qué recibe, qué garantiza— antes del cuerpo. |
| 0x200Ah: Modularización: una función no debe exceder 4 parámetros de entrada | Firmas con demasiados parámetros sueltos que el llamador confunde. | Los datos que viajan juntos se empaquetan en un struct y se pasan mediante un único puntero. |
| 0x1015h: Limitá la profundidad de anidación a tres niveles | Anidación de cuatro o más niveles que sepulta el camino feliz. | Cada nivel profundo se convierte en una función invocada desde el nivel superior. |
| 0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda | Validaciones enterradas dentro del flujo principal. | Las validaciones extraídas se invocan como cláusulas de guarda al inicio. |
| 0x2003h: Todas las funciones deben incluir documentación completa y estructurada | Funciones sin propósito, precondición ni postcondición documentados. | Cada función extraída se documenta con su bloque estructurado (@brief, @param, @pre, @post, @returns). |
| 0x2008h: Los ejercicios deben ser resueltos mediante funciones | Lógica escrita en un único bloque monolítico dentro de main. | El ejercicio se descompone en funciones y main queda como orquestador. |
| 0x0017h: Agrupá sentencias relacionadas y separá bloques lógicos | Muros de sentencias donde no se distinguen las etapas. | Los párrafos lógicos que sobreviven a la extracción quedan separados por líneas en blanco. |
| 0x7007h: Evitá los parámetros bandera de tipo bool | Parámetros bandera bool que eligen entre dos comportamientos. | Los dos comportamientos se separan en dos funciones con nombres explícitos. |
¿Cuándo Extraer una Función?¶
Señales de que Necesitás Extraer¶
Código duplicado: El mismo fragmento aparece en múltiples lugares
Función larga: Más de 30-50 líneas (depende del contexto) (0x2014h: Cada función debe caber en una sola idea y en 25 líneas)
Niveles de abstracción mezclados: Lógica de alto nivel con detalles de implementación
Comentario explicativo: Si necesitás un comentario para explicar un bloque, ese bloque debería ser una función
Dificultad para nombrar: Si no podés describir qué hace la función en una frase corta
Anidamiento profundo: Más de 2-3 niveles de indentación (0x1015h: Limitá la profundidad de anidación a tres niveles)
Ejemplo: Comentario que Señala Necesidad de Extracción¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22void procesar_pedido(pedido_t *pedido) { // Validar datos del cliente if (pedido->cliente.nombre == NULL || strlen(pedido->cliente.nombre) == 0) { return; } if (pedido->cliente.email == NULL || !strchr(pedido->cliente.email, '@')) { return; } // Calcular total con impuestos double subtotal = 0; for (int i = 0; i < pedido->num_items; i++) { subtotal += pedido->items[i].precio * pedido->items[i].cantidad; } double impuestos = subtotal * 0.21; double total = subtotal + impuestos; // Guardar en base de datos // ... }
Los comentarios son indicadores claros: cada bloque debería ser una función.
Proceso de Extracción¶
Paso 1: Identificar el Fragmento¶
Seleccionar el bloque de código que realiza una tarea cohesiva.
Paso 2: Analizar Dependencias¶
Identificar:
Variables locales usadas: Se convertirán en parámetros
Variables modificadas: Se retornarán o pasarán por referencia
Variables declaradas internamente: Permanecen en la nueva función
Paso 3: Elegir Nombre Descriptivo¶
El nombre debe describir qué hace, no cómo lo hace:
validar_cliente()✓verificar_nombre_y_email()✗ (demasiado específico)proceso1()✗ (no descriptivo)
Paso 4: Determinar Firma¶
Decidir parámetros y tipo de retorno basándose en el análisis de dependencias. Es el momento de escribir el contrato de la función —qué recibe, qué garantiza, qué devuelve y qué casos no admite— antes de tocar su cuerpo (0x2016h: Escribí el contrato de la función antes de implementarla).
Paso 5: Extraer y Reemplazar¶
Crear la nueva función y reemplazar el código original con una llamada.
Paso 6: Probar¶
Verificar que el comportamiento no cambió.
Casos Prácticos¶
Caso 1: Extracción Básica - Validación¶
Código Original:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22int procesar_usuario(const char *nombre, const char *email, int edad) { // Validación embebida en la función principal if (nombre == NULL || strlen(nombre) < 3) { printf("Nombre inválido\n"); return -1; } if (email == NULL || !strchr(email, '@')) { printf("Email inválido\n"); return -1; } if (edad < 18 || edad > 120) { printf("Edad inválida\n"); return -1; } // Lógica principal printf("Usuario %s registrado\n", nombre); return 0; }
Código Refactorizado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39bool validar_nombre(const char *nombre) { if (nombre == NULL || strlen(nombre) < 3) { printf("Nombre inválido\n"); return false; } return true; } bool validar_email(const char *email) { if (email == NULL || !strchr(email, '@')) { printf("Email inválido\n"); return false; } return true; } bool validar_edad(int edad) { if (edad < 18 || edad > 120) { printf("Edad inválida\n"); return false; } return true; } int procesar_usuario(const char *nombre, const char *email, int edad) { if (!validar_nombre(nombre)) return -1; if (!validar_email(email)) return -1; if (!validar_edad(edad)) return -1; // Lógica principal ahora es clara printf("Usuario %s registrado\n", nombre); return 0; }
Beneficios:
Funciones reutilizables
Testing independiente de cada validación
procesar_usuarioahora es más legibleCada función tiene una única responsabilidad (0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única))
Caso 2: Extracción con Múltiples Valores de Retorno¶
Código Original:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20void analizar_ventas(double ventas[], int n) { // Calcular estadísticas double total = 0; double maximo = ventas[0]; double minimo = ventas[0]; for (int i = 0; i < n; i++) { total += ventas[i]; if (ventas[i] > maximo) maximo = ventas[i]; if (ventas[i] < minimo) minimo = ventas[i]; } double promedio = total / n; printf("Total: %.2f\n", total); printf("Promedio: %.2f\n", promedio); printf("Máximo: %.2f\n", maximo); printf("Mínimo: %.2f\n", minimo); }
Código Refactorizado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37typedef struct { double total; double promedio; double maximo; double minimo; } estadisticas_t; estadisticas_t calcular_estadisticas(const double ventas[], int n) { estadisticas_t stats = {0}; if (n == 0) return stats; stats.maximo = ventas[0]; stats.minimo = ventas[0]; for (int i = 0; i < n; i++) { stats.total += ventas[i]; if (ventas[i] > stats.maximo) stats.maximo = ventas[i]; if (ventas[i] < stats.minimo) stats.minimo = ventas[i]; } stats.promedio = stats.total / n; return stats; } void mostrar_estadisticas(estadisticas_t stats) { printf("Total: %.2f\n", stats.total); printf("Promedio: %.2f\n", stats.promedio); printf("Máximo: %.2f\n", stats.maximo); printf("Mínimo: %.2f\n", stats.minimo); } void analizar_ventas(const double ventas[], int n) { estadisticas_t stats = calcular_estadisticas(ventas, n); mostrar_estadisticas(stats); }
Beneficios:
Separación de cálculo y presentación
Función de cálculo es testeable y reutilizable
Diferentes formatos de salida sin cambiar lógica
Caso 3: Extracción de Lazo Complejo¶
Código Original:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39void procesar_archivo(const char *ruta) { FILE *f = fopen(ruta, "r"); char linea[256]; int lineas_procesadas = 0; int lineas_con_error = 0; while (fgets(linea, sizeof(linea), f)) { // Limpiar salto de línea linea[strcspn(linea, "\n")] = '\0'; // Saltar líneas vacías if (strlen(linea) == 0) continue; // Saltar comentarios if (linea[0] == '#') continue; // Parsear línea char *separador = strchr(linea, '='); if (separador == NULL) { lineas_con_error++; continue; } *separador = '\0'; char *clave = linea; char *valor = separador + 1; // Trim whitespace while (*clave == ' ') clave++; while (*valor == ' ') valor++; // Procesar par clave-valor printf("%s -> %s\n", clave, valor); lineas_procesadas++; } fclose(f); printf("Procesadas: %d, Errores: %d\n", lineas_procesadas, lineas_con_error); }
Código Refactorizado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58bool es_linea_valida(const char *linea) { return strlen(linea) > 0 && linea[0] != '#'; } bool parsear_par_clave_valor(char *linea, char **clave, char **valor) { char *separador = strchr(linea, '='); if (separador == NULL) { return false; } *separador = '\0'; *clave = linea; *valor = separador + 1; // Trim whitespace while (**clave == ' ') (*clave)++; while (**valor == ' ') (*valor)++; return true; } void procesar_linea_config(char *linea, int *procesadas, int *errores) { // Limpiar salto de línea linea[strcspn(linea, "\n")] = '\0'; if (!es_linea_valida(linea)) { return; } char *clave; char *valor; if (!parsear_par_clave_valor(linea, &clave, &valor)) { (*errores)++; return; } printf("%s -> %s\n", clave, valor); (*procesadas)++; } void procesar_archivo(const char *ruta) { FILE *f = fopen(ruta, "r"); if (f == NULL) { printf("Error abriendo archivo\n"); return; } char linea[256]; int lineas_procesadas = 0; int lineas_con_error = 0; while (fgets(linea, sizeof(linea), f)) { procesar_linea_config(linea, &lineas_procesadas, &lineas_con_error); } fclose(f); printf("Procesadas: %d, Errores: %d\n", lineas_procesadas, lineas_con_error); }
Beneficios:
Cada función tiene una responsabilidad clara
Fácil agregar nuevas validaciones
Testing unitario de parseo sin I/O
El error de
fopense resuelve con una cláusula de guarda y retorno anticipado (0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda)Lazo principal ahora es simple y legible
Caso 4: Extracción de Cálculo Complejo¶
Código Original:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42double calcular_precio_final(double precio_base, int cantidad, bool es_mayorista, const char *codigo_postal) { // Descuento por cantidad double descuento_cantidad = 0; if (cantidad >= 100) { descuento_cantidad = 0.20; } else if (cantidad >= 50) { descuento_cantidad = 0.15; } else if (cantidad >= 10) { descuento_cantidad = 0.10; } // Descuento mayorista double descuento_mayorista = es_mayorista ? 0.05 : 0; // Descuento total (no acumulativo lineal) double descuento_total = descuento_cantidad + descuento_mayorista; if (descuento_total > 0.25) descuento_total = 0.25; double subtotal = precio_base * cantidad * (1 - descuento_total); // Impuesto según región double impuesto = 0.21; // IVA estándar if (codigo_postal[0] == '9') { // Patagonia impuesto = 0.10; } // Cargo por envío double envio = 0; if (cantidad < 5) { envio = 500; } else if (cantidad < 20) { envio = 300; } return subtotal * (1 + impuesto) + envio; }
Código Refactorizado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50double calcular_descuento_por_cantidad(int cantidad) { if (cantidad >= 100) return 0.20; if (cantidad >= 50) return 0.15; if (cantidad >= 10) return 0.10; return 0.0; } double calcular_descuento_mayorista(bool es_mayorista) { return es_mayorista ? 0.05 : 0.0; } double calcular_descuento_total(int cantidad, bool es_mayorista) { const double DESCUENTO_MAXIMO = 0.25; double descuento = calcular_descuento_por_cantidad(cantidad) + calcular_descuento_mayorista(es_mayorista); return (descuento > DESCUENTO_MAXIMO) ? DESCUENTO_MAXIMO : descuento; } double calcular_impuesto_regional(const char *codigo_postal) { const double IVA_ESTANDAR = 0.21; const double IVA_PATAGONIA = 0.10; // Patagonia tiene código postal que empieza con 9 if (codigo_postal[0] == '9') { return IVA_PATAGONIA; } return IVA_ESTANDAR; } double calcular_cargo_envio(int cantidad) { if (cantidad < 5) return 500.0; if (cantidad < 20) return 300.0; return 0.0; } double calcular_precio_final(double precio_base, int cantidad, bool es_mayorista, const char *codigo_postal) { double descuento = calcular_descuento_total(cantidad, es_mayorista); double subtotal = precio_base * cantidad * (1 - descuento); double impuesto = calcular_impuesto_regional(codigo_postal); double total_con_impuesto = subtotal * (1 + impuesto); double envio = calcular_cargo_envio(cantidad); return total_con_impuesto + envio; }
Beneficios:
Cada regla de negocio está aislada
Fácil modificar política de descuentos
Testing unitario de cada componente
Función principal muestra el flujo claramente
Caso 5: Ejemplo Integrador — Varias Reglas a la Vez¶
Este caso reúne los síntomas anteriores en una sola función. El código ❌
acumula cuatro niveles de anidación, una bandera bool, cálculo y presentación
mezclados y ningún contrato documentado. La versión ✅ reparte la lógica en
funciones pequeñas con responsabilidad única y contrato explícito.
Código Original (❌):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44typedef enum { ESTADO_PENDIENTE, ESTADO_ENVIADO } estado_t; typedef struct { const char *cliente; double precio; int cantidad; estado_t estado; } item_t; double procesar_pedido(item_t items[], int n, bool aplicar_iva) { double total = 0.0; for (int i = 0; i < n; i++) { if (items[i].cantidad > 0) { if (items[i].precio > 0) { if (items[i].estado == ESTADO_PENDIENTE) { if (items[i].cantidad >= 10) { total += items[i].precio * items[i].cantidad * 0.80; } else { total += items[i].precio * items[i].cantidad; } } } } } if (aplicar_iva) { total *= 1.21; } printf("Total: %.2f\n", total); return total; }
Código Refactorizado (✅):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78/** @brief Indica si un ítem participa del total. * @param item Ítem a evaluar. * @pre item no es NULL. * @post Devuelve true solo si cantidad y precio son positivos y el estado * es pendiente. * @returns true si el ítem es facturable. */ static bool item_facturable(const item_t *item) { return item != NULL && item->cantidad > 0 && item->precio > 0 && item->estado == ESTADO_PENDIENTE; } /** @brief Aplica el descuento por volumen. * @param subtotal Importe bruto del ítem. * @param cantidad Unidades vendidas. * @pre subtotal es mayor o igual que cero. * @post Devuelve el importe con el descuento por volumen. * @returns Importe neto del ítem. */ static double descontar_por_volumen(double subtotal, int cantidad) { if (cantidad >= 10) { return subtotal * 0.80; } return subtotal; } /** @brief Suma los ítems facturables sin impuestos. * @param items Arreglo de ítems. * @param n Cantidad de ítems. * @pre items tiene al menos n elementos. * @post Devuelve la suma neta de los ítems facturables. * @returns Total sin impuestos. */ static double total_sin_impuestos(const item_t *items, int n) { double total = 0.0; for (int i = 0; i < n; i++) { if (!item_facturable(&items[i])) { continue; } double subtotal = items[i].precio * items[i].cantidad; total += descontar_por_volumen(subtotal, items[i].cantidad); } return total; } /** @brief Agrega el IVA a un total neto. * @param neto Total sin impuestos. * @pre neto es mayor o igual que cero. * @post Devuelve neto multiplicado por 1.21. * @returns Total con IVA. */ static double con_iva(double neto) { return neto * 1.21; } int main(void) { item_t items[] = { {"Ana", 1000.0, 12, ESTADO_PENDIENTE}, {"Beto", 500.0, 3, ESTADO_ENVIADO}, }; int n = (int)(sizeof(items) / sizeof(items[0])); double total = con_iva(total_sin_impuestos(items, n)); printf("Total: %.2f\n", total); return 0; }
Reglas que resuelve este ejemplo:
Cada función responde a una sola idea y entra en pocas líneas (0x2014h: Cada función debe caber en una sola idea y en 25 líneas, 0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única)).
item_facturablevalida la precondición y el lazo descarta concontinueen lugar de anidar cuatroif(0x1015h: Limitá la profundidad de anidación a tres niveles, 0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda).La bandera
bool aplicar_ivase reemplaza porcon_iva(), que declara su comportamiento en el nombre (0x7007h: Evitá los parámetros bandera de tipo bool).El contrato de cada función se escribe con
@brief,@param,@pre,@posty@returnsantes del cuerpo (0x2003h: Todas las funciones deben incluir documentación completa y estructurada, 0x2016h: Escribí el contrato de la función antes de implementarla).Las etapas de cálculo y de presentación se separan con líneas en blanco (0x0017h: Agrupá sentencias relacionadas y separá bloques lógicos).
mainqueda como orquestador que encadena funciones (0x2008h: Los ejercicios deben ser resueltos mediante funciones).
Técnicas Avanzadas¶
Composición de Funciones¶
Construir funciones complejas componiendo funciones simples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33// Funciones atómicas bool es_letra(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } bool es_digito(char c) { return c >= '0' && c <= '9'; } bool es_espacio(char c) { return c == ' ' || c == '\t' || c == '\n'; } // Función compuesta bool es_alfanumerico(char c) { return es_letra(c) || es_digito(c); } bool es_identificador_valido(const char *str) { if (str == NULL || strlen(str) == 0) return false; if (!es_letra(str[0]) && str[0] != '_') return false; for (size_t i = 1; str[i] != '\0'; i++) { if (!es_alfanumerico(str[i]) && str[i] != '_') { return false; } } return true; }
Extracción con Parámetros de Configuración¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18typedef struct { double descuento_mayorista; double impuesto_estandar; double impuesto_reducido; double cargo_envio_pequeno; double cargo_envio_mediano; } configuracion_precios_t; double calcular_precio_con_config(double precio_base, int cantidad, bool es_mayorista, const configuracion_precios_t *config) { // Uso de configuración inyectada double descuento = es_mayorista ? config->descuento_mayorista : 0.0; double subtotal = precio_base * cantidad * (1 - descuento); // ... resto del cálculo usando config return subtotal; }
Conviene vigilar el bool es_mayorista: si el valor de la bandera cambia por
completo el comportamiento, la regla de parámetros bandera pide separar cada
caso en una función con nombre propio (0x7007h: Evitá los parámetros bandera de tipo bool).
Extracción con Callbacks¶
Para lógica parametrizable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25typedef bool (*criterio_filtro_t)(int valor); bool es_par(int valor) { return valor % 2 == 0; } bool es_positivo(int valor) { return valor > 0; } int contar_elementos_que_cumplen(const int *arr, int n, criterio_filtro_t criterio) { int contador = 0; for (int i = 0; i < n; i++) { if (criterio(arr[i])) { contador++; } } return contador; } // Uso int pares = contar_elementos_que_cumplen(arr, n, es_par); int positivos = contar_elementos_que_cumplen(arr, n, es_positivo);
Antipatrones a Evitar¶
1. Sobre-Extracción¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14// Excesivo - funciones triviales que no agregan valor int incrementar(int x) { return x + 1; } int decrementar(int x) { return x - 1; } // Uso innecesariamente verboso for (int i = 0; i < n; i = incrementar(i)) { // ... }
2. Funciones con Demasiados Parámetros¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17// Problemático void procesar(int a, int b, int c, int d, int e, int f, int g) { // Difícil de usar y mantener } // Mejor: usar estructura typedef struct { int param_a; int param_b; int param_c; // ... } parametros_procesamiento_t; void procesar(const parametros_procesamiento_t *params) { // Más claro }
El empaquetado en un struct es justamente lo que exige la regla de
modularización: una función no debe superar los cuatro parámetros de entrada
(0x200Ah: Modularización: una función no debe exceder 4 parámetros de entrada).
3. Funciones con Efectos Secundarios Ocultos¶
1 2 3 4 5 6 7 8 9 10 11// Problemático int calcular_total(int *contador_global) { (*contador_global)++; // Efecto secundario oculto return *contador_global * 100; } // Mejor: efecto secundario explícito o función pura int calcular_total_puro(int contador) { return contador * 100; }
Guía de Decisión¶
¿Debería Extraer Esta Función?¶
SÍ, si:
Se repite en múltiples lugares
Tiene más de 20-30 líneas
Mezcla niveles de abstracción
Requiere comentario explicativo
Es difícil de testear en su contexto actual
Tiene anidamiento profundo (>3 niveles)
NO, si:
Es trivial (1-2 líneas obvias)
Solo se usa una vez y está clara en contexto
La extracción oscurece en lugar de clarificar
Requeriría demasiados parámetros (>5-6)
Niveles de Abstracción¶
Una función bien diseñada mantiene un nivel de abstracción consistente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26// Mal: mezcla niveles de abstracción void procesar_pedido(pedido_t *pedido) { // Alto nivel if (!validar_pedido(pedido)) return; // Bajo nivel - no pertenece aquí for (int i = 0; i < pedido->num_items; i++) { if (pedido->items[i].precio < 0) { return; } } // Alto nivel guardar_pedido(pedido); } // Bien: nivel consistente void procesar_pedido(pedido_t *pedido) { if (!validar_pedido(pedido)) return; if (!validar_items(pedido)) return; guardar_pedido(pedido); }
Además del nivel de abstracción, la extracción deja naturalmente párrafos separados por líneas en blanco: las sentencias que colaboran en una misma etapa quedan agrupadas y los bloques lógicos, separados (0x0017h: Agrupá sentencias relacionadas y separá bloques lógicos).
Diagnóstico y refactorización¶
Usá esta tabla para reconocer el síntoma en el código propio y elegir la técnica de extracción que corresponde.
| Regla | Síntoma en el código | Técnica de esta guía |
|---|---|---|
| 0x2014h: Cada función debe caber en una sola idea y en 25 líneas | Cuerpo que supera las 25 líneas y mezcla etapas. | Extraer cada etapa en una función corta de una sola idea. |
| 0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única) | La función “hace y además”: leer, validar y calcular. | Aislar cada responsabilidad en su propia función. |
| 0x2016h: Escribí el contrato de la función antes de implementarla | Decisiones de borde tomadas a mitad de la implementación. | Redactar el contrato al definir la firma. |
| 0x200Ah: Modularización: una función no debe exceder 4 parámetros de entrada | Firma con cinco o más parámetros sueltos. | Empaquetar los datos que viajan juntos en un struct y pasar un puntero. |
| 0x1015h: Limitá la profundidad de anidación a tres niveles | Cuarto nivel de if/for anidado. | Extraer el bloque interno a una función. |
| 0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda | Validaciones enterradas en ramas profundas. | Mover las precondiciones a guardas al inicio. |
| 0x2003h: Todas las funciones deben incluir documentación completa y estructurada | Función sin propósito, precondición ni postcondición escritas. | Documentar con @brief, @param, @pre, @post y @returns. |
| 0x2008h: Los ejercicios deben ser resueltos mediante funciones | Toda la solución dentro de main. | Descomponer en funciones y dejar main como orquestador. |
| 0x0017h: Agrupá sentencias relacionadas y separá bloques lógicos | Muro de sentencias sin etapas visibles. | Agrupar por etapa y separar con una línea en blanco. |
| 0x7007h: Evitá los parámetros bandera de tipo bool | Llamada imprimir(lista, true) sin saber qué hace el true. | Separar en dos funciones con nombres explícitos. |
Resumen¶
La extracción de funciones es fundamental para:
Legibilidad: Nombres descriptivos documentan el código
Reutilización: DRY (Don’t Repeat Yourself)
Testing: Funciones pequeñas son más fáciles de testear
Mantenibilidad: Cambios localizados y predecibles
Abstracción: Ocultar complejidad de implementación
Proceso:
Identificar fragmento cohesivo
Analizar dependencias
Elegir nombre descriptivo
Determinar firma
Extraer y reemplazar
Probar
Principios:
Una responsabilidad por función
Nivel de abstracción consistente
Nombres que describen QUÉ, no CÓMO
Minimizar efectos secundarios
Preferir funciones puras cuando sea posible
La extracción de funciones es una habilidad que se desarrolla con práctica. Al principio puede parecer tedioso, pero con el tiempo se vuelve una segunda naturaleza que mejora dramáticamente la calidad del código.
Checklist de verificación¶
¿Cada función cabe en una sola idea y en 25 líneas? (0x2014h: Cada función debe caber en una sola idea y en 25 líneas)
¿Puedo describir cada función con una frase sin “y”? (0x2005h: Cada función debe tener una única responsabilidad (Principio de Responsabilidad Única))
¿Escribí el contrato de cada función antes de implementarla? (0x2016h: Escribí el contrato de la función antes de implementarla)
¿Ninguna función supera los cuatro parámetros de entrada? (0x200Ah: Modularización: una función no debe exceder 4 parámetros de entrada)
¿La anidación se mantiene en tres niveles o menos? (0x1015h: Limitá la profundidad de anidación a tres niveles)
¿Las precondiciones se validan con guardas y retornos anticipados al inicio? (0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda)
¿Cada función tiene su bloque de documentación estructurada? (0x2003h: Todas las funciones deben incluir documentación completa y estructurada)
¿Separé las etapas con líneas en blanco, eliminé los parámetros bandera
booly dejémaincomo orquestador? (0x0017h: Agrupá sentencias relacionadas y separá bloques lógicos, 0x7007h: Evitá los parámetros bandera de tipo bool, 0x2008h: Los ejercicios deben ser resueltos mediante funciones)