Regla 0x300Dh: Utilizá enum en lugar de 'números mágicos' para conjuntos de estados y valores constantes
Memoria, punteros y tipos (0x30XX)
0x300Dh: Utilizá enum en lugar de ‘números mágicos’ para conjuntos de estados y valores constantes¶
Enunciado normativo¶
Los conjuntos de estados y de valores constantes relacionados DEBEN declararse con
enum(o con constantes simbólicas con nombre) y NO DEBEN aparecer como literales numéricos dispersos en el código.
¿Por qué existe esta regla?¶
El problema¶
Un literal como if (estado == 2) no dice qué representa 2: el significado vive
sólo en la cabeza del autor y quien escribe puede confundirlo con el 3. enum
introduce un conjunto de constantes con nombre junto al tipo, y así el
compilador, el depurador y el lector comparten la misma semántica.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Bug lógico | Dos estados se confunden porque 1 y 2 son intercambiables a la vista. |
| Mantenibilidad | Insertar un estado intermedio obliga a renumerar todos los usos. |
| Depuración | El depurador muestra 2 en lugar de ESTADO_LISTO. |
Fundamento en el estándar y en la cátedra¶
C11 §6.7.2.2 define enum como un conjunto de constantes enteras con nombre. La
cátedra lo adopta para todo grupo de valores mutuamente excluyentes y lo prefiere
sobre #define porque agrupa los valores y les da un tipo.
Alcance y excepciones¶
Aplica a estados, modos, códigos de error y conjuntos de valores relacionados.
No aplica a constantes de dominio aisladas (por ejemplo una longitud máxima),
donde corresponde un #define o una const conforme a 0x0103h: Las constantes (const o #define) deben nombrarse en MAYUSCULAS_SNAKE_CASE.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Comparación contra un literal¶
if (estado == 2)
{
reiniciar();
}Por qué falla: no se sabe qué es 2. Si el conjunto de estados cambia, el
significado se pierde y la comparación puede pasar a referirse a otro estado. El
nombre del estado debería estar en el código.
❌ Contraejemplo 2 — Constantes relacionadas definidas por separado¶
#define ESTADO_NUEVO 0
#define ESTADO_LISTO 1
#define ESTADO_ERROR -1
void avanzar(int estado)
{
if (estado == ESTADO_LISTO)
{
siguiente_paso();
}
}Por qué falla: los valores se declaran por separado, sin un tipo que los agrupe.
avanzar acepta cualquier int, incluidos valores ajenos al conjunto, y el
compilador no puede advertirlo.
✅ Ejemplo conforme 1 — enum con tipo propio¶
typedef enum estado
{
ESTADO_NUEVO,
ESTADO_LISTO,
ESTADO_ERROR
} estado_t;
void avanzar(estado_t estado)
{
if (estado == ESTADO_LISTO)
{
siguiente_paso();
}
}El conjunto y su tipo se declaran juntos. El lector ve el nombre del estado, el depurador también, y la firma comunica que sólo se aceptan valores del conjunto. El alias respeta 0x3004h: Utilizá typedef para definir tipos de estructuras con el sufijo _t.
✅ Ejemplo conforme 2 — enum para índices simbólicos¶
typedef enum campo
{
CAMPO_NOMBRE = 0,
CAMPO_EDAD,
CAMPO_CIUDAD,
CAMPO_CANTIDAD
} campo_t;
char *etiquetas[CAMPO_CANTIDAD];
etiquetas[CAMPO_EDAD] = "Edad";CAMPO_CANTIDAD cierra el conjunto y expresa la dimensión del arreglo, evitando
un literal de tamaño. Los índices se leen con nombre y el arreglo no puede
desincronizarse sin advertirlo. Se refuerza con 0x010Fh: Prohibición de constantes numéricas mágicas en índices de arreglos.
⚠️ Casos límite¶
Valores serializados: si el número se guarda en un archivo o protocolo, no renumera el
enumsin actualizar el formato; documentá la correspondencia.switch: todoswitchsobre unenumdebe cubrir los casos y cerrar condefault, conforme a 0x1008h: Toda instrucción switch debe incluir un caso default.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Comparación de estado contra literal numérico. |
gcc / clang | gcc -Wall -Wextra -std=c11 -Wswitch-enum ... | case faltante en switch sobre enum. |
| Revisión manual | — | Literales repetidos que representan el mismo estado. |
Checklist de autocontrol¶
¿Los estados forman un
enumcon nombre?¿El
enumtiene un alias_t?¿Evité comparar estados contra literales?
¿Usé el último miembro como cantidad cuando aplica?
Reglas relacionadas¶
0x2007h: Los valores de retorno numéricos deben definirse como constantes de preprocesador o enums — los valores de retorno numéricos también se nombran.
0x010Fh: Prohibición de constantes numéricas mágicas en índices de arreglos — índices simbólicos en lugar de literales.
0x1008h: Toda instrucción switch debe incluir un caso default —
defaultobligatorio enswitch.0x0103h: Las constantes (const o #define) deben nombrarse en MAYUSCULAS_SNAKE_CASE — convención de mayúsculas para constantes.
Antipatrón: Número mágico literal en condición lógica¶
Síntoma en el código del estudiante¶
Las decisiones de dominio se escriben con literales sueltos:
if (estado == 404) {
/* ... */
} else if (estado == 500) {
/* ... */
}El lector no puede saber qué significa 404 ni 500 sin acudir al enunciado.
Lo mismo ocurre con 0, 1, 2 usados como modos de un TAD.
Diagnóstico¶
Mecanismo del defecto¶
Un literal numérico no tiene nombre ni tipo semántico. El compilador solo ve un
int; no puede verificar que el valor pertenezca al conjunto válido de estados.
Cuando el conjunto cambia (se agrega un estado, se reordena), hay que cazar
todos los literales repetidos a mano y cada omisión se convierte en un bug
lógico silencioso.
El mismo problema es más grave al pasar estos valores como argumentos: una
firma void configurar(int modo) acepta cualquier entero, incluidos los que no
tienen sentido. Un enum restringe el dominio y autodocumenta el contrato.
Consecuencia observable¶
Código ilegible sin el enunciado al lado.
Bugs por desincronización tras cambiar un valor en un solo lugar.
Imposibilidad de que el compilador advierta sobre un valor inválido.
Fundamento en el estándar C11¶
C11 §6.7.2.2: un
enumdefine un conjunto de constantes de tipo entero con nombres; el compilador conoce ese conjunto y su tipo subyacente.C11 §6.7.2.2p4: los enumeradores tienen tipo
int(salvo que no quepan), por lo que unswitchsobreenumpuede cubrirse exhaustivamente.La regla 0x010Fh extiende el principio a los índices de arreglos: todo literal con significado de dominio se reemplaza por una constante simbólica.
Corrección idiomática¶
❌ Código con el antipatrón¶
int procesar(int estado)
{
if (estado == 1) {
return 0;
} else if (estado == 2) {
return 1;
}
return -1;
}✅ Código refactorizado — enum con nombre¶
typedef enum {
ESTADO_NUEVO = 0,
ESTADO_EN_PROCESO = 1,
ESTADO_TERMINADO = 2
} estado_t;
int procesar(estado_t estado)
{
switch (estado) {
case ESTADO_NUEVO:
return 0;
case ESTADO_EN_PROCESO:
return 1;
case ESTADO_TERMINADO:
return 2;
default:
return -1;
}
}✅ Alternativa — constantes #define para valores externos¶
#define HTTP_NOT_FOUND 404
#define HTTP_SERVER_ERROR 500
if (codigo == HTTP_NOT_FOUND) {
/* ... */
}enum es preferible cuando los valores forman un conjunto cerrado y
relacionado; #define sirve para constantes aisladas.
⚠️ Casos límite¶
0y1como cantidades triviales (i = 0) son legítimos: no son “mágicos”, son la identidad de un contador.Los
-1de error suelen merecer un nombre (ERROR,NO_ENCONTRADO).Un
enumcon un único valor no aporta; conviene un#definebien nombrado.
Errores típicos al compilar o ejecutar¶
# El compilador no detecta un valor fuera de dominio:
$ ./programa
# estado = 7 entra por ninguna rama y devuelve -1 silenciosamente
$ gcc -Wall -Wextra -std=c11 -Wswitch programa.c
# con enum y switch sin default útil, avisa si falta un caso enumeradoChecklist de verificación¶
¿Los estados del dominio tienen nombres (
enum) en lugar de literales?¿El
switchcubre todos los enumeradores o tienedefault?¿Las firmas usan el tipo
enumen lugar deintpara esos valores?¿Las constantes aisladas siguen la convención MAYUSCULAS_SNAKE_CASE?
Reglas relacionadas¶
0x300Dh: Utilizá enum en lugar de ‘números mágicos’ para conjuntos de estados y valores constantes — enum para conjuntos de estados.
0x010Fh: Prohibición de constantes numéricas mágicas en índices de arreglos — constantes simbólicas para índices de arreglos.
0x2007h: Los valores de retorno numéricos deben definirse como constantes de preprocesador o enums — valores de retorno numéricos como constantes o enums.
0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) — otro uso de una constante sin tipo.