1. Introducción

Modularizar es una estrategia de resolución de problemas y de ingeniería de software que consiste en dividir el problema original en un conjunto de subproblemas . Consiste en aplicar una estrategia heurística fundamental: descomposición y recombinación . Esta heurística consiste en poder descomponer un todo en sus partes y poder recombinar las partes en un todo.

En programación y en el diseño de algorítmos, un buen diseño estructurado persigue elaborar algorítmos que cumplan la propiedad de modularidad , esto es dado un problema que se pretende resolver mediante la utilización de una computadora, se busca dividir dicho programa en módulos independientes.

El diseño estructurado incluye la descomposición, para lo cual se requiere un adecuado análisis de dicho problema, siendo necesario definir primeramente el problema. Merece la pena el esfuerzo de dividir un problema grande en subproblemas más pequeños.

Ahora la cuestión es ¿cómo realizar la descomposición?; una manera es realizando un estudio descendente (top-down) que nos lleve desde la concepción del problema (algorítmo, programa…) global hasta identificar sus partes (módulos).

Esta técnica se repite una y otra vez refinando el problema hasta obtener subproblemas suficientemente pequeños, que puedan ser resueltos por módulos que cumplan, en la medida de lo posible, las características deseables en un módulo en el ámbito de la programación. En cada paso del refinamiento, una o varias instrucciones del programa dado, se descomponen en instrucciones más detalladas.

¿Cuándo parar el refinamiento?. Un refinamiento excesivo podría dar lugar a un número tan grande de módulos que haría poco práctica la descomposición. Se tendrán en cuenta este criterio para dejar de descomponer: Cuando el MODULO definido realice una única tarea, lo suficientemente simple y entendible, y no existan subtareas que requieran descomposición.

El uso apropiado de la modularización tiene importantes ventajas:

  1. Cada módulo debe hacer una única cosa (y de manera genérica).
  2. Cada módulo oculta algo (encapsulamiento).

El uso apropiado de la modularización tiene importantes ventajas:

  1. Facilita el desarrollo del software, dado que los subproblemas con más fáciles de resolver
  2. Facilita la reutilización del software, dado que un módulo se puede usar en muchos programas.
  3. Facilita el mantenimiento. Se puede profundizar en las pruebas de cada módulo más de lo que se hace un programa mayor.

Los módulos se pueden ver como cajas que tiene datos de entrada, que realizan alguna acción y devuelven o no algún valor al respecto.

Para entender claramente en que consiste la modularización, mostraremos un ejemplo introductorio.

2. Parámetros en la Modularización

Si quisiéramos escribir un saludo, y este saludo fuera a utilizarse varias veces podríamos definir un módulo al cual llamaremos saludo en el cual se escriban las líneas necesarias, este módulo se podría utilizar cada vez que queremos saludar. Debemos construir un módulo que haga una única cosa en forma genérica (saluda) pero no nos interesa cómo realiza ese saludo, si es formal o informal,

Entonces nuestro módulo saludo() mostrará el texto ’Hola, buenos días!’ y en la línea siguiente el texto ’cómo le va?’, ahora lo podemos visualizar en el pseudocódigo.

La primera línea del pseudocódigo nos indica que este módulo realiza una acción pero no debemos esperar que devuelva nada, esto se detalla en la parte RETORNA Ø.

ALGORITMO saludo() RETORNA ∅
    (* escribe un saludo general *)
    ESCRIBIR(”Hola, buenos días!”)
    ESCRIBIR(”cómo le va?”)
FIN MODULO saludo

Supongamos que queremos saludar a Marcelo, el algorítmo del saludo sería:

MÓDULO saludo() RETORNA ∅
    (* escribe un saludo general *)
    ESCRIBIR(”Hola, buenos días!”+”Marcelo”)
    ESCRIBIR(”cómo le va?”)
FIN MÓDULO saludo 

Este algorítmo no cumple la condición de que sea genérico, Para eso podríamos leer el nombre y así permitir que valga para todas las personas (ejemplo Marcelo, María, Juan, Fernando,…). Podríamos modificarlo de la siguiente manera:

MÓDULO saludo() RETORNA ∅
    (* escribe un saludo de la persona ingresada *)
    ESCRIBIR(”Ingrese el nombre de la persona”)
    LEER(nombre)
    ESCRIBIR(”Hola, buenos días!” + nombre)
    ESCRIBIR(”cómo le va?”)
FIN MÓDULO saludo

Este algorítmo es genérico, pero si se quiere saludar se requiere que la persona que ejecute el algorítmo ingrese el nombre cada vez que se utiliza, lo cual tampoco es muy recomendable, entonces una manera es escribir un módulo que reciba como datos de entrada el nombre y luego salude, el ingreso del nombre se podría hacer en otro lado. Esto es más conveniente ya que por ahí se necesita que el nombre sea ingresado por teclado. Veamos como cambia el encabezado del módulo teniendo en cuenta que el nombre ya se conoce y por lo tanto se pasa como dato de entrada.

MÓDULO saludo(TEXTO nombre) RETORNA ∅
    (* escribe un saludo de la persona ingresada como parámetro *)
    ESCRIBIR(”Hola, buenos días!” + nombre)
    ESCRIBIR(”cómo le va?”)
FIN MÓDULO saludo 

Las palabras encerradas en los paréntesis del encabezado del módulo se las llama parámetros. Los parámetros son variables que los reconoce el algorítmo, y los utiliza para lo que sea necesario. El saludo visto, se podría utilizar para saludar a todas las personas con su nombre. Si hubiera más de un parámetro éstos son colocados dentro de los paréntesis, separados por comas. Es importante definir el tipo de dato de cada parámetro, de la misma manera que se definen los tipos de cualquier variable. De esta manera describimos un módulo que recibe un nombre como parámetro y realiza el saludo con el nombre. El saludo se podría utilizar para saludar a todas las personas con su nombre. Ahora podemos especificar cómo se utiliza este módulo, lo que se conoce como invocación de un módulo.

3. Invocación de los Módulos

Supongamos que queremos saludar a varias personas, podríamos utilizar el mismo módulo con cada saludo, lo que significa que invocaríamos dos veces al mismo módulo. Veamos el saludo de dos hermanos: Juan y Pedro. Entonces estaríamos invocando dos veces al mismo módulo


MÓDULO saludo(TEXTO nombre) RETORNA ∅
    (* escribe un saludo de la persona ingresada como parametro *)
    ESCRIBIR(”Hola, buenos días!” + nombre)
    ESCRIBIR(”¿Cómo le va?”)
FIN MÓDULO saludo ´

ALGORÍTMO saludosVarios() RETORNA ∅
    (* saluda a dos hermanos *)
    TEXTO nombre1, nombre2
    ESCRIBIR(”Ingrese el nombre de la primera persona”)
    LEER(nombre1)
    ESCRIBIR(”Ingrese el nombre de la segunda persona”)
    LEER(nombre2)
    saludo(nombre1)
    saludo(nombre2)
FIN ALGORÍTMO saludosVarios

La modularización es útil porque nos ayuda a organizar ideas y además permite repetir la misma secuencia de pasos las veces que sea necesario, y si el módulo está definido con parámetros, se puede repetir incluso con diferentes valores en sus argumentos todas las veces que sea necesario en un programa. Esta es una traza del algorítmo llamador saludosVarios, y luego de los llamados realizados

saludosVarios()
nombre1 nombre2 salida por pantalla
Juan Pedro Ingrese el nombre de la primera persona
Ingrese el nombre de la segunda persona
Hola, buenos días! Juan
¿Cómo le va?
Hola, buenos días! Pedro
¿Cómo le va?

Primera invocación a: saludo

nombre
Juan

Segunda invocación a: saludo

nombre
Pedro

El algorítmo saludo ejecuta alguna acción sin necesidad de retornar nada, lo que se visualiza en el encabezado del algorítmo; sin embargo muchas veces se necesita el retorno de un valor, como las funciones matemáticas. A este tipo de módulo los denominamos módulos con resultado o también funciones .

4. Devolver un resultado

Los módulos que vimos hasta ahora muestran mensajes, pero a veces necesitamos que calculen un valor. Necesitamos que los módulos se comporten como las funciones matemáticas que conocemos, que se usan para calcular resultados. Por ejemplo queremos poder calcular y = f (x) en nuestros programa. Lo que significa que una función podría devolver un valor para ser utilizado luego. Para ello introduciremos la instrucción RETORNA expresion que indica cuál es el valor que tiene que retornar nuestro módulo. Veamos el pseudocódigo para elevar un número al cuadrado, podemos ver que el módulo lo llamamos MODULO, que su finalización ocurre en la línea FIN MODULO… También vemos que el valor retornado de ese módulo es el valor que acompaña a la directiva RETORNA. Esto quiere decir que este módulo siempre se debe invocar con un valor (la variable deberá recibir un valor) y su devolución también será un nuevo valor

MÓDULO cuadrado(REAL valor) RETORNA REAL 
    (* el módulo calcula el cuadrado de un valor recibido como parámetro*)
    REAL cuad
    cuad ← valor * valor
    RETORNA cuad
FIN MÓDULO cuadrado 

Contar con funciones es de gran utilidad, ya que nos permite ir armando una biblioteca de instrucciones con problemas que vamos resolviendo, y que se pueden reutilizar para resolver nuevos problemas. Sin embargo, más útil que tener una biblioteca donde los resultados se imprimen por pantalla, es retornar el valor calculado. De este modo podremos crear una biblioteca de funciones para que las personas que utilizan esas funciones pueda manipular los resultados a voluntad: los impriman, los usen para realizar cálculos más complejos, etc.

Buena Práctica: Es de buena programación proveer bibliotecas de funciones que puedan ser invocadas por los demás módulos.

5. Mostrar versus Retornar Valores

A continuación se define una función muestraSegundos (horas, minutos, segundos) con tres parámetros (horas, minutos y segundos) que imprime por pantalla la transformaciónn a segundos de una medida de tiempo expresada en horas, minutos y segundos:

MÓDULO enSegundos(ENTERO horas, minutos, segundos) RETORNA Ø 
    (* transforma en segundos una medida de tiempo expresada en horas, minuots y segundos *)
    ENTERO segSal
    segSal ← 3600 * horas + 60 * minutos + segundos
    ESCRIBIR(”Son” + segSal + ” segundos”)
FIN MÓDULO enSegundos

Este algorítmo no es correcto, ya que en vez de utilizar muestraSegundos como un módulo tipo función biblioteca, muestra una salida con el resultado dentro de la función. Si se están realizando cálculos matemáticos es muy posible que interese la solución pero no el mensaje que aparece. Así mostramos cómo debe implementarse la función mejorada y su invocación.

MÓDULO enSegundos(ENTERO horas, minutos, segundos) RETORNA ENTERO 
    (* transforma en segundos una medida de tiempo expresada en horas, minuots y segundos *)
    ENTERO segSal
    segSal ← 3600 * horas + 60 * minutos + segundos
    RETORNA segSal
FIN MÓDULO enSegundos 

Ahora cuando invocamos al algorítmo enSegundos lo deberemos hacer con valores en los parametros y suponiendo que devuelve la cantidad de segundos. Si el objetivo es obtener la diferencia entre dos horarios de un evento podríamos hacer el algorítmo general duracionEvento que llama al módulo enSegundos en dos oportunidades.

ALGORÍTMO duracionEvento () RETORNA ∅
    (* ingresa los horarios de inicio y fin y calcula la duración del evento *)
    ENTERO segInicio, segFin, hr, min, seg
    ESCRIBIR(”Ingrese hora, minuto y segundo de la hora de inicio ”)
    LEER(hr)
    LEER(min)
    LEER(seg)
    segInicio ← enSegundos(hr, min, seg)
    ESCRIBIR(”Ingrese hora, minuto y segundo de la hora de finalización ”)
    LEER(hr)
    LEER(min)
    LEER(seg)
    segFin ← enSegundos(hr, min, seg)
    ESCRIBIR(”La duración del evento fue de ” + segFin-segInicio + ” segundos”)
FIN ALGORÍTMO duracionEvento

Nótese que la llamada al algorítmo enSegundos(, , ) retorna un valor entero que será asignado a la variable segInicio en la primer llamada y a la variable segFin en la segunda llamada. Otro detalle es que en el algorítmo duracionEvento las variables las llamamos hs, mins, y segs, en cambio en la definición del módulo enSegundos los nombres de las variables de entrada son horas, minutos y segundos. Las invocaciones a un módulo pueden ser parte de una expresión. Estas pueden ser parte de una expresión siempre que su valor retornado pueda ser utilizado convenientemente con otros operandos y operadores. En el caso que utilicemos varias funciones, las agruparemos en lo que denominamos librerias, las cuales estarán compuestas generalmente por varias funciones, algunas retornando valores y otras no.

Las trazas de estos algorítmos deben cumplir lo siguiente.

  1. Se deben crear tantas tablas de trazas como módulos sean invocados
  2. Cada tabla ademas de las variables propias del método, incluirá parámetros y el valor retornado
  3. El método invocador, o main, incluirá además una columna etiquetada como ’Salida por Pantalla’
  4. Si una misma función es invocada más de una vez deberemos crear nuevas instancias de la misma tabla de la función

Es importante notar que cuando invocamos a un módulo no es necesario utilizar una variable con el mismo nombre del parámetro de definición del módulo. Si es necesario que los valores sean de tipos compatible. La variable que se utiliza en la invocación como parámetro (hs, mins y segs) se denomina parámetro actual. En cambio la variable que se utiliza como parte de la de definición del módulo (horas, minuots y segundos) se denomina parámetro formal.

No es necesario que el parámetro actual tenga el mismo nombre que el parámetro formal, simplemente que se requiere que sean de tipos compatibles. La cantidad de parámetros puede ser mayor a uno, con lo cual, al invocar al módulo debemos pasar la misma cantidad de parámetros en el orden esperado, y los tipos de los parámetros deben coincidir.

Cuando la cantidad es superior a uno, se separan los mismos utilizando comas. Los algorítmos que creemos pueden utilizar modularización y tienen un solo punto de acceso. El punto de acceso es el algorítmo principal.

¡Importantísimo!

Siempre debemos verificar 3 cosas en la correspondencia entre parámetros actuales y formales: 
  + cantidad de parámetros
  + conformidad de tipos de los parámetros
  + orden de los parámetros

6. Importancia de la Modularización

Ahora que hemos visto distintos conceptos sobre la modularización podemos decir que modular favorece la:

  1. Construcción de algorítmos y programas * En vez de construir un programa muy grande mejor escribir varios programas pequeños. * Permite que los equipos de programadores trabajen en módulos independientes (modularidad en mantenibilidad ISO 9126, ISO/IEC 25010).
  2. Depuración de algorítmos y programas
    • Depurar un programa muy grande es mucho más difícil que depurar varios programas pequeños
    • La modularización aisla los errores.
  3. Lectura de algorítmos y programas
    • Aumenta la legibilidad y comprensión de un programa (usabilidad, inteligibilidad) ISO 9126, ISO/IEC 25010).
    • Un módulo debe ser inteligible a partir de su nombre, los comentarios escritos en su cabecera y los nombres de los módulos que los llaman.
  4. Modicación de algorítmos y programas
    • Un pequeño cambio en los requerimientos de un programa debería implicar sólo un pequeño cambio en pocos módulos (capacidad de modificarlo ISO 9126, ISO/IEC 25010)
    • La modularización aisla las modificaciones.
  5. Eliminación de redundancia de código
    • Localizar operaciones que ocurren en diferentes lugares de un programa
    • El código de una operación aparecerá una sola vez

Modelo de Calidad de Producto de Software

7. Conceptos Importantes

Un módulo puede tener ninguno, uno o más parámetros. En el caso de tener más de uno, se separan por comas tanto en la declaración del módulo como en la invocación. Es altamente recomendable documentar cada módulo que se escribe, para poder saber qué parámetros recibe, qué devuelve y qué hace sin necesidad de leer el código. Los módulos pueden imprimir mensajes para comunicarlos al usuario, y/o devolver valores. Cuando una función realice un cálculo o una operación con sus parámetros, es recomendable que devuelva el resultado en lugar de calcularlo e imprimirlo, permitiendo realizar otras operaciones con ese resultado. No es posible acceder a las variables definidas dentro de un módulo desde el algorítmo principal, si se quiere utilizar algún valor calculado en la función, será necesario devolverlo. Si un módulo retorna algún valor, la ultima primitiva del módulo debe mostrar el valor retornado.

8. Modularización en Java

Hay distintas maneras de definir métodos en Java. Al principio utilizaremos métodos estáticos (static) para implementar subrutinas. Parámetros de entrada: todos los parámetros de tipo primitivo entran por valor (no cambian). Salida de un método: se puede devolver un sólo valor (de tipo primitivo o clase) o ninguno (void). Ejemplo: Definir un algorítmo para que dada una temperatura en grados Fahrenheit retorne la temperatura en grados Celsius. Modularizar de la siguiente forma: * Definir un módulo que reciba como parámetro una temperatura Fahrenheit y retorne la temperatura convertida a Celsius. * El algorítmo prinicipal solicitará la temperatura en grados Fahrenheit, y mostrará la conversion a Celsius invocando al módulo anterior.

MÓDULO pasarFahrCel (REAL grados) RETORNA REAL 
  (* recibe grados Fahrenheit y retorna Celsius *)
  REAL celsius
  celsius ← 5 * (grados-32)/9
  RETORNA celsius
FIN MÓDULO pasarFahrCel ´

ALGORÍTMO conversion() RETORNA ∅
  (* conversor de temperaturas *)
  REAL gradosFahrenheit
  REAL gradosCelsius
  ESCRIBIR (”Ingrese la temperatura en grados fahrenheit”)
  LEER(gradosFahrenheit )
  gradosCelsius = pasarFahrCel(gradosFahrenheit)
  ESCRIBIR(gradosFahrenheit + ”grados Fahrenheit = ” + 
      gradosCelsius+ ” grados Celsius”)
FIN ALGORÍTMO conversion

import java.util.Scanner;
public class farACelsius
{
// Parte principal de programa
public static void main( String[] args )
{//...
  double gradosFahrenheit;
  double gradosCelsius;
  Scanner sc = new Scanner(System.in);
  System.out.println("Ingrese temperatura en grados fahrenheit");
  gradosFahrenheit = sc.nextInt();  
  System.out.println(gradosFahrenheit 
    + "grados Fahrenheit =" +
        gradosCelsius + "grados Celsius");
}
public static double pasarFahrCel(double grados)
{
  //...
  double celsius = 5 * (grados-32)/9;
  return celsius;
}
}

8.1. Funciones Matemáticas

Java ofrece un gran número de funciones matemáticas básicas. La siguiente tabla muestra algunas de ellas: Por ejemplo para calcular el valor absoluto de un número la llamada a la función abs será:

int x = Math.abs( −15);
System.out.println("el valor absoluto de -15 es: " + x);

Lo cual mostrará en pantalla el siguiente mensaje:

el valor absoluto de -15 es: 15.

Por ejemplo para calcular la potencia de un número elevado a otro, ambos leídos por teclado, el código será: ```{r engine = ‘js’, results=‘asis’, echo = T, highlight = T} double x,y; x = 2.0; y = 8.0; System.out.println(“La potencia de” + x + “elevado a” + y + ” es “+ Math.pow(x,y));


Lo cual dará, para una entrada donde x = 2 e y = 8, el siguiente mensaje en pantalla:


```js
La potencia de 2.0 elevado a 8.0 es 256.0