Diseño de un programa
Una vez que los requisitos de un programa han sido establecidos, ya se puede iniciar la fase de diseño. En esta etapa se tiene que encontrar una solución informática al problema planteado. Dicha solución determinará cómo se va a resolver el problema.
Por norma general, no suele ser fácil encontrar una solución óptima y, menos aún, cuando el problema tiene cierta envergadura. Para encontrar dicha solución, el desarrollador puede hacer uso del diseño modular.
Diseño modular
Dado un problema a resolver, en primer lugar hay que estudiar si existe la posibilidad de dividirlo en otros más pequeños, llamados subproblemas (este método es conocido como "divide y vencerás"). Cada uno de ellos puede tratarse de manera aislada; por tanto, la complejidad global del problema disminuirá considerablemente. Del mismo modo, si los subproblemas obtenidos siguen siendo demasiado complicados, también puede ser conveniente fragmentarlos. Y así sucesivamente, hasta llegar a subproblemas realmente sencillos.
EJEMPLO Suponiendo que se quiere desarrollar una aplicación para gestionar el alquiler de películas de un videoclub, y que en su ERS se detalla la manera en que dicho software tratará los datos de los socios (nombre, apellidos, dirección, etc.), de las películas (título, director, duración, etc.) y de los alquileres (fecha de préstamo, fecha de devolución, etc.), en este caso, el problema principal se puede descomponer en otros tres más pequeños: gestión de socios, gestión de películas y gestión de alquileres. Cada uno de los cuales, también puede ser fragmentado. Por ejemplo, la gestión de películas se podría descomponer en: altas, bajas, modificaciones y consultas. Y así sucesivamente, mientras se considere oportuno.
A cada subproblema se le considera parte o módulo del problema global, y cada uno de ellos se resolverá por medio de un programa o subprograma.
Gráficamente, el proceso descrito se puede representar de la siguiente forma:
Dependiendo de las características de cada problema y subproblemas derivados, se alcanzará un nivel de descomposición diferente. A la persona que realice el análisis, dichas características le servirán para abstraer lo máximo posible el problema y subproblemas a resolver. La abstracción permite considerar por separado cada subproblema, aislándolo de los demás.
Los módulos se interrelacionan entre sí, ya que, cada uno de ellos tendrá un comportamiento que afectará al que esté por encima o por debajo de él. Sin embargo, la manera en que cada uno de los módulos realice sus tareas no será visible al resto de los módulos, a esto se le conoce como encapsulación.
En resumen, la solución a un problema suele venir dada por un programa representado por un módulo principal, el cual se descompone en subprogramas (submódulos), los cuales, a su vez, también se pueden fraccionar, y así sucesivamente, es decir, el problema se resuelve de arriba hacia abajo. A este método se le denomina diseño modular o descendente (top-down).
Qué es un algoritmo
En la fase de codificación, todos los módulos definidos mediante el diseño modular se convertirán en un programa, es decir, la aplicación final estará compuesta por la suma de todos los programas que se diseñen. Pero antes, hay que determinar cuáles son las instrucciones o acciones de cada uno de dichos programas. Para ello, se debe hacer uso de algoritmos.
Un algoritmo establece, de manera genérica e informal, la secuencia de pasos o acciones que resuelve un determinado problema. Los algoritmos constituyen la documentación principal que se necesita para poder iniciar la fase de codificación y, para representarlos, se utiliza, fundamentalmente, dos tipos de notación: pseudocódigo y diagramas de flujo. El diseño de un algoritmo es independiente del lenguaje que después se vaya a utilizar para codificarlo.
Pseudocódigo
El pseudocódigo es un lenguaje de programación algorítmico; es un lenguaje intermedio entre el lenguaje natural y cualquier lenguaje de programación específico, como son: C, FORTRAN, Pascal, etc. No existe una notación formal o estándar de pseudocódigo, sino que, cada programador puede utilizar la suya propia. Ahora bien, en los algoritmos de ejemplo de este tutorial y otros de Abrirllave, la mayoría de las palabras que se utilizan son una traducción literal –del inglés al castellano– de las palabras que se usan en lenguaje C para escribir las instrucciones de los programas. Por tanto, el pseudocódigo empleado en este tutorial es, mayormente, "C En Español (CEE)". Se pretende de esta forma facilitar al estudiante la codificación posterior de los algoritmos de los ejemplos al lenguaje C. La codificación de un algoritmo consiste en traducirlo a un lenguaje de programación específico, C en nuestro caso.
EJEMPLO Dadas las especificaciones del ejemplo del apartado anterior (Análisis) para un programa que va a calcular la suma de dos números enteros cualesquiera introducidos por el usuario y, después, va a mostrar por pantalla el resultado obtenido, puesto que se trata de un problema muy sencillo, en este caso, no es necesario descomponerlo en otros más pequeños. En consecuencia, el programa resultante estará compuesto de un solo módulo. El algoritmo de dicho módulo, escrito en pseudocódigo CEE, puede ser el siguiente:
algoritmo Sumar
variables
entero a, b, c
inicio
escribir( "Introduzca el primer número (entero): " )
leer( a )
escribir( "Introduzca el segundo número (entero): " )
leer( b )
c ← a + b
escribir( "La suma es: ", c )
fin
Un algoritmo escrito en pseudocódigo siempre se suele organizar en tres secciones: cabecera, declaraciones y cuerpo. En la sección de cabecera se escribe el nombre del algoritmo, en este caso Sumar. En la sección de declaraciones se declaran algunos de los objetos que va a utilizar el programa. En el tutorial de lenguaje C de Abrirllave se estudian en detalle los distintos tipos de objetos que pueden ser utilizados en un programa, tales como: variables, constantes, subprogramas, etc. De momento, obsérvese que, en este ejemplo, las variables a, b y c indican que el programa necesita tres espacios en la memoria principal de la computadora para guardar tres números enteros. Cada una de las variables hace referencia a un espacio de memoria diferente.
En el cuerpo están descritas todas las acciones que se tienen que llevar a cabo en el programa, y siempre se escriben entre las palabras inicio y fin. La primera acción:
escribir( "Introduzca el primer número (entero): " )
Indica que se debe mostrar por pantalla el mensaje que hay entre comillas dobles. Después, mediante la acción:
leer( a )
Se está indicando que el programa esperará a que el usuario teclee un número entero, el cual se almacenará en el espacio de memoria representado por la variable a. El mismo proceso se tiene que seguir con el segundo número, que se guardará en el espacio de memoria representado por la variable b.
escribir( "Introduzca el segundo número (entero): " )
leer( b )
Acto seguido, la acción:
c ← a + b
Indica que en el espacio de memoria representado por la variable c se debe almacenar la suma de los dos números introducidos por el usuario del programa. Para terminar, el resultado de la suma se mostrará por pantalla con la acción:
escribir( "La suma es: ", c )
Diagramas de flujo (Ordinogramas)
Los algoritmos también se pueden representar, gráficamente, por medio de diagramas de flujo. Los diagramas de flujo se pueden utilizar con otros fines, sin embargo, en este tutorial solamente los vamos a emplear para representar algoritmos. A tales diagramas de flujo también se les conoce como ordinogramas. Dicho de otra forma, un ordinograma representa, de manera gráfica, el orden de los pasos o acciones de un algoritmo. Por ejemplo, el algoritmo Sumar escrito en pseudocódigo para el problema del ejemplo de apartado anterior (Análisis) se puede representar mediante el siguiente ordinograma:
El pseudocódigo y los diagramas de flujo son las dos herramientas más utilizadas para diseñar algoritmos en programación estructurada. Si bien, entre ambos tipos de representación existen las siguientes diferencias importantes:
- Los diagramas de flujo empezaron a utilizarse antes que el pseudocódigo.
- En pseudocódigo se suelen definir tres secciones del algoritmo (cabecera, declaraciones y cuerpo). Sin embargo, en un ordinograma sólo se representa el cuerpo.
- En un ordinograma suele ser más fácil ver, a primera vista, cuál es el orden de las acciones del algoritmo.
- Los símbolos gráficos utilizados en un diagrama de flujo han sido estandarizados por el American National Standards Institute (ANSI). Sin embargo, no existe un "pseudocódigo estándar".
A continuación, se muestran los símbolos gráficos más utilizados para diseñar ordinogramas.
Cualidades de un algoritmo
Para cualquier problema dado no existe una única solución algorítmica; es tarea de la persona que diseña un algoritmo encontrar la solución más optima, ésta no es otra que aquella que cumple más fielmente las cualidades deseables de todo algoritmo bien diseñado:
- Finitud. Un algoritmo siempre tiene que finalizar tras un número finito de acciones. Cuando el algoritmo Sumar sea ya un programa, la ejecución de éste siempre será la misma, ya que, siempre se seguirán las acciones descritas en el cuerpo del algoritmo, una por una, desde la primera hasta la última y en el orden establecido.
- Precisión. Todas las acciones de un algoritmo deben estar bien definidas, esto es, ninguna acción puede ser ambigua, sino que cada una de ellas solamente se debe poder interpretar de una única manera. Dicho de otra forma, si el programa que resulta de un algoritmo se ejecuta varias veces con los mismos datos de entrada, en todos los casos se obtendrán los mismos datos de salida.
- Claridad. Lo normal es que un problema se pueda resolver de distintas formas. Por tanto, una de las tareas más importantes del diseñador de un algoritmo es encontrar la solución más legible, es decir, aquella más comprensible para el ser humano.
- Generalidad. Un algoritmo debe resolver problemas generales. Por ejemplo, el programa Sumar deberá servir para realizar sumas de dos números enteros cualesquiera, y no, solamente, para sumar dos números determinados, como pueden ser el 3 y el 5.
- Eficiencia. La ejecución del programa resultante de codificar un algoritmo deberá consumir lo menos posible los recursos disponibles del ordenador (memoria, tiempo de CPU, etc.).
- Sencillez. A veces, encontrar la solución algorítmica más eficiente a un problema puede llevar a escribir un algoritmo muy complejo, afectando a la claridad del mismo. Por tanto, hay que intentar que la solución sea sencilla, aun a costa de perder un poco de eficiencia, es decir, se tiene que buscar un equilibrio entre la claridad y la eficiencia. Escribir algoritmos sencillos, claros y eficientes se consigue a base de práctica.
- Modularidad. Nunca hay que olvidarse del hecho de que un algoritmo puede formar parte de la solución a un problema mayor. Pero, a su vez, dicho algoritmo debe descomponerse en otros, siempre y cuando, esto favorezca a la claridad del mismo.
La persona que diseña un algoritmo debe ser consciente de que todas las propiedades de un algoritmo se transmitirán al programa resultante.