Qué es la programación modular en mil palabras o alguna más

(Este artículo forma parte del Curso de Programación en C)

Hay otra confusión que me he encontrado muchas veces entre los estudiantes que empiezan a hacer sus pinitos con la programación de ordenadores. Consiste en mezclar dos conceptos relacionados pero no sinónimos (ni mucho menos excluyentes): programación estructurada y programación modular.

De la programación estructurada ya hablamos aquí y en unas cuantas entradas posteriores que están enlazadas en ese mismo artículo. Hoy nos dedicaremos a aclarar el concepto de programación modular y a relacionarlo con el de programación estructurada.

Una definición de programación modular

Podemos definir la programación modular como aquélla que afronta la solución de un problema descomponiéndolo en subproblemas más simples, cada uno de los cuales se resuelve mediante un algoritmo o módulo más o menos independiente del resto (de ahí su nombre: “programación modular”).

Las ventajas de la programación modular son varias:

  • Facilita la comprensión del problema y su resolución escalonada
  • Aumenta la claridad y legibilidad de los programas
  • Permite que varios programadores trabajen en el mismo problema a la vez, puesto que cada uno puede trabajar en uno o varios módulos de manera bastante independiente
  • Reduce el tiempo de desarrollo, reutilizando módulos previamente desarrollados
  • Mejora la fiabilidad de los programas, porque es más sencillo diseñar y depurar módulos pequeños que programas enormes
  • Facilita el mantenimiento de los programas

Resumiendo, podemos afirmar sin temor a equivocarnos que es virtualmente imposible escribir un programa de grandes dimensiones si no procedemos a dividirlo en fragmentos más pequeños, abarcables por nuestro pobre intelecto humano.

Insisto en que la programación modular y la estructurada no son técnicas incompatibles, sino más bien complementarias. La mayoría de los programas que se desarrollan con lenguajes estructurados son, de hecho, estructurados y modulares al mismo tiempo.

Pero expliquemos más despacio que es eso de “descomponer un problema en subproblemas simples”…

Descomposición modular: ¡divide y vencerás!

La forma más habitual de diseñar algoritmos para resolver problemas de cierta envergadura se suele denominar, muy certeramente, divide y vencerás (en inglés, divide and conquer o simplemente DAC). Fíjese en que hemos dicho “diseñar” algoritmos: estamos adentrándonos, al menos en parte, en la fase de diseño del ciclo de vida del software.

El método DAC consiste en dividir un problema complejo en subproblemas, y tratar cada subproblema del mismo modo, es decir, dividiéndolo a su vez en subproblemas. Así sucesivamente hasta que obtengamos problemas lo suficientemente sencillos como para escribir algoritmos que los resuelvan. Llamaremos módulo a cada uno de estos algoritmos que resuelven los problemas sencillos.

Una vez resueltos todos los subproblemas, es decir, escritos todos los módulos, es necesario combinar de algún modo las soluciones para generar la solución global del problema.

Esta forma de diseñar una solución se denomina diseño descendente o top-down. No es la única técnica de diseño que existe, pero sí la más utilizada.

Resumiendo lo dicho hasta ahora, el diseño descendente debe tener dos fases:

  1. La identificación de los subproblemas más simples y la construcción de algoritmos que los resuelvan (módulos)
  2. La combinación de las soluciones de esos algoritmos para dar lugar a la solución global

La mayoría de lenguajes de programación estructurada permiten aplicar técnicas de diseño descendente mediante un proceso muy simple: independizando fragmentos de código en subprogramas o módulos denominados procedimientos y funciones, que en otro post analizaremos en profundidad.

Algoritmo principal y subalgoritmos

En general, el problema principal se resuelve en un algoritmo que denominaremos algoritmo o módulo principal, mientras que los subproblemas sencillos se resolverán en subalgoritmos, también llamados módulos a secas. Los subalgoritmos están subordinados al algoritmo principal, de manera que éste es el que decide en qué orden deben ejecutarse los subalgoritmo y con qué conjunto de datos.

El algoritmo principal realiza llamadas o invocaciones a los subalgoritmos, mientras que éstos devuelven resultados a aquél. Así, el algoritmo principal va recogiendo todos los resultados y puede generar la solución al problema global.

progmodular1.png

Cuando el algoritmo principal hace una llamada al subalgoritmo (es decir, lo invoca), se empiezan a ejecutar las instrucciones del subalgoritmo. Cuando éste termina, devuelve los datos de salida al algoritmo principal, y la ejecución continúa por la instrucción siguiente a la de invocación. También se dice que el subalgoritmo devuelve el control al algoritmo principal, ya que éste toma de nuevo el control del flujo de instrucciones después de habérselo cedido temporalmente al subalgoritmo.

El programa principal puede invocar a cada subalgoritmo el número de veces que sea necesario. A su vez, cada subalgoritmo puede invocar a otros subalgoritmos, y éstos a otros, etc. Cada subalgoritmo devolverá los datos y el control al algoritmo que lo invocó.

progmodular2.png

Los subalgoritmos pueden hacer las mismas operaciones que los algoritmos, es decir: entrada de datos, proceso de datos y salida de datos. La diferencia es que los datos de entrada se los proporciona el algoritmo que lo invoca, y los datos de salida son devueltos también a él para que haga con ellos lo que considere oportuno. No obstante, un subalgoritmo también puede, si lo necesita, tomar datos de entrada desde el teclado (o desde cualquier otro dispositivo de entrada) y enviar datos de salida a la pantalla (o a cualquier otro dispositivo de salida).

Un ejemplo

Vamos a diseñar un algoritmo que calcule el área y la circunferencia de un círculo cuyo radio se lea por teclado. Se trata de un problema muy simple que puede resolverse sin aplicar el método divide y vencerás, pero lo utilizaremos como ilustración.

Dividiremos el problema en dos subproblemas más simples: por un lado, el cálculo del área, y, por otro, el cálculo de la circinferencia. Cada subproblema será resuelto en un subalgoritmo, que se invocará desde el algoritmo principal. La descomposición en algoritmos y subalgoritmos sería la siguiente (se indican sobre las flechas los datos que son interrcambiados entre los módulos):

progmodular5.png

Lógicamente, los subalgoritmos deben tener asignado un nombre para que puedan ser invocados desde el algoritmo principal, y también existe un mecanismo concreto de invocación/devolución.

Nivel de descomposición modular

Los problema complejos, como venimos diciendo, se descomponen sucesivamente en subproblemas más simples cuya solución combinada dé lugar a la solución general. Pero, ¿hasta dónde es necesario descomponer? O, dicho de otro modo, ¿qué se puede considerar un “problema simple” y qué no?

La respuesta se deja al sentido común y a la experiencia del diseñador del programa. Como regla general, digamos que un módulo no debería constar de más de 30 ó 40 líneas de código. Si obtenemos un módulo que necesita más código para resolver un problema, probablemente podamos dividirlo en dos o más subproblemas. Por supuesto, esto no es una regla matemática aplicable a todos los casos. En muchas ocasiones no estaremos seguros de qué debe incluirse y qué no debe incluirse en un módulo.

Tampoco es conveniente que los módulos sean excesivamente sencillos. Programar módulos de 2 ó 3 líneas daría lugar a una descomposición excesiva del problema, aunque habrá ocasiones en las que sea útil emplear módulos de este tamaño.

Diagramas de estructura modular

La estructura modular, es decir, el conjunto de módulos de un programa y la forma en que se invocan unos a otros, se puede representar gráficamente mediante un diagrama de estructura modular. Esto es particularmente útil si el programa es complejo y consta de muchos módulos con relaciones complicadas entre sí.

En el diagrama se representan los módulos mediante cajas, en cuyo interior figura el nombre del módulo, unidos por líneas, que representan las interconexiones entre ellos. En cada línea se pueden escribir los parámetros de invocación y los datos devueltos por el módulo invocado.

El diagrama de estructura siempre tiene forma de árbol invertido. En la raíz figura el módulo principal, y de él “cuelgan” el resto de módulos en uno o varios niveles.

En el diagrama también se puede representar el tipo de relación entre los módulos. Las relaciones posibles se corresponden exactamente con los tres tipos de estructuras básicas de la programación estructurada:

  • Estructura secuencial: cuando un módulo llama a otro, después a otro, después a otro, etc.
  • Estructura selectiva: cuando un módulo llama a uno o a otro dependiendo de alguna condición
  • Estructura iterativa: cuando un módulo llama a otro (o a otros) en repetidas ocasiones

Las tres estructuras de llamadas entre módulos se representan con tres símbolos diferentes:

progmodular3.png

A modo de ejemplo, veamos el diagrama de estructura del algoritmo que calcula el área y la circunferencia de un círculo, que pusimos como ejemplo un poco más arriba. La descomposición modular que hicimos entonces consistía en un algoritmo principal que llamaba a dos subalgoritmos: uno para calcular el área y otro para calcular la circunferencia.

progmodular4.png

Los dos subalgoritmos (o módulos) son llamados en secuencia, es decir, uno tras otro, por lo que lo representamos con la estructura secuencial. El módulo principal pasará a los dos subalgoritmos el radio (R) del círculo, y cada subalgoritmo devolverá al módulo principal el resultado de sus cálculos.

Escritura del programa

Una vez diseñada la estructura modular, llega el momento de escribir los algoritmos y subalgoritmos mediante pseudocódigo, diagramas de flujo o cualquier otra herramienta. Lo más habitual es comenzar por los módulos (subalgoritmos) de nivel inferior e ir ascendiendo por cada rama del diagrama de estructura.

Lo dicho hasta ahora respecto de algoritmos y subalgoritmos se puede traducir en programas y subprogramas cuando pasemos del pseudocódigo a lenguajes de programación concretos, como C. Los subprogramas, en general, se pueden dividir en dos tipos, muy similares pero con alguna sutil diferencia: las funciones y los procedimientos, que estudiamos en este otro post.

Categorías

Licencia

ClustrMaps

José Rivas

Amigo, excelente su información acerca de programación modular. De verdad que es justamente lo que estaba buscando para lo que me piden en la uni… Excelente

Gracias. Uno hace lo que puede. Intentaré terminar de subir todo el curso de C a lo largo del próximo mes.

Estupendo articulo, como la mayoría de los que escribes. Un consejo: creo que estaría bien proporcionar un PDF de estos articulos, ya que muchos son dignos de tenerlos a mano en papel. Cómo este que, a pesar de cubrir aspectos básicos de programación, se olvidan con facilidad y está muy bien repasar de vez en cuando. Como es mí caso, que realizo tareas de programación de ciento en viento.

Gracias. En respuesta a su petición, he activado un plugin para exportar los artículos a PDF. Ahora, aparece un icono al final de cada artículo para iniciar su conversión a formato PDF.

Según he podido comprobar, la conversión no es perfecta (introduce algunos saltos de línea en donde no debería, y falla estrepitosamente en los artículos que incluyen imágenes), pero los administradores del sitio sólo tienen instalado ése. Bueno, no es perfecto, pero es mejor que nada. Trataré de solucionar lo de las imágenes para que al menos este artículo pueda convertirse a PDF.

Estupendo gracias, muy bueno info, sirivó para mi tarea :) la mejor info que encontré.

JUAN JOSE MORENO

GRACIAS MAN POR SU INFORMACION ESTA MUY CLARA Y TAMBIEN POR COMPARTIR TU CONOCIMIENTO…..SOY PARTIDADRIO DE LO QUE NO SE DA SE PIERDE….SALUDOS Y CUIDESEN…..

Estaba buscando informacion que fuera clara con respecto a la programacion modular y los diagramas modulares… y justo di con este post! felicitaciones! excelente, muy clarificador!