Unidad de trabajo nº 10

Diseño Modular y
Programación Estructurada


[Anterior] [Índice General] [Siguiente]


Índice

  1. Introducción

  2. Programación y abstracción

  3. Diseño modular

  4. Programación estructurada

  5. Librerías de funciones


1. Introducción

En las dos anteriores unidades temáticas hemos comenzado a experimentar el uso de la modularidad como potente herramienta para la construcción de programas de complejidad creciente.

Cuando se comienza a aprender a programar, se adquieren ciertas destrezas básicas a base de practicar con sencillos y breves supuestos. Pero los problemas del mundo real son mucho más complejos y extensos, así como cambiantes en el tiempo, por lo que su programación requiere un mayor esfuerzo en la fase de diseño y en la de mantenimiento.

Es por esto que deben considerarse recursos que faciliten la resolución de tales problemas. Según Niklaus Wirth (1974): 

"Nuestra herramienta mental más importante para competir con la complejidad es la abstracción. Por tanto, un problema complejo no deberá considerarse inmediatamente en términos de instrucciones de un lenguaje, sino de elementos naturales del problema mismo, abstraídos de alguna manera".

El enfoque más útil que emplea la mente humana para abordar la complejidad es el de la abstracción. Si esto es cierto en relación con cualquier tipo de actividad que requiera la resolución de problemas lógicos, lo es mucho más en el caso de la programación de ordenadores. 

En el caso que nos ocupa, se trata de construir modelos mentales que reproduzcan situaciones reales, y encapsulen y aíslen información y comportamientos, como piezas ejecutables. Éstas deben tener una interfaz de comunicación con el exterior (mundo real) y entre sí, conocida. Así mismo deben tener una ejecución correcta y ser de la máxima reutilización en otras situaciones similares.

Un esquema que resume el proceso de diseño de programas podría ser:

  1. Descomposición del problema en bloques o módulos de sólida cohesión interna (Diseño modular)
  2. Programación de cada módulo mediante métodos estructurados (Programación estructurada)
  3. Integración de módulos mediante procedimientos descendentes (TOP-DOWN) o ascendentes (BOTTOM-UP)

En lo que resta de tema, abordaremos las técnicas más destacadas que se utilizan en este proceso.

[Arriba]

 


2. Programación y abstracción 

Existen diferentes mecanismos utilizados en el proceso de abstracción lógica aplicada al diseño de software, si bien, pueden contemplarse como evoluciones en el tiempo de una idea originaria común consistente en:

  1. detectar comportamientos comunes
  2. obtener un algoritmo que resuelva el problema 
  3. adecuarlo par su adaptación al mayor número de situaciones posibles 

A continuación pasamos a enumerar dichos mecanismos: 

El diseño modular y la programación estructurada son corrientes que surgen en los años 70 , en el momento en que empieza el auge de los lenguajes de alto nivel, y hacen uso del primer tipo de recurso: procedimientos y funciones para la consecución de sus objetivos.

A partir de aquí el interés principal del diseño de software evoluciona y se desplaza desde la importancia de los algoritmos (enfoque procedimental) como medios para la obtención de resultados, hasta centrarse en la propia naturaleza de los datos u objetos como elementos principales de interés. Se constituye entonces el diseño de software en un proceso de modelización de la realidad a través de los datos como su representación concreta.

[Arriba]

 


3. Diseño modular

El concepto básico del  diseño modular es muy simple. Consiste en dividir un programa en módulos que puedan ser analizados, programados y depurados por separado. La máxima que rige esta filosofía es: Divide y vencerás

La división de un problema en módulos o programas independientes exige que haya un módulo que controle o relacione a todos los demás. Es el denominado módulo base o principal del problema. En C éste sería main(). Todo programa C debe estar constituido por un módulo main() y posiblemente otra serie de módulos secundarios.

ejemplo de descomposición modular

Realmente, la programación modular es un intento por diseñar programas por componentes, de forma que cualquiera de ellos pueda ser sustituido sin afectar al conjunto. 

Las ventajas de la programación modular se pueden resumir en los siguientes puntos:

El inconveniente principal sería en cambio la inexistencia de algoritmos precisos que dirijan o auxilien al programador en la forma más adecuada de realizar la división modular, por lo que la experiencia sería el factor más determinante en estos casos.

Finalmente, podríamos resumir de manera esquemática los objetivos de la programación modular de la siguiente forma:

3.1 Concepto de módulo

Un módulo está constituido por una o varias instrucciones físicamente contiguas y lógicamente encadenadas, las cuales se pueden referenciar mediante un nombre y pueden ser llamadas desde diferentes puntos de un programa.

Se denominan parámetros a una serie de datos utilizados como vía de comunicación entre módulos. Puede haber varios tipos:

Un módulo de tipo función  también devuelve un dato mediante una instrucción explícita  (return) como consecuencia de su ejecución.

Por otra parte, la salida del módulo debe ser exclusivamente función de sus entradas, y no de ningún dato externo ya que es estado de aquel será impredecible y cambiante según el contexto en que se encuentre la invocación al módulo. Son los llamados: "efectos laterales".

Esencialmente, el módulo debe ser una caja negra de la cual sólo sean accesibles desde el exterior sus entradas y sus salidas.

Además, en la creación de módulos deben cumplirse los siguientes aspectos:

3.2 Técnicas de la programación modular

Existen distintas técnicas par enfocar el diseño modular, pero todas ellas tienen en común el siguiente guión del proceso:

  1. estudio de las especificaciones del problema (análisis general)

  2. establecimiento del organigrama modular, que es un esquema de bloques que muestra la descomposición del problema y la comunicación entre el módulo principal y los secundarios

  3. confección del organigrama o pseudocódigo de cada módulo

  4. codificación de cada módulo en el lenguaje elegido

  5. pruebas parciales de cada componente

  6. prueba final de los módulos enlazados

Si observamos este guión, veremos que es una adaptación del diseño del ciclo de vida clásico, pero en el que se divide en problema en módulos para, al final, volver a componer con ellos una unidad.

En el sencillo ejemplo mostrado arriba, cada módulo "hoja" constituye una unidad de programación independiente, que será posteriormente enlazada al resto para la resolución del problema

[Arriba]


4. Programación estructurada

En el apartado anterior se ha visto cómo la programación modular es una filosofía de la programación en la que se dan una serie de consejos para la realización de programas por descomposición en módulos. 

Sin embargo, para programar cada módulo individual es necesario aplicar también técnicas que nos faciliten la labor y nos aseguren al máximo la corrección y adecuación del producto final. Los módulos deben estar dotados de una estructura interna sólida y se deben minimizar al máximo los errores humanos.

Este es el objetivo de la programación estructurada, presentándose por tanto como el complemento ideal al diseño modular. Es difícil dar una definición de lo que es en sí pero podríamos hablar de una:

Técnica de construcción de programas que utilizan al máximo los recursos del lenguaje, limitan el número de estructuras aplicables a la construcción de algoritmos y presenta una serie de reglas que coordinan el desarrollo de las diferentes fases de la programación.

Si bien la definición en sí es un poco abstracta, lo que sí podemos concretar son los siguientes conceptos, presentes en todas las obras de teóricos de la programación al respecto (Dijkstra, Hoare, Knuth...):

4.1 Estructuras básicas de control

Existe un teorema enunciado por Bohm y Jacopini en 1966 denominado teorema de la estructura que dice lo siguiente:

Un programa propio puede ser escrito utilizando solamente tres tipos de estructuras de control, siendo éstas las siguientes:

Además, se considera un programa propio a aquel que cumple las siguientes características:

La asunción del teorema anterior llevó a Dijkstra a enunciar la siguiente sentencia: 

La estructura GOTO es perjudicial para la programación

El motivo de esta afirmación fue que se había demostrado por una parte que no era necesaria, y por otra, que propiciaba malos hábitos de programación. Pero en Ensamblador y código máquina sigue siendo imprescindible.

Casi todos los lenguajes de alto nivel actuales la conservan, aunque su uso está muy restringido (y mal visto).

Sobre las estructuras de control básicas permitidas, no cabe ningún comentario, pues han sido ya convenientemente tratadas en los temas anteriores.

4.2 Recursos Abstractos

El proceso de obtención de diferentes pasos hasta encontrar la solución de un problema es un proceso abstracto.

Diseñar o concebir un problema en términos abstractos consiste en no tener en cuenta la máquina que lo va a resolver, ni el lenguaje de programación que se va a utilizar.

En este punto, por tanto, se insiste en la necesidad del diseño de un algoritmo independiente como paso previo a la codificación en un lenguaje concreto, por lo que el trabajo realizado en esta fase de abstracción, podría amortizarse también al trasladarlo a diferentes implementaciones concretas sin necesidad de reiniciar el proceso completo en cada caso.

Para realizar este diseño abstracto, se pueden utilizar recursos tradicionales tales como organigramas, pseudocódigo, tablas de decisión, etc. Pero sobre todo, aparece un nuevo concepto de diseño que es el presentado a continuación:

4.3 Metodología descendente: top-down

La metodología o diseño descendente también conocida como diseño top-down consiste en refinar un módulo de programa en niveles de menor a mayor complejidad que den solución al problema.

Si nos fijamos, consiste en llevar los principios macroscópicos del diseño modular de programas hasta el interior de que cada módulo, terminando el proceso al llegar a las instrucciones simples.

Cuando se considera una solución modular para cualquier problema, pueden formularse muchos niveles de abstracción. En el nivel superior de abstracción, se establece una solución en términos amplios. En los niveles inferiores de abstracción se toma una orientación más procedimental.  Por último, en el nivel más bajo de abstracción, se
establece la solución de forma que pueda implementarse directamente. 

Esencialmente consiste en crear una estructura jerárquica de tipo arbórea, en la que los niveles superiores próximos a la raíz enuncian el problema a groso modo, mientras que según vamos descendiendo por la estructura, vamos entrando en mayor detalle, hasta llegar a las hojas del árbol, que representan las instrucciones simples en que se descompone el problema.

4.4 Ejemplo de diseño top-down

A continuación vamos a desarrollar un ejemplo de esto. Supongamos que debemos programar un módulo que controle el flujo de información y documentos de un almacén. Lo primero que hacemos es descomponer la gestión en las tres tareas básicas del almacén:

A continuación, profundizaríamos en las subtareas en que se descomponen las anteriores, y así sucesivamente hasta llegar al nivel de detalle de una acción concreta:

compras:
  • buscar proveedor
  • conseguir crédito
  • gestionar transporte

 

manejo:
  • almacenamiento
  • inventario
  • etiquetado

 

ventas:
  • facturación
  • reparto

 

 

ejemplo de diseño top-down de gestión de almacén

existen distintas metodologías: Jackson, Bertini, Warnier, de reconocido prestigio, aunque ya un poco en desuso. A modo de ilustración, veremos como se representaría este ejemplo utilizando la metodología Warnier:

ejemplo de almacen en metodología Warnier


[Arriba]


5. Librerías de funciones

Las librerías de funciones son elementos que si bien ya se encontraban presentes en los primeros tiempos de la programación de alto nivel (Fortran), adquieren su máxima dimensión en relación con la corriente de diseño modular y la programación estructurada de los años 70.

Una librería es una colección de funciones que se encuentran recogidas en un archivo común, y que puede ser enlazada desde cualquier programa.

La colección se construye con una cierta cohesión interna: hay librerías gráficas, matemáticas, estadísticas, lógicas, etc.

Las librerías se construyen con un enfoque diferente aunque complementario al del diseño top-down: su objetivo es ampliar las funcionalidades de un determinado lenguaje con extensiones. No se trata de partir de un problema particular y descomponerlo en módulos, sino de construir módulos con la suficiente independencia del contexto como para poder ser invocados en diferentes situaciones.

A la utilización de estos módulos para la creación de problemas complejos se le suele llamar también diseño bottom-up.

Antes de programar nuevos algoritmos, es un buen consejo el de "bucear" en las librerías que acompañan (o amplían) a un traductor para evitar realizar un esfuerzo en algo que ya está hecho, pues de lo contrario contradeciríamos el principio de reusabilidad. 

Esta idea será posteriormente recogida en la programación objetos como base para la creación de "catálogos de objetos" reutilizables.

[Arriba]


[Anterior] [Índice General] [Siguiente]