TEMA 7. METODOLOGÍA DE LA PROGRAMACIÓN Una aplicación informática se compone de programas que tienen por objeto la realización automática de una o varias tareas mediante el uso de un sistema informático. El proceso desde el planteamiento del problema hasta que se tiene la solución informática instalada en una o varias computadoras, se denomina ciclo de vida de una aplicación informática. Este se descompone en varias fases: FASES de DISEÑO FASES de INSTALACIÓN Problema Ingenieros Especificación Programa fuente Diplomados/IT Algoritmo Programa objeto Técnicos FP Ejecutable Aplicación • Análisis. Consiste en el examen y descripción detallada de los siguientes aspectos relativos al problema: equipo a utilizar, personal informático necesario, estudio de los datos de entrada, estudio de los resultados que se pretenden obtener, relación entre la entrada y la salida, y descomposición del problema en módulos. Resultado: documentos que componen la especificación del problema. • Programación. Consiste en el diseño de la solución al problema planteado en forma de algoritmo. Resultado: documentos integrados por herramientas de representación de algoritmos como seudocódigo, ordinogramas, etc... • Codificación. Transcripción del algoritmo resultante de la fase anterior a un lenguaje de codificación concreto. Resultado: se denomina programa y antiguamente solía devolverse o generarse un documento en papel denominado hoja de codificación o cuaderno de carga. • Edición. Se escribe el programa fuente a partir de las hojas de codificación en la memoria del ordenador, y se graba en algún soporte permanente. • Compilación. Traducción del programa fuente aún programa objeto o compilado, con programas traductores. • Fase de montaje o linkado. Se añade al programa objeto una serie de rutinas del sistema. Resultado: programa ejecutable directamente en la máquina sin necesidad de traductor. • Prueba de ejecución. Consiste en ejecutar el programa con una serie de conjuntos de datos de prueba para comprobar si el funcionamiento de éste entra dentro de los estándares de calidad fijados. • Explotación y mantenimiento. La explotación consiste en el uso continuo y habitual por parte de los usuarios de la aplicación en un entorno. El mantenimiento de la aplicación consiste en la comprobación del buen funcionamiento de ésta y en la adaptación a cualquier nueva circunstancia que 1 implique su actualización. Según el momento en el que se detectan los errores, podemos clasificarlos en los siguientes tipos: • Errores de compilación. Corresponden al incumplimiento de las reglas sintácticas del lenguaje. Por ejemplo palabras clave mal escritas, falta de delimitadores. Son los mas fáciles de corregir. • Errores de ejecución. Se deben a operaciones no permitidas, como dividir por cero, leer un dato numérico en una variable numérica, etc...Se detectan porque se produce una parada anormal del programa. • Errores de lógica. Corresponden a la obtención por el programa de resultados que no son correctos. Son bastantes difíciles de encontrar, sólo se puede ser ejecutando un número suficiente de juegos de prueba y compararlos con los datos producidos por el programa con los que generaríamos a mano, o bien con una aplicación suficientemente probada. • Errores de especificación. Se deben a la realización de unas especificaciones incorrectas, debidas a la mala comunicación entre el equipo de desarrollo y quien platea el problema. La elección del algoritmo más adecuado se debe basar en una serie de requerimientos de calidad que adquieren gran importancia a la hora de evaluar costes de diseño y mantenimiento, que son los siguientes: • Legibilidad. Claro y sencillo, que resulte fácil su lectura y su compresión. • Fiabilidad. Robusto, es decir, capaz de recuperarse ante errores y usos inadecuados. • Portabilidad. A dos niveles: 1) los algoritmos generados en la fase de programación deben ser implementables en diferentes lenguajes; 2) los códigos fuente obtenidos deben ser independientes de la máquina y del SO. • Modificabilidad. El código ha de ser fácil de mantener, o sea, ha de permitir que se realicen cambios para adaptarlos a nuevas situaciones. • Eficiencia. El programa generado tendrá que hacer un uso correcto y ajustado de los recursos que no son ilimitados. La metodología de la programación es un conjunto de métodos y técnicas disciplinadas que ayudan al desarrollo de unos programas que cumplan los requisitos anteriormente expuestos. Estos objetivos suelen ser la programación modular que consistía en una serie de descomposiciones del problema hasta llegar a un conjunto de módulos implementables. La programación estructurada consiste en el uso exclusivo de las estructuras secuencia, selección e iteración para el control de flujo de las instrucciones. La Documentación de los Programas está constituida principalmente por una serie de anotaciones dentro del propio código fuente y por una serie de documentos que acompañan a la aplicación software. Su fin es facilitar la explotación y el mantenimiento de la aplicación, y debe ser amplia, clara y precisa. Existen dos tipos de documentos: • Interna. Está constituida básicamente por el listado del programa fuente y su objetivo es facilitar la lectura y comprensión del mismo. Se considera parte de la documentación interna los siguientes aspectos: • Comentarios. Son frases explicativas que se colocan en cualquier parte del programa fuente, ignoradas por el compilador. Es conveniente incluir comentarios con aquellos razonamientos que nos hayan sido difíciles de alcanzar y que preveamos que van a ser difíciles de comprender pasado un tiempo. • Código autodocumentado. Las palabras reservadas de los lenguajes de alto nivel se podrían considerar como parte de la documentación por corresponder con términos en inglés que expresan su contenido. 2 • Externa. Es el conjunto de documentos que se acompaña con el programa, pero sin formar parte de los ficheros fuente. La documentación externa debe influir a los siguientes apartados: especificación del análisis, descripción del diseño del programa, de las versiones si las hubiera, del programa principal y los subprogramas, de los manuales del usuario y el anual del mantenimiento. Las técnicas de desarrollo y diseño de programas usadas en la programación convencional tienen inconvenientes sobre todo a la hora de verificar y modificar un programa. Una técnica que se está implantando, para paliar los problemas exteriores al ciclo de vida de la aplicación, es la descomposición de un problema en problemas cada vez más pequeños hasta llegar a módulos implementables, Top−down, en el cual tenemos la programación estructurada. Las técnicas de programación estructurada pretenden resolver los saltos condicionales y los incondicionales, las cuales se pueden definir como la programación sin saltos condicionales e incondicionales. Las características de un programa estructurado son las siguientes: fácil de leer y comprender, fácil de codificar en una amplia variedad de lenguajes y sistemas, fácil de mantener, eficiente (aprovechando al máximo los recursos del sistema) y modularizable. Un diagrama propio es aquel que sólo posee una entrada y una salida. Un programa propio es el que solo tiene un punto de entrada y otro de salida, y donde todo elemento del programa es accesible, es decir, existe al menos un camino de los que van del inicio al fin que pasa por él; es un programa que no tiene bucles infinitos. Dos programas son equivalentes si ante cualquier situación de datos proporcionan el mismo resultado pero de distinta forma. Todo programa propio tiene siempre al menos un programa propio equivalente que sólo utiliza las estructuras básicas de programación, que son la secuencia, selección e iteración. O sea, que diseñando programas con sentencias primitivas y estructuras básicas podremos hacer cualquier trabajo y considerar en mejorar la creación, lectura, comprensión y mantenimiento de los programas. La programación estructurada utiliza: • Diseño Top Down. Los programas se diseñan de lo más genérico a lo más particular por medio de sucesivos refinamientos o descomposiciones que nos llevarán a las instrucciones particulares de cada programa. • Recursos abstractos. Es el complemento perfecto para el diseño Top−Down y, en el cual, utilizamos el concepto de abstracción, es decir, en cada descomposición suponemos que las partes resultantes están resueltas, dejando su realización al siguiente refinamiento y considerando que al final estarán formadas por un conjunto de instrucciones y estructuras básicas. • Estructuras básicas. Toda acción se puede realizar usando las 3 estructuras básicas de control, es decir, la secuencial, alternativa y la repetitiva. • Estructura secuencial. Es una estructura con una entrada y una salida, cuya ejecución es lineal y las acciones se ejecutarán en el orden en el que aparecen. Cada acción sólo tendrá una entrada y una salida. • Estructuras alternativas. Es una estructura con una sola entrada y una sola salida, de la cual se realiza una opción según una condición. Ésta puede ser simple o compuesta. Las alternativas pueden ser de dos tipos: 1ª, dos salidas condicionales, una de las cuales puede ser nula; y 2ª, tres o más salidas. • Estructura repetitiva. Estructura con una entrada y una salida, en la cual se repite un número determinado o indeterminado de veces, dependiendo de una determinada condición. 3 FOR: se repite una acción un determinado número de veces representado normalmente por N. no si WHILE: Se repite una acción mientras que se cumpla la condición que calcula el bucle. La condición es evaluada siempre antes de cada repetición. no si DO/WHILE: Se repite una acción mientras se cumpla la condición que controla el bucle. La condición se evaluará después de cada repetición del bucle. si Un árbol o estructura arborescente es un grafo en el que se distinguen generalmente tres clases de nodos: raíz, nodos intermedios y hojas. La raíz es el nodo del que parte toda la estructura del árbol, es decir, es un nodo que sólo tiene salidas. Los nodos intermedios son los que tienen una entrada o enlace de entrada o al menos una salida o enlace de salida. Las hojas son nodos que se sitúan en la parte más lejana a la raíz y que sólo tienen entradas. Existen tres tipos de ordenar un árbol: • Preorden. Consiste en los siguientes tres pasos: 1º, consultar la raíz; 2º, consultar el subárbol; y 3º, consultar el subárbol derecho. • Inorden. 1º, Recorrer el subárbol izquierdo hasta llegar a una hoja y tratar esa hoja. 2º Consultar la raíz. 3º, recorrer el subárbol derecho hasta llegar a una hoja y tratarla. • Postorden. 1º, recorrer el subárbol izquierdo hasta llegar a una hoja. 2º, recorrer el subárbol derecho hasta llegar a una hoja. 3º, consultar la raíz. El método de Jackson es un método de representación de programas en forma de árbol y que se denomina diagrama arborescente de Jackson. Consta de las siguientes partes: • Definición detallada de los datos de E/S, incluyendo los archivos lógicos utilizados. • Representación del proceso o algoritmo. La simbología utilizada en este método se puede describir como el uso de rectángulos que tienen los siguientes aspectos: Acción Decisión Repetición La lectura del diagrama generado se hace en preorden y la representación de un algoritmo se basa en los siguientes tres puntos: • Un programa se representa por un solo diagrama en el que se incluyen todas las operaciones necesarias para solucionar el problema. La forma para conectar dos páginas es mediante la palabra `proceso' encerrada en un rectángulo y con un número o nombre. 4 • Todo diagrama comienza con un rectángulo en cuyo interior figura el nombre del programa. • Se ha de procurar, para facilitar la lectura, que el diagrama que generemos sea lo más simétrico posible. FOR: en el rectángulo de condición se escribirá o bien en número de veces que se va a repetir el bucle o bien el nombre de la variable contador, su valor inicial, su valor final y el incremento. Ej.: queremos que la condición se repita 5 veces. MIENTRAS/WHILE: en el rectángulo de Condición se escribirá la condición que rige el bucle. HASTA: En el rectángulo de Condición se pondrá `hasta' y la condición. Por ej.: El método de Chapin se trata de una forma de representar programas como un bloque compacto que consta de dos cosas: • Definición detallada de los datos de E/S, incluyendo los archivos lógicos utilizados. • Representación del proceso o algoritmo. Su simbología se basa en el empleo de rectángulos y la lectura del gráfico generado se hace de arriba a abajo. La representación del algoritmo se basa en los siguientes puntos: • Un programa se representa por un solo diagrama en el que se incluyen todas las operaciones a realizar para la resolución del problema. • Todo diagrama comienza en un rectángulo en el que su parte superior y fuera de él figura el nombre del programa. • Estructura secuencial. Son como un conjunto de acciones secuenciales, donde ABCD se ejecutarán de arriba a abajo. • Estructura alternativa. Puede ser: simple o de una opción, doble o múltiple. • Estructura iterativa. FUNDAMENTO DE PROGRAMACIÓN (3) El diseño descendente o toc−down consiste en una serie de descomposiciones sucesivas del problema inicial que reciben un refinamiento progresivo del repertorio de instrucciones que van a formar parte del programa. Sus objetivos son: • Simplificación del problema y de los subprogramas resultantes de cada descomposición. • Programación independiente de cada uno de los módulos, incluso por diferentes personas. • Simplificación de la lectura y el mantenimiento del programa final. Un programa diseñado en Toc−Down tiene dos partes bien diferenciadas: • Programa principal. Describe la solución completa del programa y consta principalmente de llamadas a subprogramas. Estas llamadas son indicaciones al procesador de que debe continuar la ejecución del programa en el comienzo del subprograma llamado, regresando al punto de partida una vez que lo haya concluido. Además, puede contener operaciones e instrucciones primitivas del lenguaje, y sentencias de control ejecutables por el procesador. Debe ser breve. • Subprogramas. Figuran agrupados en un lugar distinto al programa. Su estructura es parecida a la del programa principal, salvo en el encabezamiento y la finalización. Éste puede tomar sus propios subprogramas correspondientes. La función de un subprograma es la de resolver una parte del problema. 5 Éstos se dividen en: • Subprogramas internos. Son aquellos subprogramas que están localizados en el mismo archivo que el programa principal. • Subprogramas externos. Son aquellos que figuran físicamente separados del programa principal, es decir, en distintos ficheros fuente. Se pueden compilar independientemente y también los podemos encontrar en un lenguaje diferente del programa principal. Los objetos manipulados por un programa se clasifican según su porción de programa y/o subprogramas en que son definidos y conocidos. Los objetos globales son los declarados en el programa principal, cuyo ámbito se extiende al mismo y a todos sus subprogramas. Se pueden ver desde cualquier parte del programa. Los objetos locales de un subprograma son los declarados en dicho subprograma, cuyo ámbito está restringido a él mismo y a los subprogramas declarados en él. Sólo se puede ver dentro de él. Cada vez que se llama a un subprograma, los datos de entrada le son pasados por medio de determinadas variables, y análogamente, cuando termina la ejecución los resultados regresan mediante otras o las mismas unidades. Los parámetros pueden ser: • Parámetros formales. Son variables locales de un subprograma utilizadas para la recepción y el envío de los datos. Siempre son fijos. • Parámetros actuales. Son las variables y datos enviados en cada llamada de subprograma por el programa o subprograma llamante. Son los valores y cambian. Se denomina paso de parámetros al proceso de transmisión y recepción de datos y resultados mediante variables de enlace. Puede realizarse de dos modos: • Paso por valor o copia. Cuando pasamos una variable a un subprograma, realmente le pasamos una copia de su valor, conservándose el valor de la variable aunque se modifique dentro del subprograma a la salida de éste. • Paso por referencia o variable. Cuando le enviamos una variable, le pasamos la dirección de ésta. En este caso, el cambio que se produzca en el programa aparecerá reflejado al acabar la ejecución de éste. Un parámetro actual pasado por valor es un dato de entrada al subprograma, y se copia en su parámetro formal correspondiente. No se proporciona la variable al subprograma, sino sólo su contenido. Un parámetro actual pasado por referencia es una variable del programa llamante, para el programa llamado, el cual coloca un resultado en esa variable, que queda a disposición del llamante una vez ha concluido la ejecución del programa. Proporciona su dirección de memoria al parámetro formal asociado, pudiendo modificarla, y dejando en ellas los valores que va a devolver. Estos parámetros suponen un ahorro de memoria, puesto que la variable local no existe realmente, sino que se asocia a la global en cada llamada. Una función es un subprograma que recibirá una serie de parámetros en la posición y de los tipos indicados en su lista de parámetros y que va a devolver un único valor de un tipo determinado que será el tipo de la función. Los procedimientos son un tipo de subprogramas que recibirán una serie de parámetros o argumentos en la cantidad, tipo y posición que indique su lista de parámetros realizará un determinado proceso y no devolverá ningún resultado de modo explícito. Es para cuando queremos modificar o devolver más de un valor o 6 variable u operar con una estructura de datos compleja. Las funciones en C son el bloque principal en la construcción de programas. La forma general es: Especificador_de_tipo nombre_de_función (lista_de_parámetros) { <secuencia de acciones> } El especificador_de_tipo es el tipo de dato que devolverá la función mediante la sentencia `return'. Si no lo especificamos, el compilador asume un tipo de vuelta concreto. La lista_de_parámetros es la lista de nombre de variables separadas por comas y con sus tipos asociados que reciben los valores de loa parámetros o argumentos cuando se llama a una función. Puede no tener ningún parámetro, y entonces pondremos `void'. Return tiene dos usos importantes: • Forzar una salida inmediata de la función en la que nos encontramos. • Devolver un valor, generalmente, calculado en la función. Hay dos formas de cómo una función puede terminar su ejecución y que ésta devuelva al sitio desde el que se la llamó: • Modo implícito. Se produce al llegar a la llave que cierra el código de la función. • Modo explícito. Porque tienen que devolver un valor o para simplificar el código de la función y hacerlo más eficiente permitiendo varios puntos de salida. Todas las funciones, excepto void, devuelven un valor. Si la función no es especificada como `void' y no se especifica un valor de vuelta, entonces el valor devuelto por la función queda técnicamente indefinido. Hay varios tipos de funciones: • Funciones computacionales. Diseñadas para operar con sus argumentos y devolver un valor basado en esos cálculos. • Son aquellas funciones que manipulan la información y nos devuelven un valor que indica únicamente el éxito o fracaso de tal manipulación. Como eje: la función de librería fwrite() que sirve para escribir en un fichero. • Son funciones que no tienen un valor de vuelta explícito y son estrictamente de tipo procedimiento sin generar un valor. Por ejemplo, la función srand() que inicializa la semilla del generador de números aleatorios. Algunas funciones devuelven algo de todas formas, aunque no sea de interés, como por ejemplo printf que devuelve el nº de caracteres escritos. Cuando se utiliza una sentencia RETURN en main, el programa devuelve un código de terminación al proceso que lo llamó, y debe ser un entero. En sistemas operativos como MS−DOS o OS/2, un valor de vuelta 0 indica que el programa ha terminado normalmente. Todos los demás indica que se ha producido algún error. Las reglas de ámbito de la función son reglas que controlan si un fragmento de código conoce o tiene acceso a 7 otro fragmento de código o datos. Cada función es un bloque de código y es privado a esa función sin poder accederse a él a menos que se llame a esa función. El código que comprende el cuerpo de la función está oculto al resto del programa y no se puede acceder ni modificar. Se denominan variables locales a las variables definidas de cada función. Existen a partir de que entra en la función y se destruye cuando sale de ella, es decir, que no conservan su valor entre dos llamadas consecutivas, a excepción de cuando una variable local se declara con el modificador `static'. Cuando es así, el compilador la trata como sí fuera global, salvo que sólo sigue siendo visible en el ámbito de la función en la que se declaró. No se puede definir una función dentro de otra función. Para usar argumentos se deben declarar variables que acepten los valores de los argumentos. También se denominan parámetros, los cuales se comportan como variables locales de la función, creándose al entrar en ésta y destruyéndose al salir de ella. Se utiliza la llamada por valor para pasar argumentos. Una función por valor se la pasa copia del valor de la variable. Es posible causar una llamada por referencia pasando un puntero al argumento. El paso de arrays o matrices como argumentos es una excepción al convenio de paso por valor que se tiene en C. Cuando se usa un array como argumento de una función sólo se pasa la dirección de la matriz, no una copia de los datos de la matriz entera. C soporta tres argumentos, pero sólo dos son definidos por ANSII: argc y argv. Permiten pasar información al programa de C mediante la línea de comandos. Un argumento de línea de comandos es una información que sigue al nombre del programa en la línea de comandos del sistema operativo. El parámetro argc contiene el nº de argumentos de la línea de órdenes, o sea, el nº de parámetros que podemos poner. El parámetro argv es un puntero a un array de punteros de caracteres. Cada elemento del array apunta a un argumento de la línea de órdenes. Todos los argumentos de esta línea son tratados como cadenas de caracteres. Un prototipo de función es una copia prácticamente literal de la cabecera de la declaración de ésta que se sitúa generalmente al principio de los ficheros C y forzosamente antes de la 1ª llamada a esa función. Tiene dos objetivos fundamentales: • Identificar el tipo de vuelta de la función para que el compilador pueda realizar una generación de código correcta. • Especificar el tipo y nº de argumentos que utiliza la función. Su forma es: tipo nombre función (lista de argumentos) Problema principal Subprograma 1 8 Subprograma 2 Subprograma 3 Módulo 1.1 Módulo 1.1 Módulo 1.1 Módulo 1.1 Módulo 1.1 Módulo 1.1 Módulo 1.1 Obtener las actas Elaboración de actas Entrada de alumnos y calificaciones Impresiones de actas EDICIÓN ANÁLISIS COMPILACIÓN Entrada de alumnos Entrada de calificaciones Cálculo de medias Ordenación de las actas Impresión de actas PROGRAMACIÓN MONTAJE/LINKADO CODIFICACIÓN PRUEBA de EJECUCIÓN 9 EXPLOTACIÓN Y MANTENIMIENTO EXPLOTACIÓN PRUEBAS DESARROLLO Gestión de datos de clase Tratamiento Salida de datos Entrada de datos Cálculos Medias asign Cálculos Medias globales Captura Datos Eval/Asign/alumno Inicialización Notas globales V c !V i V c> Vf A Vc = Vc +I En este tipo de estructuras repetitivas existen dos normas fundamentales de obligado cumplimiento: el valor I nunca puede ser 0 y V a no puede modificarse en el ámbito del bucle. Condición A A Condición +*\A^BCDE ABC^\D*E+ 10