Introducción¶
Un número mágico (magic number) es un valor literal numérico que aparece directamente en el código sin explicación de su significado o propósito. Estos valores dificultan la comprensión del código, su mantenimiento y aumentan la probabilidad de errores cuando se necesita modificar el valor en múltiples lugares.
La refactorización para eliminar números mágicos consiste en reemplazarlos por constantes con nombres descriptivos que expresen claramente su propósito y significado en el contexto del programa.
¿Por Qué Son Problemáticos los Números Mágicos?¶
1. Falta de Claridad¶
Código con números mágicos:
if (edad >= 18 && edad < 65) {
aplicar_descuento(precio * 0.85);
}Preguntas que surgen:
¿Por qué 18? ¿Mayoría de edad? ¿Edad mínima para conducir?
¿Por qué 65? ¿Jubilación? ¿Algún límite legal?
¿Qué significa 0.85? ¿15% de descuento? ¿85% del precio?
2. Mantenimiento Difícil¶
Si el descuento cambia de 15% a 20%, debés:
Buscar todas las ocurrencias de
0.85Determinar cuáles corresponden a este descuento específico
Cambiar
0.85por0.80Esperar no haber modificado un
0.85que significaba otra cosa
3. Propensión a Errores¶
1 2 3 4 5 6 7 8 9// Archivo 1 if (intentos >= 3) { bloquear_cuenta(); } // Archivo 2 if (intentos > 3) { // ¿Error? ¿Inconsistencia? notificar_administrador(); }
¿El límite es 3 o 4? La inconsistencia genera bugs sutiles.
4. Duplicación Oculta¶
char buffer[256];
char nombre[256];
char direccion[256];Si 256 se repite porque representa el mismo concepto (por ejemplo, longitud
máxima de entrada), debería ser una constante compartida.
Tipos de Números Mágicos¶
1. Literales Numéricos¶
Los más comunes y evidentes:
1 2 3 4 5 6 7 8 9 10 11// Malo if (temperatura > 100) { activar_alarma(); } // Bueno const int TEMPERATURA_EBULLICION_AGUA = 100; if (temperatura > TEMPERATURA_EBULLICION_AGUA) { activar_alarma(); }
2. Factores de Conversión¶
1 2 3 4 5 6 7 8 9 10// Malo double metros = pies * 0.3048; double kilogramos = libras * 0.453592; // Bueno const double METROS_POR_PIE = 0.3048; const double KG_POR_LIBRA = 0.453592; double metros = pies * METROS_POR_PIE; double kilogramos = libras * KG_POR_LIBRA;
3. Tamaños de Buffer¶
1 2 3 4 5 6 7 8 9 10 11// Malo char nombre[50]; char apellido[50]; fgets(nombre, 50, stdin); // Bueno #define MAX_NOMBRE 50 char nombre[MAX_NOMBRE]; char apellido[MAX_NOMBRE]; fgets(nombre, MAX_NOMBRE, stdin);
4. Códigos de Estado o Error¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23// Malo if (resultado == -1) { printf("Error\n"); } else if (resultado == 0) { printf("OK\n"); } else if (resultado == 1) { printf("Advertencia\n"); } // Bueno enum estado_resultado { RESULTADO_ERROR = -1, RESULTADO_EXITO = 0, RESULTADO_ADVERTENCIA = 1 }; if (resultado == RESULTADO_ERROR) { printf("Error\n"); } else if (resultado == RESULTADO_EXITO) { printf("OK\n"); } else if (resultado == RESULTADO_ADVERTENCIA) { printf("Advertencia\n"); }
5. Límites y Umbrales¶
1 2 3 4 5 6 7 8 9 10 11// Malo if (calificacion >= 60) { printf("Aprobado\n"); } // Bueno const int CALIFICACION_MINIMA_APROBACION = 60; if (calificacion >= CALIFICACION_MINIMA_APROBACION) { printf("Aprobado\n"); }
Técnicas de Refactorización¶
Método 1: Constantes con const¶
Ventajas:
Type-safe (tiene tipo específico)
Scope controlado (puede ser local, global, o estática)
Compatible con debuggers modernos
const int INTENTOS_MAXIMOS = 3;
const double PI = 3.14159265358979323846;
const char* MENSAJE_BIENVENIDA = "Hola, usuario";Scope local:
1 2 3 4 5 6 7 8 9 10void procesar_pedido(double precio) { const double IMPUESTO = 0.21; // IVA en Argentina const double DESCUENTO_MAYORISTA = 0.15; double total = precio * (1 + IMPUESTO); if (es_mayorista()) { total *= (1 - DESCUENTO_MAYORISTA); } // ... }
Método 2: Macros con #define¶
Ventajas:
Disponible en todo el archivo (o globalmente si está en .h)
Puede usarse para definir tamaños de arrays
Compatible con código legacy
Desventajas:
Sin type-safety
Puede causar problemas si se redefine
No tiene scope
1 2 3 4 5 6 7 8 9 10#define MAX_USUARIOS 100 #define LONGITUD_NOMBRE 50 #define VERSION "1.0.3" typedef struct { char nombre[LONGITUD_NOMBRE]; int id; } usuario_t; usuario_t usuarios[MAX_USUARIOS];
Método 3: Enumeraciones¶
Ideal para conjuntos de valores relacionados:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16enum dias_semana { LUNES = 1, MARTES = 2, MIERCOLES = 3, JUEVES = 4, VIERNES = 5, SABADO = 6, DOMINGO = 7 }; enum prioridad_tarea { PRIORIDAD_BAJA, PRIORIDAD_MEDIA, PRIORIDAD_ALTA, PRIORIDAD_CRITICA };
Método 4: Variables Static Const en Funciones¶
Para constantes que solo se usan en una función específica:
double calcular_area_circulo(double radio) {
static const double PI = 3.14159265358979323846;
return PI * radio * radio;
}Casos Prácticos de Refactorización¶
Caso 1: Sistema de Autenticació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 22 23 24 25 26 27 28#include <stdio.h> #include <string.h> #include <stdbool.h> bool autenticar_usuario(const char* usuario, const char* password) { int intentos = 0; while (intentos < 3) { if (strlen(password) < 8) { printf("Password muy corta\n"); return false; } if (strcmp(usuario, "admin") == 0 && strcmp(password, "12345678") == 0) { return true; } intentos++; if (intentos < 3) { printf("Intento %d de 3 fallido\n", intentos); } } // Bloquear por 300 segundos printf("Cuenta bloqueada por 300 segundos\n"); return false; }
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#include <stdio.h> #include <string.h> #include <stdbool.h> // Constantes de configuración del sistema const int MAX_INTENTOS_LOGIN = 3; const int LONGITUD_MINIMA_PASSWORD = 8; const int TIEMPO_BLOQUEO_SEGUNDOS = 300; // Credenciales (en producción vendrían de BD) const char* USUARIO_ADMIN = "admin"; const char* PASSWORD_ADMIN = "12345678"; // Solo para demo bool autenticar_usuario(const char* usuario, const char* password) { int intentos = 0; while (intentos < MAX_INTENTOS_LOGIN) { if (strlen(password) < LONGITUD_MINIMA_PASSWORD) { printf("Password debe tener al menos %d caracteres\n", LONGITUD_MINIMA_PASSWORD); return false; } if (strcmp(usuario, USUARIO_ADMIN) == 0 && strcmp(password, PASSWORD_ADMIN) == 0) { return true; } intentos++; if (intentos < MAX_INTENTOS_LOGIN) { printf("Intento %d de %d fallido\n", intentos, MAX_INTENTOS_LOGIN); } } printf("Cuenta bloqueada por %d segundos\n", TIEMPO_BLOQUEO_SEGUNDOS); return false; }
Beneficios:
Fácil ajustar la política de seguridad (cambiar 3 intentos a 5, etc.)
Claro qué representa cada valor
Un solo lugar para modificar cada parámetro
Caso 2: Procesamiento de Temperaturas¶
Código Original:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18void procesar_temperatura(double temp_fahrenheit) { double temp_celsius = (temp_fahrenheit - 32) * 5 / 9; if (temp_celsius < 0) { printf("Congelación\n"); } else if (temp_celsius < 25) { printf("Frío\n"); } else if (temp_celsius < 35) { printf("Agradable\n"); } else { printf("Calor\n"); } // Alarma si supera 100°C if (temp_celsius > 100) { activar_alarma(); } }
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// Constantes de conversión const double FAHRENHEIT_OFFSET = 32.0; const double CELSIUS_A_FAHRENHEIT_FACTOR = 9.0 / 5.0; const double FAHRENHEIT_A_CELSIUS_FACTOR = 5.0 / 9.0; // Umbrales de temperatura en Celsius const double TEMP_CONGELACION = 0.0; const double TEMP_FRIO = 25.0; const double TEMP_AGRADABLE = 35.0; const double TEMP_EBULLICION_AGUA = 100.0; double fahrenheit_a_celsius(double fahrenheit) { return (fahrenheit - FAHRENHEIT_OFFSET) * FAHRENHEIT_A_CELSIUS_FACTOR; } void procesar_temperatura(double temp_fahrenheit) { double temp_celsius = fahrenheit_a_celsius(temp_fahrenheit); if (temp_celsius < TEMP_CONGELACION) { printf("Congelación\n"); } else if (temp_celsius < TEMP_FRIO) { printf("Frío\n"); } else if (temp_celsius < TEMP_AGRADABLE) { printf("Agradable\n"); } else { printf("Calor\n"); } if (temp_celsius > TEMP_EBULLICION_AGUA) { activar_alarma(); } }
Caso 3: Gestión de Archivos¶
Código Original:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21#include <stdio.h> void procesar_archivo(const char* ruta) { char buffer[1024]; FILE* archivo = fopen(ruta, "r"); if (archivo == NULL) { printf("Error abriendo archivo\n"); return; } while (fgets(buffer, 1024, stdin) != NULL) { // Procesar línea if (strlen(buffer) > 512) { printf("Línea muy larga (max 512)\n"); continue; } } fclose(archivo); }
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#include <stdio.h> #include <string.h> // Configuración de buffers #define TAMANO_BUFFER_LECTURA 1024 #define LONGITUD_MAXIMA_LINEA 512 // Mensajes de error const char* MSG_ERROR_APERTURA = "Error abriendo archivo"; const char* MSG_LINEA_LARGA = "Línea muy larga (max %d caracteres)"; void procesar_archivo(const char* ruta) { char buffer[TAMANO_BUFFER_LECTURA]; FILE* archivo = fopen(ruta, "r"); if (archivo == NULL) { printf("%s\n", MSG_ERROR_APERTURA); return; } while (fgets(buffer, TAMANO_BUFFER_LECTURA, archivo) != NULL) { // Procesar línea if (strlen(buffer) > LONGITUD_MAXIMA_LINEA) { printf(MSG_LINEA_LARGA, LONGITUD_MAXIMA_LINEA); printf("\n"); continue; } // ... más procesamiento } fclose(archivo); }
Caso 4: Validación de Datos¶
Código Original:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15bool validar_usuario(const char* nombre, int edad, double salario) { if (strlen(nombre) < 3 || strlen(nombre) > 50) { return false; } if (edad < 18 || edad > 120) { return false; } if (salario < 0 || salario > 1000000) { return false; } return true; }
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// Límites de validación const int LONGITUD_MINIMA_NOMBRE = 3; const int LONGITUD_MAXIMA_NOMBRE = 50; const int EDAD_MINIMA = 18; const int EDAD_MAXIMA = 120; const double SALARIO_MINIMO = 0.0; const double SALARIO_MAXIMO = 1000000.0; bool validar_nombre(const char* nombre) { size_t longitud = strlen(nombre); return longitud >= LONGITUD_MINIMA_NOMBRE && longitud <= LONGITUD_MAXIMA_NOMBRE; } bool validar_edad(int edad) { return edad >= EDAD_MINIMA && edad <= EDAD_MAXIMA; } bool validar_salario(double salario) { return salario >= SALARIO_MINIMO && salario <= SALARIO_MAXIMO; } bool validar_usuario(const char* nombre, int edad, double salario) { return validar_nombre(nombre) && validar_edad(edad) && validar_salario(salario); }
Números Mágicos Aceptables¶
El Número 0 y 1¶
En contextos obvios, 0 y 1 generalmente no necesitan ser constantes:
// Aceptable
int contador = 0;
int resultado = funcion() + 1;Potencias de 2 Comunes¶
En algunos contextos de bajo nivel:
// Generalmente aceptable en contextos de bits
int mascara = valor & 0xFF;
int desplazamiento = numero << 8;Índices de Arrays¶
// Aceptable
char primer_caracter = cadena[0];
char ultimo_caracter = cadena[strlen(cadena) - 1];Patrón de Organización de Constantes¶
Opción 1: Archivo de Configuración¶
config.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24#ifndef CONFIG_H #define CONFIG_H // Configuración del sistema #define VERSION_MAYOR 1 #define VERSION_MENOR 0 #define VERSION_PATCH 3 // Límites del sistema #define MAX_USUARIOS 100 #define MAX_CONEXIONES_SIMULTANEAS 50 #define TIMEOUT_CONEXION_SEGUNDOS 30 // Tamaños de buffer #define TAMANO_BUFFER_GENERAL 1024 #define LONGITUD_MAXIMA_NOMBRE 50 #define LONGITUD_MAXIMA_EMAIL 100 // Configuración de negocio const double IVA_ARGENTINA = 0.21; const int EDAD_MINIMA_CONDUCTOR = 18; const int PUNTOS_LICENCIA_INICIAL = 0; #endif // CONFIG_H
Opción 2: Constantes por Módulo¶
Cada módulo define sus propias constantes en su archivo .c:
1 2 3 4 5 6 7// usuario.c static const int LONGITUD_MINIMA_PASSWORD = 8; static const int MAX_INTENTOS_LOGIN = 3; // archivo.c static const int TAMANO_BUFFER = 4096; static const char* EXTENSION_TEMPORAL = ".tmp";
Opción 3: Enumeraciones Agrupadas¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17// Códigos de error del sistema enum codigos_error { ERROR_EXITO = 0, ERROR_MEMORIA = -1, ERROR_ARCHIVO_NO_ENCONTRADO = -2, ERROR_PERMISOS = -3, ERROR_FORMATO_INVALIDO = -4 }; // Niveles de log enum nivel_log { LOG_DEBUG = 0, LOG_INFO = 1, LOG_WARNING = 2, LOG_ERROR = 3, LOG_FATAL = 4 };
Proceso de Refactorización Paso a Paso¶
1. Identificar Números Mágicos¶
Buscar literales numéricos en el código:
# Con grep
grep -n "[^a-zA-Z][0-9]\+[^a-zA-Z]" archivo.c
# Revisar manualmente cada ocurrencia2. Analizar Contexto¶
Para cada número, preguntarse:
¿Qué representa?
¿Se usa en múltiples lugares?
¿Podría cambiar en el futuro?
¿Es claro su significado para otros desarrolladores?
3. Elegir Nombre Descriptivo¶
El nombre debe ser:
Auto-explicativo
En mayúsculas para constantes
Prefijo del módulo si es global
1 2 3 4 5 6 7 8// Malo const int MAX = 100; // Mejor const int MAX_USUARIOS = 100; // Mejor aún const int MAX_USUARIOS_SISTEMA = 100;
4. Determinar Scope¶
Global: Si se usa en múltiples archivos →
.hArchivo: Si se usa solo en un archivo →
static consten.cFunción: Si se usa solo en una función →
constlocal
5. Reemplazar Todas las Ocurrencias¶
Usar búsqueda y reemplazo con cuidado:
// Buscar: \b100\b (expresión regular)
// Reemplazar caso por caso, verificando contexto6. Verificar con Tests¶
Ejecutar tests para asegurar que el comportamiento no cambió.
Refactorización en Código Legacy¶
Estrategia Incremental¶
No intentar refactorizar todo a la vez:
Priorizar: Números más críticos o problemáticos primero
Por módulo: Refactorizar archivo por archivo
Con tests: Cada cambio debe tener tests que lo validen
Ejemplo de Refactorización Gradual¶
Iteración 1: Identificar candidatos
1 2 3 4 5 6 7 8// Marcar con comentarios if (temperatura > 100) { // MAGIC: punto de ebullición alarma(); } if (intentos >= 3) { // MAGIC: máximo de intentos bloquear(); }
Iteración 2: Extraer constantes locales
1 2 3 4 5 6 7 8 9const int PUNTO_EBULLICION = 100; if (temperatura > PUNTO_EBULLICION) { alarma(); } const int MAX_INTENTOS = 3; if (intentos >= MAX_INTENTOS) { bloquear(); }
Iteración 3: Centralizar si es necesario
Mover a archivo de configuración si se repite.
Antipatrones a Evitar¶
1. Sobre-Constantes¶
1 2 3 4 5 6 7 8 9// Excesivo const int UNO = 1; const int CERO = 0; const int VERDADERO = 1; // Innecesario for (int i = CERO; i < MAX; i = i + UNO) { // ... }
2. Constantes Obvias¶
// Obvio, no mejora la claridad
const int DIAS_EN_SEMANA = 7;
const int MESES_EN_ANIO = 12;
// Pero en contexto específico puede ser útil:
const int DIAS_LABORABLES_SEMANA = 5;3. Nombres Vagos¶
1 2 3 4 5 6 7// Malo const int LIMITE = 100; const int MAX = 50; // Bueno const int LIMITE_VELOCIDAD_KMH = 100; const int MAX_CARACTERES_COMENTARIO = 50;
Resumen¶
La eliminación de números mágicos es una refactorización fundamental que:
Mejora la legibilidad: El código se autodocumenta
Facilita el mantenimiento: Cambios centralizados
Reduce errores: Valores consistentes en todo el código
Documenta decisiones: Los nombres explican el “por qué”
Reglas clave:
Usar
constpara type-safety cuando sea posibleUsar
#definepara tamaños de arrays y compatibilidad legacyUsar
enumpara conjuntos de valores relacionadosNombres descriptivos y auto-explicativos
Scope apropiado (local cuando sea posible)
El tiempo invertido en esta refactorización se recupera rápidamente en mantenimiento y prevención de bugs.