Introducción a la programación estructurada
La programación estructurada es un paradigma que, desde la década de 1970, ha transformado la forma en que diseñamos y mantenemos software. Su objetivo principal es reducir la complejidad mediante el uso de tres estructuras de control básicas, la modularidad y la claridad en la lógica del programa. En este curso exploraremos los conceptos clave que aparecen en el cuestionario, proporcionando ejemplos en C#, analogías didácticas y buenas prácticas para que puedas aplicar estos principios en tus proyectos.
El teorema de Böhm‑Jacopini y sus tres estructuras de control
Según el teorema de Böhm‑Jacopini, cualquier algoritmo computable puede expresarse usando únicamente:
- Secuencia: ejecución lineal de instrucciones una tras otra.
- Selección (también llamada decisión): estructuras
if‑elseoswitchque eligen entre caminos alternativos. - Iteración: bucles como
while,do‑whileoforque repiten un bloque de código.
Estas tres construcciones son suficientes para describir cualquier proceso lógico, siempre que se evite el uso indiscriminado del salto goto. La ausencia de goto favorece la legibilidad y facilita el análisis estático del código.
Modularidad: dividir el código en funciones o métodos
Una de las ventajas más importantes de la modularidad es facilitar el mantenimiento. Al encapsular la lógica en funciones pequeñas y bien nombradas, los desarrolladores pueden localizar y corregir errores sin tener que revisar todo el programa.
Beneficios concretos
- Reutilización de código: una función bien diseñada puede ser llamada desde varios lugares.
- Pruebas unitarias más simples: cada módulo se puede probar de forma aislada.
- Mejor legibilidad: el flujo principal del programa se vuelve más claro al delegar tareas a sub‑rutinas.
En C# la modularidad se logra mediante métodos dentro de clases, pero el principio sigue siendo el mismo: separar responsabilidades.
public int Sumar(int a, int b)
{
return a + b; // función simple y reutilizable
}
public void ProcesarDatos()
{
int total = Sumar(5, 7);
Console.WriteLine($"Resultado: {total}");
}
Imperativo vs. declarativo: ¿cómo describimos la solución?
El paradigma imperativo indica cómo lograr un resultado, describiendo paso a paso la secuencia de operaciones. En cambio, el paradigma declarativo especifica qué se desea obtener, dejando que el motor del lenguaje descubra el proceso.
Ejemplo culinario
Imagínate una receta de pastel. En un enfoque imperativo se escribiría:
// Imperativo
Mezclar harina, azúcar y huevos;
Batir la mezcla 5 minutos;
Hornear a 180°C durante 30 minutos;
Mientras que en un enfoque declarativo simplemente se diría:
// Declarativo
Obtener pastel de chocolate;
El motor de la cocina (el horno, la batidora) se encarga de los detalles. En programación, lenguajes como SQL o LINQ son ejemplos declarativos, mientras que C#, Java o Python son típicamente imperativos.
Diferencia semántica entre while y do‑while en C#
Ambos bucles repiten un bloque de código mientras una condición sea verdadera, pero la evaluación de la condición ocurre en momentos diferentes:
while: la condición se evalúa antes de ejecutar el cuerpo. Si la condición es falsa desde el inicio, el cuerpo nunca se ejecuta.do‑while: el cuerpo se ejecuta al menos una vez y luego se evalúa la condición.
Ejemplo práctico:
// while: puede no ejecutarse
int i = 0;
while (i > 0)
{
Console.WriteLine(i);
i--;
}
// do‑while: siempre se ejecuta una vez
int j = 0;
do
{
Console.WriteLine(j);
j--;
} while (j > 0);
Switch vs. if‑else: complejidad ciclomática
Una afirmación frecuente es que switch siempre reduce la complejidad ciclomática. Esto es un error conceptual porque la métrica de complejidad ciclomática cuenta el número de caminos de ejecución independientes, sin importar la sentencia utilizada.
Por qué el tipo de sentencia no altera la métrica
Si un switch tiene cinco casos, la complejidad será 6 (una rama por caso más la salida). Un conjunto de if‑else con cinco condiciones también genera 6 caminos. Lo que sí influye es el número de ramas, no si se usan switch o if‑else.
Un truco visual: imagine cada caso como una puerta. Tanto con una llave (if) como con un panel de selección (switch), el número de puertas que debes abrir sigue siendo el mismo.
// Ejemplo con switch
switch (opcion)
{
case 1: /* ... */ break;
case 2: /* ... */ break;
case 3: /* ... */ break;
default: /* ... */ break;
}
// Equivalente con if‑else
if (opcion == 1) { /* ... */ }
else if (opcion == 2) { /* ... */ }
else if (opcion == 3) { /* ... */ }
else { /* ... */ }
La carta de Dijkstra de 1968 y la crisis del software
En 1968, Edsger W. Dijkstra publicó la influyente carta “Go To Statement Considered Harmful”. En ella argumentó contra el uso indiscriminado del goto, señalando que los saltos arbitrarios dificultan la comprensión y el razonamiento formal del código.
La carta impulsó la adopción de estructuras de control bien definidas (secuencia, selección, iteración) y sentó las bases de la programación estructurada. Fue un hito clave en la llamada crisis del software, que describía la creciente incapacidad de los equipos para producir sistemas fiables y mantenibles.
Elección de la estructura de iteración adecuada para colecciones de tamaño desconocido
Cuando el número de elementos a procesar no se conoce de antemano y la búsqueda termina al encontrar un elemento que cumple una condición, el bucle más natural es while con una condición basada en la búsqueda.
// Búsqueda de un número primo en una lista indefinida
int numero;
Console.WriteLine("Introduce números (0 para terminar):");
while ((numero = int.Parse(Console.ReadLine())) != 0)
{
if (EsPrimo(numero))
{
Console.WriteLine($"{numero} es primo");
break; // sale al encontrar el primer primo
}
}
Un for con contador fijo no sirve porque el límite no está definido, y un do‑while obligaría a ejecutar el cuerpo al menos una vez, lo que puede no ser deseado.
Programación estructurada dentro de métodos de clases orientadas a objetos
En C# la programación orientada a objetos (POO) y la estructurada no son excluyentes. Un método de una clase sigue aplicando las mismas tres estructuras de control (secuencia, selección, iteración) aunque esté encapsulado dentro de un objeto.
Ejemplo de método estructurado en una clase
public class Calculadora
{
public int Factorial(int n)
{
// Secuencia: declaración de variables
int resultado = 1;
// Iteración: bucle while
while (n > 1)
{
resultado *= n; // Selección implícita dentro del bucle
n--;
}
return resultado; // Secuencia final
}
}
El hecho de que el método pertenezca a una clase no elimina la modularidad ni la necesidad de estructuras de control; simplemente añade encapsulación y reutilización a nivel de objeto.
Conclusión y próximos pasos
Dominar los fundamentos de la programación estructurada es esencial para escribir código claro, mantenible y fácil de probar. Recuerda:
- Usar siempre secuencia, selección e iteración como bloques básicos.
- Aprovechar la modularidad para aislar funcionalidades.
- Distinguir entre enfoques imperativos y declarativos según el problema.
- Elegir el bucle adecuado (
whilevsdo‑while) según la necesidad de una ejecución previa. - Entender que la complejidad ciclomática depende del número de ramas, no del tipo de sentencia.
- Valorar la influencia histórica de Dijkstra y su llamado a evitar
goto. - Aplicar estructuras de iteración apropiadas a colecciones de tamaño desconocido.
- Integrar la programación estructurada dentro de la POO sin perder sus principios.
Practica cada concepto con pequeños ejercicios en C#. Con la práctica constante, estos principios pasarán de ser teoría a convertirse en hábitos de desarrollo que mejorarán la calidad de tus proyectos.