Estructuras de control: cómo piensa un desarrollador al programar
Cuando un desarrollador escribe software, rara vez está pensando simplemente en "usar un for" o "usar un if". Lo que realmente está haciendo es definir qué debe ocurrir, cuándo debe ocurrir y bajo qué condiciones puede cambiar el comportamiento del programa.
Las estructuras de control son, en cierta forma, la manera en que el programador convierte decisiones cotidianas en reglas que una máquina puede ejecutar.
if: hacer algo solamente cuando tiene sentido
Supongamos que tenemos una tienda y queremos aplicar un descuento únicamente cuando un cliente ha gastado más de $2,000.
const total = 2350;
if (total > 2000) {
console.log("Aplicar descuento");
}
La pregunta del desarrollador no fue "¿dónde pongo un if?".
La pregunta fue más parecida a:
> "¿Qué condición tiene que cumplirse para permitir esta operación?"
Por eso muchas veces el if aparece alrededor de reglas de negocio:
if (user.isAuthenticated && user.role === "admin") {
deleteUser(userId);
}
Aquí el código está expresando una decisión concreta: la operación existe, pero no está disponible para cualquiera.
else: cuando la primera posibilidad no se cumple
A veces no basta con saber qué hacer cuando una condición es verdadera. También hay que decidir qué ocurre en caso contrario.
const stock = 0;
if (stock > 0) {
console.log("Comprar producto");
} else {
console.log("Producto agotado");
}
En sistemas reales esto aparece constantemente:
if (payment.approved) {
createOrder();
} else {
showPaymentError();
}
No se trata solamente de "verdadero o falso". El flujo del programa está tomando una dirección diferente dependiendo de lo ocurrido.
else if: cuando existen varias situaciones posibles
Hay casos donde una sola condición no representa todo el problema.
Por ejemplo, un sistema podría clasificar una respuesta dependiendo de su código:
const status = 404;
if (status === 200) {
console.log("Todo correcto");
} else if (status === 404) {
console.log("Recurso no encontrado");
} else if (status === 500) {
console.log("Error del servidor");
}
Aquí el desarrollador está construyendo una pequeña cadena de decisiones.
La idea suele ser:
> "Si no ocurrió A, comprueba B. Si tampoco ocurrió B, comprueba C."
switch: cuando estamos comparando un mismo valor
Supongamos que el sistema recibe un tipo de usuario:
const role = "editor";
switch (role) {
case "admin":
console.log("Acceso completo");
break;
case "editor":
console.log("Puede modificar contenido");
break;
case "viewer":
console.log("Solo puede consultar");
break;
default:
console.log("Rol desconocido");
}
Aquí no estamos evaluando condiciones completamente diferentes. Estamos preguntando:
> "¿Cuál de estos posibles valores recibió el sistema?"
Por eso switch suele resultar cómodo cuando existen múltiples estados conocidos de una misma variable.
for: cuando necesitamos recorrer algo
Un for no necesariamente significa "repite esto diez veces".
En código real suele aparecer porque existe una colección de información que necesitamos procesar.
const users = [
{ name: "Ana", active: true },
{ name: "Luis", active: false },
{ name: "Carlos", active: true }
];
for (const user of users) {
if (user.active) {
console.log(`Enviar correo a ${user.name}`);
}
}
El razonamiento sería algo como:
> "Tengo muchos usuarios y necesito aplicar la misma regla a cada uno."
Eso es mucho más cercano a cómo se utiliza un for en aplicaciones reales.
Otro caso:
for (const product of products) {
if (product.stock === 0) {
disablePurchaseButton(product.id);
}
}
No estamos repitiendo código porque sí. Estamos recorriendo un conjunto de datos para encontrar situaciones concretas.
while: seguir trabajando mientras algo continúe siendo cierto
while suele utilizarse cuando no sabemos exactamente cuántas iteraciones serán necesarias.
Por ejemplo:
let attempts = 0;
while (attempts < 3 && !loginSuccessful) {
attempts++;
loginSuccessful = tryLogin();
}
La lógica aquí no es:
> "Haz esto tres veces."
Es:
> "Continúa intentando mientras todavía sea válido hacerlo."
La diferencia parece pequeña, pero cambia la forma de pensar el problema.
do...while: primero ocurre, después se comprueba
Hay situaciones donde necesitamos ejecutar algo al menos una vez.
let option;
do {
option = askForOption();
} while (option !== "exit");
Primero se muestra el proceso; después se pregunta si debe continuar.
Esto resulta útil cuando la primera ejecución no depende de una condición previa.
break: dejar de recorrer porque ya encontramos lo que buscábamos
Imaginemos que queremos localizar un usuario concreto:
let foundUser = null;
for (const user of users) {
if (user.id === targetId) {
foundUser = user;
break;
}
}
El break evita seguir recorriendo información innecesaria.
El pensamiento sería:
> "Ya encontré lo que necesitaba; seguir buscando no aporta nada."
Este tipo de decisión aparece mucho cuando se intenta evitar trabajo innecesario.
continue: ignorar este caso y seguir con los demás
También puede ocurrir lo contrario: encontramos un elemento que simplemente no queremos procesar.
for (const user of users) {
if (!user.active) {
continue;
}
sendNotification(user);
}
El desarrollador está diciendo:
> "Este elemento no cumple las condiciones para esta operación, así que pasa al siguiente."
Es especialmente útil cuando queremos mantener el código principal limpio:
for (const order of orders) {
if (order.cancelled) {
continue;
}
processOrder(order);
}
En lugar de envolver todo el procesamiento dentro de un enorme if, se elimina temprano el caso que no interesa.
La idea importante
Las estructuras de control no son simplemente palabras reservadas que hay que memorizar.
Son herramientas para representar decisiones.
Un programador podría enfrentarse a algo como:
for (const order of orders) {
if (order.cancelled) {
continue;
}
if (order.total > 5000) {
applyVipDiscount(order);
}
processOrder(order);
}
Y detrás de esas pocas líneas existen varias decisiones:
- Hay múltiples órdenes que deben procesarse.
- Algunas órdenes no deben procesarse.
- Las órdenes suficientemente grandes reciben un tratamiento diferente.
- Las demás continúan por el flujo normal.
Ese es uno de los cambios importantes al aprender programación: dejar de pensar solamente en sintaxis y comenzar a pensar en flujo de ejecución.
El código termina siendo una forma de describir cómo debe comportarse el sistema ante diferentes situaciones.
Por eso un desarrollador no solamente piensa:
> "Necesito un for."
Más bien piensa:
> "Tengo una colección, necesito revisar cada elemento, algunos casos deben descartarse y otros requieren un tratamiento diferente."
Y después convierte ese razonamiento en código.
© Arturo Juárez Monroy