Unidad 7: Heurísticas del Diseño

7.0 Introducción

En este capítulo, desarrollaremos algunas heurísticas con las cuales las estructuras de sistemas pueden mejorarse. Entenderemos por heurísticas, ciertos "trucos", los cuales nos permitirán incrementar la modularidad del sistema. Las heurísticas no son reglas rígidas. Son útiles porque sirven como indicadores para examinar y verificar las estructuras, para potenciales mejoras.

7.1 Tamaño de Módulo

El tamaño del módulo está relacionado con la modularidad, más no solo de manera simple de "cortar en programa en más piezas". Esto es, la modularidad técnica no se incrementa simplemente cuando el tamaño de módulo decrece, mientras otros aspectos permanecen igual.

Para la mayoría de los propósitos, módulos de mucho más de cien sentencias, están fuera del tamaño óptimo con respecto a la economía de detección y corrección de errores. En el otro extremo de la escala el límite es menos obvio. Excepto para ocasionales programadores "descarrilados", que piensan que modularidad es equivalente a partir un programa en módulos de una sentencia, encontraremos que los módulos muy pequeños, han sido diseñados concisa y deliberadamente, y usualmente por razones funcionales. Sin embargo, normalmente, módulos menores a cinco a nueve líneas, deben considerarse para un replanteo. Esto es especialmente relevante cuando existen muchos de estos módulos muy pequeños en el sistema.

Sugerencias para el tamaño óptimo de un módulo han provenido de diferentes fuentes en el transcurso de los años. Una de las sugerencias conocidas es la de Baker quién recomienda que un módulo debe consistir de aproximadamente cincuenta líneas, lo que coincide con el número de líneas que se pueden poner en una hoja de listado común.

Otra recomendación proviene de Weinberg, cuyos estudios muestran que la comprensión de un programador se disipa fácilmente si el módulo tiene más de treinta líneas.

No siempre módulo muy grandes o muy pequeños son necesariamente malos. La cuestión importante es que los módulos reflejen la estructura del problema lo más fielmente posible. Descomponer un módulo simplemente para llevarlo a un tamaño óptimo, aún perdiendo su significado con respecto a la estructura del problema, no es una buena estrategia.

Debemos examinar separadamente cada caso de módulos muy grandes o muy pequeños.

Un módulo demasiado grande a menudo puede deberse a dos razones principales:

Una factorización incompleta en módulos subordinados apropiados
Dos o más funciones han sido combinadas (frecuentemente con cohesión lógica) en un mismo módulo.

En el primer caso, se puede realizar una reducción a través de la identificación y extracción de subfunciones.

fig7-1.jpg (5080 bytes)

En el segundo caso, podemos descomponer el módulo en sus funciones constitutivas.

fig7-2.jpg (5527 bytes)

En cualquier caso, los diagramas de estructura pueden usarse como herramienta, y las modificaciones estructurales deben probarse sobre papel.

Recalcamos que lo importe es dar sentido a la nueva estructura dentro del contexto del problema. Si no es posible dar una interpretación razonable a la nueva estructura, debe mantenerse la original.

Cuando tratamos con módulos muy pequeños, debemos distinguir dos casos:

el módulo atómico (nivel más bajo)
módulo no atómico

En el caso de módulos atómicos, las cuestiones a considerar son: la cantidad de llamadas desde módulos superiores que recibe el módulo (fan-in), y la sobrecarga (overhead) producido por el proceso de llamada a subrutina.

Un módulo muy pequeño puede ser comprimido en sus módulos superiores. Cuando el módulo tiene un uso simple (fan-in = 1 ) esta es una alternativa a considerar. Pero si el módulo tiene usos múltiples (alto fan-in) puede ser muy peligroso absorverlo en sus módulos superiores, porque se duplicará el esfuerzo de implementación y mantenimiento del mismo.

En el caso de que la sobrecarga producida por el mecanismo de llamada a subrutina sea intolerable, se puede testear y depurar el módulo independientemente y luego utilizar la inclusión en línea del módulo subordinado en sus superiores (ligado en tiempo de compilación). La mayoría de los lenguajes dan facilidades para esto como ser el COPY del Cobol, #include del C, %INCLUDE del PL/I, las macros de la mayoría de los assembler., etc., o utilizar facilidades de los preprocesadores de compilación.

Cuando el módulo es no atómico, el análisis es más complicado. Las opciones son comprimir el módulo hacia arriba en su superior, o hacia abajo en un subordinado. Ambas opciones deben ser consideradas.

Un caso especial es el llamado módulo fantasma, un módulo que lo único que contiene son llamadas a otros módulos subordinados. Obviamente estos módulos son el caso límite de módulos muy pequeños.

A través de esta discusión, se asumió que el diseñador conoce previo a la implementación el tamaño que tendrán los módulos. Normalmente esto no requiere un proceso de estimación sustancial. La experiencia ha demostrado que el tamaño comparativamente pequeño de módulos en sistemas razonablemente modulares simplifica el proceso de estimación. En adición, el proceso de estimación se torna más exacto cuando el diseñador tiene experiencia con la función que realizarán varios módulos.

7.2 Amplitud del Control (Fan-out)

La amplitud del control o ancho de salida (fan-out), es el número de subordinados inmediatos de un módulo. Al igual que en con el tamaño de módulo, amplitudes de control muy altas o muy bajas, son indicadores de un posible diseño pobre.

Ya se ha visto anteriormente que la función de un administrador se torna muy compleja si el número de subordinados excede 7± 2 subordinados inmediatos. En general deben verificarse anchos de salida mayores a 10 y aquellos de solamente 1 o 2. También observaremos que una amplitud de control muy alta es más peligroso que una baja.

Una amplitud de control demasiado baja puede incrementarse en la mayoría de los casos descomponiendo el módulo en subfunciones subordinadas adicionales, o comprimiendo el módulo en sus superiores. Como se dijo en el punto anterior, esto es válido si se puede dar a los nuevos módulos un sentido dentro de la estructura del problema.

Una amplitud de control demasiado alta puede ser un indicativo de una reticencia por parte del diseñador a delegar responsabilidades en módulos subordinados. Sin embargo en la mayoría de los casos, esto proviene de estructuras "panqueque" o fallas en la definición de niveles intermedios. Para solucionar este problema, debe considerarse la posibilidad de agruparse varios subordinados en una función combinada. El principio de cohesión debe guiar este proceso para evitar módulos de baja cohesión.

7.3 Ancho de Entrada (Fan-In)

Siempre que sea posible, desearemos maximizar el ancho de entrada de un módulo (fan-in) durante el proceso de diseño. Cada instancia de múltiples entradas de un módulo, representa que ha podido evitarse duplicidad de código.

Sin embargo, un gran ancho de entrada no es algo que deba buscarse a cualquier costo. Por ejemplo, es ridículo combinar muchas funciones "no relacionadas" en un módulo bajamente cohesivo, con el propósito de incrementar el fan-in.

Un gran ancho de entrada es alcanzado a través de un proceso analítico que acompaña los pasos del proceso de diseño estructurado. Cada vez que vamos a dibujar un nuevo módulo en el diagrama de estructura, primero debemos verificar si no existe ya algún otro módulo que realice la función requerida. Si es así, dibujaremos una flecha hacia la caja del módulo ya existente en el diagrama, en lugar de dibujar una nueva. En orden de evitar un "enriedo" de flechas en el diagrama, pueden utilizarse conectores dentro de la página (un círculo) y fuera de página (un polígono), similarmente a la que se hace en los diagramas de flujo.

Es importante notar que la especificación del fan-in es una tarea del diseñador, no del implementador (codificador).

Un problema puede producirse cuando el nuevo módulo es parecido pero no exactamente igual al módulo existente. Si el diseñador no distingue la diferencia subyacente entre ambos módulos, pueden generarse problemas durante la integración del sistema. Podrán fallar el nuevo módulo, el anterior uso del módulo existente.

La clave está en comprender cual es la parte común de ambos módulos, y aislarlo en un nuevo módulo. Por ejemplo, supongamos que tenemos una función Q1 que parece ser similar a Q2. Si Q es la parte común de ambas funciones, podemos reestructurar nuestro diseño de la siguiente manera:

fig7-3.jpg (7620 bytes)

Las porciones remanentes Q1’ y Q2’ pueden reestructurarse absorviéndolas en sus módulos superiores si fueran pequeñas. Podemos tener así una serie de alternativas diferentes.

fig7-4.jpg (6428 bytes)

7.4 Alcance de Efecto / Alcance de Control

Cada decisión o sentencia condicional (if-then-else) en un sistema tiene algunas consecuencias: ciertos procesos se realizarán o no como resultado de una decisión. Equivalentemente podemos decir que cierto procesamiento es condicional en base a la salida o resultado de una decisión. Es importante aprender donde se encuentran los resultados de una determinada decisión, dentro de una estructura modular. En orden de este estudio, introduciremos dos términos nuevos: alcance de efecto y alcance de control.

El alcance de efecto de una decisión es la colección de todos los módulos que contienen algún procesamiento que está condicionado por dicha decisión.

Aunque solo una pequeña parte del procesamiento de un módulo se vea afectada por la decisión. Si la activación de un módulo completo está condicionada por la decisión, el módulo superior (invocador) también se incluye dentro del alcance de efecto.

El alcance de control de un módulo es el módulo mismo y todos sus subordinados.

El alcance de control es un parámetro puramente estructural independiente de las funciones del módulo.

Estamos en condiciones ahora de plantear la siguiente heurística de diseño que involucra al alcance de efecto y al alcance de control:

Para una decisión dada, el alcance de efecto debe ser un subconjunto del alcance de control del módulo en el cual se encuentra la decisión.

En otras palabras, todos los módulos que son afectados o influenciados por una determinada decisión deben estar subordinados finalmente al módulo que toma la decisión. La toma de decisiones y la estructura modular se interrelacionan mejor cuando las decisiones se toman a un nivel no más alto que lo necesario dentro de estructura jerárquica, con el objetivo de ubicar el alcance de efecto dentro del alcance de control. En el caso ideal, el alcance de efecto debe estar limitado al módulo en el cual se realiza la decisión y a aquellos módulos inmediatamente subordinados al mismo.

 

Examinemos los siguientes cuatro casos:

fig7-5.jpg (6678 bytes) fig7-6.jpg (7151 bytes)
fig7-7.jpg (7439 bytes) fig7-8.jpg (7976 bytes)

 

El alcance de efecto de la decisión se representa por los módulos sombreados. La Fig.1 ilustra un caso en el cual el alcance de efecto no es un subconjunto del alcance del control.

La Fig.2 muestra una estructura en la cual el alcance de efecto está contenido dentro del alcance del control, pero puede discutirse que la decisión es realizada demasiado alta dentro de la jerarquía. En la Fig. 3 se ha llevado la decisión al nivel justo para incluir el alcance de efecto dentro del alcance de control. La Fig. 4 muestra el caso ideal en el los módulos a quienes afecta la decisión son subordinados inmediatos del módulo que toma la decisión.

7.5 Conclusión

Debemos destacar que lo visto en este capítulo son reglas prácticas y no reglas "religiosas". Heurísticas tales como tamaño del módulo, ancho de entrada, ancho de control, alcance de efecto/control, son sumamente valiosas si se utilizan apropiadamente, pero pueden llevar a malos resultados pobres si se aplican literalmente.

 

WB01343_.gif (599 bytes) WB01344_.gif (1348 bytes) WB01345_.gif (616 bytes)