Unidad 4: Acoplamiento

 

4.0 Introducción

Muchos aspectos de la modularización pueden ser comprendidos solo si se examinan módulos en relación con otros. En principio veremos el concepto de independencia. Diremos que dos módulos son totalmente independientes si ambos pueden funcionar completamente sin la presencia del otro. Esto implica que no existen interconexiones entre los módulos, y que se tiene un valor cero en la escala de "dependencia".

En general veremos que a mayor número de interconexiones entre dos módulos, se tiene una menor independencia.

El concepto de independencia funcional es una derivación directa del de modularidad y de los conceptos de abstracción y ocultamiento de la información.

La cuestión aquí es: cuanto debe conocerse acerca de un módulo para poder comprender otro módulo?. Cuanto más debamos conocer acerca del módulo B para poder comprender el módulo A, menos independientes serán A de B.

La simple cantidad de conexiones entre módulos, no es una medida completa de la independencia funcional. La independencia funcional se mide con dos criterios cualitativos: acoplamiento y cohesión. Estudiaremos en principio el primero de ellos.

Módulos altamente "acoplados" estarán unidos por fuertes interconexiones, módulos débilmente acoplados tendrán pocas y débiles interconexiones, en tanto que los módulos "desacoplados" no tendrán interconexiones entre ellos y serán independientes.

El acoplamiento es un concepto abstracto que nos indica el grado de interdependencia entre módulos.

En la práctica podemos materializarlo como la probabilidad de que en la codificación, depuración, o modificación de un determinado módulo, el programador necesite tomar conocimiento acerca de partes de otro módulo. Si dos módulos están fuertemente acoplados, existe una alta probabilidad de que el programador necesite conocer uno de ellos en orden de intentar realizar modificaciones al otro.

Claramente, el costo total del sistema se verá fuertemente influenciado por el grado de acoplamiento entre los módulos.

4.1 Factores que influencian el Acoplamiento

Los cuatro factores principales que influyen en el acoplamiento entre módulos son:

Tipo de conexión entre módulos: los sistemas normalmente conectados, tienen menor acoplamiento que aquellos que tienen conexiones patológicas.
Complejidad de la interface: Esto es aproximadamente igual al número de ítems diferentes pasados (no cantidad de datos). Más ítems, mayor acoplamiento.
Tipo de flujo de información en la conexión: los sistemas con acoplamiento de datos tienen menor acoplamiento que los sistemas con acoplamiento de control, y estos a su vez menos que los que tienen acoplamiento híbrido.
Momento en que se produce el ligado de la Conexión: Conexiones ligadas a referentes fijos en tiempo de ejecución, resultan con menor acoplamiento que cuando el ligado tiene lugar en tiempo de carga, el cual tiene a su ver menor acoplamiento que cuando el ligado se realiza en tiempo de linkage-edición, el cual tiene menos acoplamiento que el que se realiza realiza en tiempo de compilación, todos los que a su vez tiene menos acoplamiento que cuando el ligado se realiza en tiempo de codificación.

 

4.1.1 Tipos de conexiones entre módulos

Una conexión en un programa, es una referencia de un elemento, por nombre, dirección, o identificador de otro elemento.

Una conexión intermodular ocurre cuando el elemento referenciado está en un módulo diferente al del elemento referenciante.

El elemento referenciado define una interface, un límite del módulo, a través del cual fluye datos y control.

La interface puede considerarse como residente en el elemento referenciado. Puede pensarse como un enchufe (socket) donde la conexión del elemento referenciante se inserta.

Toda interface en un módulo representa cosas que deben ser conocidas, comprendidas, y apropiadamente conectadas por los otros módulos del sistema.

Se busca minimizar la complejidad del sistema/módulo, en parte, minimizando el número y complejidad de las interfaces por módulo.

Todo módulo además debe tener al menos una interface para ser definido y vinculado al resto del sistema.

Pero, es una interface de identidad simple suficiente para implementar sistemas que funcionen adecuadamente?. La cuestión aquí es: A que propósito sirven las interfaces?

Solo flujos de control y datos pueden pasarse entre módulos en un sistema de programación. Una interface puede cumplir las siguientes cuatro únicas funciones:

transmitir datos a un módulo como parámetros de entrada
recibir datos desde un módulo como resultados de salida
ser un nombre por el cual ser recibe el control
ser un nombre por el cual ser transmite el control

Un módulo puede ser idetificado y activado por medio de una interfaz de identidad simple. También podemos pasar datos a un módulo sin agregar otras interfaces, haciendo a la interfaz de entrada capaz de aceptar datos como control. Esto requiere que los elementos de datos sean pasados dinámicamente como argumentos (parámetros) como parte de la secuencia de activación, que da el control a un módulo; cualquier referencia estática a datos puede introducir nuevas interfaces.

Se necesita también que la interface de identidad de un módulo sirva para transferir el retorno del control al módulo llamador. Esto puede realizarse haciendo que la transferencia de control desde el llamador sea una transferencia condicional. Debe implementarse además un mecanismo para transmitir datos de retorno desde el módulo llamado hacia el llamador. Puede asociarse un valor a una activación particular del modulo llamado, la cual pueda ser usada contextualmente en el llamador. Tal es el caso de las funciones lógicas. Alternativamente pueden transmitirse parámetros para definir ubicaciones donde el módulo llamado retorna valores al llamador.

Si todas las conexiones de un sistema se restringen a ser completamente parametrizadas (con respecto a sus entradas y salidas), y la transferencia condicional de control a cada módulo se realiza a través de una identidad simple y única, diremos que el sistemas está mínimamente conectado.

Diremos que un sistema está normalmente conectado cuando cumple con las condiciones de mínimamente conectado, excepto por alguna de las siguientes consideraciones:

existe más de un punto de entrada para un mismo módulo
el módulo activador o llamador puede especificar como parte del proceso de activación un punto de retorno que no sea la próxima sentencia en el orden de ejecución.
el control es transferido a un punto de entrada de un módulo por algún mecanismo distinto a una llamada explícita (ej. perform thru del COBOL).

El uso de múltiples puntos de entrada garantiza que existirán más que el número mínimo de interconexiones para el sistema. Por otra parte si cada punto de entrada determina funciones con mínima conexión a otros módulos, el comportamiento del sistema será similar a uno mínimamente interconectado.

De cualquier manera, la presencia de múltiples puntos de entrada a un mismo módulo, puede ser un indicativo de que el módulo está llevando a cabo más de una función específica. Además, es una excelente oportunidad que el programador superpondrá parcialmente el código de las funciones comprendidas dentro del mismo módulo, quedando dichas funciones acopladas por contenido.

De manera similar, los puntos de retorno alternativo son frecuentemente útiles dentro del espíritu de los sistemas normalmente conectados. Esto se da cuando un módulo continuará su ejecución en un punto que depende del valor resultante de una decisión realizada por un módulo subordinado invocado previamente. En un caso de mínima conexión, el módulo subordinado retornará el valor como un parámetro, el cual deberá ser testeado nuevamente en el módulo superior. Sin embargo, el módulo superior puede indicar por algún medio directamente el punto donde debe continuarse la ejecución del programa, (un valor relativo + o - direcciones a partir de la instrucción llamadora, o un parámetro con una dirección explícita).

Si un sistema no está mínima o normalmente conectados, entonces algunos de sus módulos presentarán conexiones patológicas. Esto significa que al menos un módulo tendrá referencias explícitas a identificadores definidos dentro de los límites de otro módulo.

 

4.1.2 Complejidad de la interface

La segunda dimensión del acoplamiento el la complejidad. Cuanto más compleja es una conexión, mayor acoplamiento se tiene. Un módulo con una interface de 100 parámetros generará mayor acoplamiento que un que solo necesite tres parámetros.

El significado de "complejidad" es el de complejidad en términos humanos, tal lo visto anteriormente.

 

4.1.3 Flujo de Información

Otro aspecto importante del acoplamiento tiene que ver con el tipo de información que se transmite entre el módulo superior y subordinado. Distinguiremos tres tipos de flujo de información:

datos
control
híbrido

Los datos son información sobre la cual una pieza de programa opera, manipula, o modifica.

La información de control (aún cuando está representada por variables de dato) es aquella que gobierna como se realizarán las operaciones o manipulaciones sobre los datos.

Diremos que una conexión presenta acoplamiento por datos si la salida de datos del módulo superior es usada como entrada de datos del subordinado. Este tipo de acoplamiento también es conocido como de entrada-salida.

Diremos que una conexión presenta acoplamiento de control si el módulo superior comunica al subordinado información que controlará la ejecución del mismo. Esta información puede pasarse como datos utilizados como señales o "banderas" (flags) o bien como direcciones de memoria para instrucciones de salto condicional (branch-adress). Estos son elementos de control "disfrazados" como datos.

El acoplamiento de datos es mínimo, y ningún sistema puede funcionar sin él.

La comunicación de datos es necesaria para el funcionamiento del sistema, sin embargo, la comunicación de control es una característica no deseable y prescindible, que sin embargo aparece muy frecuentemente en los programas.

Se puede minimizar el acoplamiento si solo se transmiten datos a través de las interfaces del sistema.

El acoplamiento de control abarca todas las formas de conexión que comuniquen elementos de control. Esto no solo involucra transferencia de control (direcciones o banderas), si no que puede involucrar el pasaje de datos que cambia, regula, o sincroniza la ejecución de otro módulo.

Esta forma de acoplamiento de control indirecto o secundario se conoce como coordinación. La coordinación involucra a un módulo en el contexto procedural de otro. Esto puede comprenderse con el siguiente ejemplo: supongamos que el módulo A llama al módulo B suministrándole elementos de datos discretos. La función del módulo B es la de agrupar estos elemento de datos en un ítem compuesto y retornárselo al módulo A (superior). El módulo B enviará al módulo A, señales o banderas indicando que necesita que se le suministre otro ítem elemental, o para indicarle que le está devolviendo el ítem compuesto. Estas banderas serán utilizadas dentro del módulo A para coordinar su funcionamiento y suministrar a B lo requerido.

Cuando un módulo modifica el contenido procedural de otro módulo, decimos que existe acoplamiento híbrido. El acoplamiento híbrido es una modificación de sentencias intermodular. En este caso, para el módulo destino o modificado, el acoplamiento es visto como de control en tanto que para el módulo llamador o modificador es considerado como de datos.

El grado de interdependencia entre dos módulos vinculados con acoplamiento híbrido es muy fuerte. Afortunadamente es una práctica en decadencia y reservada casi con exclusividad a los programadores en assembler.

 

4.1.4 Tiempo de ligado de conexiones intermodulares

"Ligado" o "Binding" es un término comúnmente usado en el campo del procesamiento de datos para referirse a un proceso que resuelve o fija los valores de identificadores dentro de un sistema.

El ligado de variables a valores, o más genéricamente, de identificadores a referentes específicos, puede tener lugar en diferentes estadios o períodos en la evolución del sistema. La historia de tiempo de un sistema puede pensarse como una línea extendiéndose desde el momento de la escritura del código fuente hasta el momento de su ejecución. Dicha línea puede subdividirse en diferentes niveles de refinamiento según distintas combinaciones de computador/lenguaje/compilador/sistema operativo.

De esta forma, el ligado puede tener lugar cuando el programador escribe una sentencia en el editor de código fuente, cuando un módulo es compilado o ensamblado, cuando el código objeto (compilado o ensamblado) es procesado por el "link-editor" o el "link-loader" (generalmente este proceso es el conocido como ligado en la mayoría de los sistemas), cuando el código "imagen-de-memoria" es cargado en la memoria principal, y finalmente cuando el sistema es ejecutado.

La importancia del tiempo de ligado radica en que cuando los valor de variables dentro de una pieza de código son fijados más tarde, el sistema es más fácilmente modificable y adaptable al cambio de requerimientos.

Veamos un ejemplo: supongamos que se nos encomienda la escritura de una serie de programas listadores siendo la impresora a utilizar en principio una del tipo matricial de 80 columnas que funciona con papel continuo de 12" de largo de página.

Alternativas:

  1. Escribimos el literal "72" en todas las rutinas de impresión de todos los programas. (ligado en tiempo de escritura)
  2. Reemplazamos el literal por la constante manifiesta LONG_PAG a la que asignamos el valor "72" en todos los programas (ligado en tiempo de compilación)
  3. Ponemos la constante LONG_PAG en un archivo de inclusión externo a los programas (ligado en tiempo de compilación)
  4. Nuestro lenguaje no permite la declaración de constantes por lo cual definimos una variable global LONG_PAG a la que le asignamos el valor de inicialización "72" (ligado en tiempo de link-edición)
  5. Definimos un archivo de parámetros del sistema con un campo LONG_PAG al cual se le asigna el valor "72". Este valor es leído junto con otros parámetros cuando el sistema se inicia. (ligado en tiempo de ejecución)
  6. Definimos en el archivo de parámetros un registro para cada terminal del sistema y personalizamos el valor del campo LONG_PAG según la impresora que tenga vinculada cada terminal. De esta forma las terminales que tienen impresoras de 12" imprimen 72 líneas por página, y las que tienen una impresora de inyección de tinta que usan papel oficio, imprimen 80. (ligado en tiempo de ejecución)

Examinaremos ahora la relación existente entre el tiempo de ligado y las conexiones intermodulares, y como el mismo afecta el grado de acoplamiento entre módulos.

Nuevamente, una referencia intermodular fijada a un referente u objeto específico en tiempo de definición, tendrá un acoplamiento mayor a una referencia fijada en tiempo de traslación o posterior aún.

La posibilidad de compilación independiente de un módulo de otros facilitará el mantenimiento y modificación del sistema, que si debiera compilarse todos los módulos juntos. Igualmente, si la link-edición de los módulos es diferida hasta el instante previo a su ejecución, la implementación de cambios se verá simplificada.

Existe un caso particular de acoplamiento de módulos derivado de la estructura lexicográfica del programa. Hablamos en este caso de acoplamiento por contenido.

Dos formas de acoplamiento por contenido pueden distinguirse:

Inclusión lexicográfica: se da cuando un módulo está incluido lexicográficamente en otro, y es una forma menor de acoplamiento. Los módulos por lo general no pueden ejecutarse separadamente. Este es el caso en el que el módulo subordinado es activado en línea dentro del contexto del módulo superior.
Solapamiento parcial: es un caso extremo de acoplamiento por contenido. Parte del código de un módulo está en intersección con el otro. Afortunadamente la mayoría de los lenguajes modernos de alto nivel no permiten este tipo de estructuras.

fig4-1.jpg (10840 bytes)

En términos de uso, mantenimiento, y modificación, las consecuencias del acoplamiento por contenido son peores que las del acoplamiento de control. El acoplamiento por contenido hace que los módulos no puedan funcionar uno sin el otro. No ocurre lo mismo en el acoplamiento de control, en el cual un módulo, aunque reciba información de control, puede ser invocado desde diferentes puntos del sistema.

4.2 Acoplamiento de Entorno Común (common-environment coupling)

Siempre que dos o más módulos interactúan con un entorno de datos común, se dice que dichos módulos están en acoplamiento por entorno común.

Ejemplos de entorno común pueden ser áreas de datos globales como la DATA división del COBOL, un archivo en disco.

El acoplamiento de entorno común es una forma de acoplamiento de segundo orden, distinto de los tratados anteriormente. La severidad del acoplamiento dependerá de la cantidad de módulos que acceden simultáneamente al entorno común. En el caso extremo de solo dos módulos donde uno utiliza como entrada los datos generados por el otro hablaremos de un acoplamiento de entrada-salida.

El punto es que el acoplamiento por entorno común no es necesariamente malo y deba ser evitado a toda costa. Por el contrario existen ciertas circunstancias en que es una opción válida.

4.3 Desacoplamiento

El concepto de acoplamiento invita a un concepto recíproco: desacoplamiento. Desacoplamiento es cualquier método sistemático o técnica para hacer más independientes a los módulos de un programa.

Cada tipo de acoplamiento generalmente sugiere un método de desacoplamiento. Por ejemplo, el acoplamiento causado por ligado, puede desacoplarse cambiando los parámetros apropiados tal lo visto en el ejemplo de el contador de líneas de los programas impresores.

El desacoplamiento. desde el punto de vista funcional, rara vez puede realizarse, excepto en los comienzos de la fase del diseño.

Como regla general, una disciplina de diseño que favorezca el acoplamiento de entrada-salida y el acoplamiento de control por sobre el acoplamiento por contenido y el acoplamiento híbrido, y que busque limitar el alcance del acoplamiento por entorno común es el enfoque más efectivo.

Otras técnicas para reducir el acoplamiento son:

Convertir las referencias implícitas en explícitas. Lo que puede verse con mayor facilidad es más fácil de comprender.
Estandarización de las conexiones.
Uso de "buffers" para los elementos comunicados en una conexión. Si un módulo puede ser diseñado desde el comienzo asumiendo que un buffer mediará cada corriente de comunicación, las cuestiones temporización, velocidad, frecuencia, etc., dentro de un módulo no afectarán el diseño de otros.
Localización. Utilizado para reducir el acoplamiento por entorno común. Consiste en dividir el área común en regiones para que los módulos solo tengan acceso a aquellos datos que les son de su estricta incumbencia.

4.4 Una Aplicación

Para clarificar el concepto de acoplamiento veremos una aplicación. Debe escribirse un programa que realizará lo siguiente:

el programa tendrá dos corrientes de entrada: una carácter a carácter desde teclado de una terminal, y la otra registro a registro desde una archivo en disco.
se comienza leyendo los caracteres provenientes de tecla hasta que se recibe el carácter "RETURN", entonces se pasa a leer el archivo registro a registro hasta recibir un registro con "//" en su encabezado, lo cual indica que se vuelve a leer desde teclado.
el paso anterior se realiza iterativamente, hasta que se recibe una señal de fin-de-transmisión desde la terminal (EOT). Entonces se continúa leyendo el archivo hasta el final (EOF).
las corrientes de datos de ambas entradas se analizarán y separarán en palabras las que se pasarán al módulo existente ProcWord, el que realizará algo con ellas.

Se comisiona en primer lugar al programador Carlitos para que confeccione el programa, quién realiza el siguiente diagrama de estructura:

 

fig4-2.jpg (12796 bytes)

Cuando se presenta el problema a la programadora Nadine, ella realiza la siguiente solución:

fig4-3.jpg (10784 bytes)

Ambas estructuras presentan las siguientes características en común:

Ambas son normalmente conectadas
Cada una consiste de 5 módulos y 4 conexiones
La lógica de encontrar palabras ha sido aislada en un módulo específico en ambos casos

Sin embargo analizaremos si ambas estructuras presentan el mismo grado de acoplamiento.

Para evaluar esto, necesitaremos mirar el tipo de información comunicada entre los módulos. Es importante notar que determinados flujos pueden comportarse como de datos y de control. Por ejemplo carácter de salida del módulo INKEY normalmente será un flujo de datos, pero en el caso especial de que el carácter será RETURN este funcionará como un flujo de control. En tales circunstancias es conveniente a efectos del estudio considerarlo como distintos flujos.

 

MODULO ENTRADAS SALIDAS
INKEY   char, EOT, RETURN
READCARD   Registro, EOF, //
FINDWORD char, EOT, RETURN

Registro, EOF, //, fuente

palabra, fin-palabras,

deme-un-carácter,

deme-un-registro,

tome-una-palabra

PROCWORD palabra  
GETCHAR   char, EOT, RETURN
GETCARD   Registro, EOF, //
GETWORD char, EOT, RETURN

Registro, EOF, //

palabra, fin-palabras
PROCWORD palabra  

 

Como se aprecia en la tabla precedente podemos establecer las siguientes comparaciones: el diagrama de Carlitos tiene 13 flujos de control y 6 de datos, en tanto que el diagrama de Nadine tiene 9 flujos de control y 6 de datos. Obviamente el diagrama la estructura de Carlitos presenta un mayor grado de acoplamiento.

Por otro lado la interfaz del módulo FINDWORD de Carlitos será bastante más compleja que la de GETWORD de Nadine debido a la cantidad de parámetros que implica.

 

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