Temario Oposición a Técnicos Auxiliares de Informática (BOE 03 NOV 2015) Considerando los Reyes, de gloriosa memoria, cuánto era provechoso e honroso que a estos sus Reinos se truxiesen libros de otras partes, para que con ellos se ficiesen los hombres letrados, quisieron e ordenaron: que de los libros non se pagase alcabala, y porque de pocos días a esta parte, algunos mercaderes nuestros, naturales y extranjeros, han trahido y cada día trahen libros mucho buenos, lo cual, por este que redunda en provecho universal de todos, e ennoblecimiento de nuestros Reinos; por ende, ordenamos e mandamos que, allende de la dicha franquiza, de aquí en adelante, de todos los libros que se truxeren a estos nuestros Reinos, así por mar como por tierra, non se pida, nin se pague, nin lleve almoxarifazgo, nin diezmo, nin portazgo, nin otros derechos algunos por los nuestros Almoxarifes, nin los Desmeros, nin Portazgueros, nin otras personas algunas, así como las cibdades e villas e lugares de nuestra Corona Real, como de Señoríos e órdenes e behenias; más que de todos los dichos derechos o almoxarifazgos sean libres e francos los dichos libros. Orden de los Reyes Católicos 1 TODOS LOS TEXTOS DE ESTOS APUNTES LLEVAN LA SIGUIENTE LICENCIA, EXCEPTO SI SE INDICA LO CONTRARIO Edición: 1ª Edición Julio 2.015 Título: Temario Oposición a Técnicos Auxiliares de Informática Autor: http://apuntedecaramelo.blogspot.com.es/ 2 TEMA 3. LENGUAJES DE PROGRAMACIÓN. REPRESENTACIÓN DE TIPOS DE DATOS. OPERADORES. INSTRUCCIONES CONDICIONALES. BUCLES Y RECURSIVIDAD. PROCEDIMIENTOS, FUNCIONES Y PARÁMETROS. VECTORES Y REGISTROS. ESTRUCTURA DE UN PROGRAMA 1. INTRODUCCIÓN 2. LENGUAJES DE PROGRAMACIÓN 2.1. Abstracción 2.2. Objetivo 2.3. Paradigma 2.4. Traducción 3. REPRESENTACIÓN DE TIPOS DE DATOS 3.1. Clasificación 3.2. Tipos de datos simples 3.2.1. Direcciones (punteros) 3.2.2. Enteros 3.2.3. Reales 3.2.4. Caracteres 4. OPERADORES 4.1. Aritméticos 4.2. Relacionales e Incrementales 4.3. Lógicos y de bits 4.4. De asignación 5. INSTRUCCIONES CONDICIONALES 6. BUCLES Y RECURSIVIDAD 7. PROCEDIMIENTOS, FUNCIONES Y PARÁMETROS 8. VECTORES Y REGISTROS 9. ESTRUCTURA DE UN PROGRAMA 9.1. Proceso de creación de un programa ejecutable 9.2. Estructura 9.3. Factores y métricas de calidad 9.4. Estrategias de pueba 190 1. INTRODUCCIÓN Un lenguaje de programación es una representación formal de algoritmos y funciones. Suponen un sistema normalizado para definir cómo y qué datos operar y desarrollar aplicaciones portables. Un lenguaje se dota de un conjunto de símbolos, más esquemático que el lenguaje natural, y una sintaxis que otorga significado a los símbolos para gestionar el comportamiento del sistema. Las características comunes, como la definición de sus elementos, (operadores, instrucciones), los niveles léxico (símbolos), sintáctico (reglas de relación entre símbolos) y semántico (significado de las expresiones), y el ámbito de su aplicación (concreta o genérica). La sintaxis hace uso de parámetros como datos (variables y constantes), operadores e instrucciones. Otra característica común es la posibilidad de definir subprogramas, estructuras que agrupan código repetido, que al necesitarse se invoca; lo que ahorra líneas de código y facilita su legibilidad. Su evolución se puede agrupar en 5 generaciones. Las 2 primeras responden al estado primitivo de desarrollo; lenguaje máquina y ensamblador. La 3ª, lenguajes de medio y alto nivel, imperativos y OO, con sintaxis más amigables (C, C++, Java…). La 4ª, lenguajes declarativos, como SQL, más cercanos al lenguaje natural. La 5ª, lenguajes en que no interviene el programador, propios de inteligencia artificial (IA). 2. LENGUAJES DE PROGRAMACIÓN Se caracterizan por la definición de los niveles léxico, sintáctico y semántico y el ámbito de aplicación. La definición de estos 3 niveles usa distintos elementos. Datos. Un lenguaje define tipos de datos y su representación interna. Típicos son enteros, reales o lógicos. En general, al declarar un tipo de dato, el lenguaje lo representa como variable. Las constantes suelen usar una palabra reservada como constant o similar. La asignación puede usar símbolos tipo “:=”, “=”, etc. Una variable se define en un ámbito local (limitado; al abandonarse se elimina la variable) o global (contexto del programa). No se deben mezclar ámbitos de uso. Es buena práctica no dar el mismo nombre a variables locales y globales. Para cambiar de ámbito se usa el “paso por valor”, no la variable en memoria. Operadores. Indican operaciones entre datos. Se distinguen aritméticos (suma, resta, etc. en forma común), relacionales, (para comparar, <, > =, <>, ==,...) y lógicos, que evalúan expresiones devolviendo un valor binario (NOT (!), AND (&), OR (||), etc.). Los operadores tienen prioridades, lo que hace que se evalúen primero unas operaciones prioritarias. Instrucciones. Indican la acción a realizar. Se distinguen condicionales, de repetición y ordinarias. Las instrucciones condicionales evalúan una expresión y en función del resultado realizan un proceso. Ejemplos son IF... THEN... ELSE, SWITCH o CASE. Las instrucciones de repetición realizan el mismo proceso varias veces. Ejemplos son WHILE, FOR o DO... UNTIL. Las instrucciones ordinarias englobarían el resto de instrucciones que realizan procesos comunes de lectura, escritura, etc. Los subprogramas, procedimientos (o métodos) y funciones, se diferencian en que los primeros realizan un proceso y las funciones además devuelven un valor. Llamar o invocar un subprograma requiere parámetros de entrada, en general variables. Un parámetro concreto, se denomina argumento. El paso de variables se realiza por valor o por referencia. Se pueden incluir comentarios en el código, que facilitan el mantenimiento y son ignorados en la traducción. Pueden usar símbolos como // o /*. Los lenguajes de programación se pueden clasificar según varios criterios: abstracción, objetivo, paradigma y traducción. 2.1. Abstracción El nivel de abstracción del lenguaje (cercanía al natural) distingue lenguajes máquina, ensamblador y de alto nivel. El lenguaje máquina es el que interpreta el microprocesador. Es la programación primitiva, engorrosa, al usar código binario e instrucciones imperativas. Al ser diferente para cada máquina, es difícil de portar. La solución es programar a alto nivel y traducir. El ensamblador es una primera abstracción del lenguaje máquina; usa nemónicos para las instrucciones. Permite comentarios y etiquetas que definen puntos de control o rutinas. Los lenguajes de alto nivel son más cercanos al humano, más fáciles de aprender y con sintaxis bien definida. Independizan el desarrollo de la tecnología, liberándolo de detalles hw. Sus características son un conjunto de instrucciones amplio y potente, uso de variables, sintaxis flexible y legibilidad. Deben traducirse o compilarse. El compilador marca la eficiencia del código máquina traducido. Ejemplos de lenguaje de alto nivel son C, C++, Java, etc. 2.2. Objetivo Este criterio distingue lenguajes de propósito general, específico y scripting. Los de propósito general pueden usarse para programar cualquier tipo de aplicación. También marcan una especialización de facto; así, C y C++ se usan mucho en programación de SO o Java en aplicaciones multiplatarfoma. 191 Los lenguajes de scripting automatizan tareas de otro programa, que los ejecuta e interpreta. Los lenguajes de shell scripting se ejecutan en el shell del SO para tareas de gestión. Los más conocidos son los de Unix o Linux como Bash. Otros lenguajes de scripting, como JavaScript, se usan en navegadores. El esquema actual de trabajo puede resumirse en: PHP en servidores, datos en XML y Javascript en navegador. PHP, PERL o ASP son lenguajes de script interpretados. Los lenguajes de propósito específico, se aplican a problemas específicos como gráficos o sonido. Por ejemplo el lenguaje MediaWiki, actuaría entre el lenguaje natural y el HTML final, no usándose para otro propósito. 2.3. Paradigma El paradigma de programación es la filosofía usada en el desarrollo. El tipo de programa a desarrollar marcará el paradigma más adecuado. Se distinguen paradigmas de programación: Estructurada. Basada en la definición de métodos y funciones que representan subprogramas que facilitan mantenimiento y reutilización de código. Desaconseja el uso de instrucciones incondicionales. Modular. Es la evolución de la estructurada. La idea subyacente es la misma, estructurar el desarrollo en módulos o subrutinas. Hace uso de bibliotecas. Orientada a objetos (POO). Su esencia es similar; organiza el código de forma modular pero orientándose más a la realidad. Un programa se denomina clase y posee una entidad diferenciada. Cada clase, puede ser hija o padre de otra que la amplíe (herencia), reutilizando masivamente el código. El uso concreto del código de la clase se hace con objetos que interactúan. C++, Java, o Pascal soportan POO. Se considera a Simula (1967) el primer lenguaje OO, concebido para simulación. Con Smalltalk (1972) se desarrolló el grueso de la teoría de POO. Otros ejemplos son ABAP, C++, Object Pascal, Eiffel, Java, JavaScript, Perl, PowerBuilder, Python, Ruby, VB .NET o Scala (usado por Twitter). Algunos lenguajes combinan la POO con otros paradigmas, como C++, Pascal u OOCOBOL. Un nivel más de abstracción lo supone la metodología de Programación Orientada a Aspectos (POA), en desarrollo. En tiempo real. Usada en sistemas con requisitos temporales críticas, como en centrales nucleares. Un lenguaje de este tipo debe ofrecer agilidad para interactuar con dispositivos de E/S, tener medios de control del tiempo, establecimiento de umbrales, prioridades, etc. Otros paradigmas. Suelen identificarse programación imperativa, que detalla comandos de funcionamiento (usada en todos los lenguajes); funcional, que define instrucciones con funciones matemáticas, como LISP; declarativa, que enuncia tareas, no cómo realizarlas, como SQL; y la lógica, que modela la lógica humana con parámetros y métricas. Se aplica en IA y sistemas expertos y lenguaje representativo es PROLOG. 2.4. Traducción Los lenguajes de programación pueden ser compilados si se traducen por completo antes de su ejecución o interpretados si se traducen instrucción a instrucción al ejecutarse, o compilados en tiempo de ejecución (usados en máquinas virtuales). Otra clasificación más específica distinguiría lenguajes tipados y no tipados. Los tipados declaran tipos de datos de forma estática; sus variables tienen el mismo tipo en ejecución. En lenguajes no tipados las variables pueden cambiar su tipo de datos durante la ejecución, como JavaScript. Preguntas de EXAMEN Es un lenguaje orientado a objetos puro: a) Smalltalk b) C c) Cobol d) C++ Qué pareja lenguaje y paradigma de programación en que se basa es correcta: a) Haskell-Lógico b) Prolog-Funcional c) Java-Imperativo d) JavaScript-Prototipado El lenguaje ensamblador es un lenguaje de programación de… a) Alto nivel y correlación directa con código máquina b) Alto nivel y correlación directa con Java c) Bajo nivel y correlación directa con código máquina d) Bajo nivel y correlación directa con Java 192 Son paradigmas de programación: a) Programación imperativa y funcional b) Programación imperativa y orientada a objetos c) Programación funcional y lógica d) Todas son correctas 3. REPRESENTACIÓN DE TIPOS DE DATOS Se puede definir dato como la representación simbólica de un hecho cuantitativo o cualitativo, que lo caracteriza. Un tipo de dato es la especificación del dominio, rango de valores y conjunto operaciones válidas relativas al mismo. La representación de un tipo de dato especifica cómo se interpretan los símbolos que lo dan significado. Por tanto, intrínsecamente define también el dominio y las operaciones sobre ellos. 3.1. Clasificación Sean los criterios de clasificación mostrados en el esquema. En función de quién define el tipo de dato se identifican los estándares, definidos por el lenguaje de programación o por el usuario. Según la representación interna que interpreta la máquina, pueden distinguirse datos escalares o simples, que se interpretan a partir de bits, o datos estructurados (estructuras de datos), que añaden otro nivel de significado al organizar en estructuras datos simples. 3.2. Tipos de datos simples Los tipos de datos simples son números enteros, reales, caracteres y direcciones de memoria (punteros), que a veces se denominan datos derivados. 3.2.1. Direcciones (punteros) Se representan como números naturales. Su dominio es 2n, siendo n el tamaño o número de líneas del bus de direcciones. En la UCP, en general, los registros tienen n bits y suele coincidir con el tamaño de palabra. En la MP suele poder accederse a palabras, medias palabras y bytes. 3.2.2. Enteros Para la representar enteros, se suele considerar el formato BCD, poco usado y con 2 variantes, decimal empaquetado, de 16 bits y desempaquetado, de 32. El formato más usado es el de coma fija, que interpreta el bit más significativo como bit de signo, y los n-1 bits restantes como módulo, complemento A1 o CA2, como se muestra en la figura. 3.2.3. Reales La representación de números reales suele usar el formato de coma flotante. El número se interpreta con el signo indicado por el BMS; exponente el exceso de 2e-1 (E=C-2e-1) de los siguientes ‘e’ bits y de mantisa el valor decimal (M) de los siguientes ‘m’ bits, con base implícita, en general 2, como se muestra en la figura. Se dan 2 variantes, la normalización entera, que interpreta la base como tal y la normalización fraccionaria que interpreta la base como 0,M. Para números negativos se usa el signo y módulo o complemento de la mantisa. La norma de referencia que suele usarse para coma flotante es IE3 754. Interpreta un número real como X=±1,MꞏbE. Los negativos se interpretan con signo y módulo. M sigue la normalización fraccionaria (omitiendo el bMs, bit Más significativo, 1), la base, b=2 y E, exceso de 2e-1-1, siendo E=C-2e-1+1. Debe tenerse en cuenta la interpretación según el almacenamiento en memoria. Se distinguen 2 convenios, extremista mayor o “big endian” y menor o “little endian”. Little endian, interpreta como byte menos significativo el de la posición de memoria más baja y big endian, como bMs. A la izquierda se muestra cómo se almacena la cadena “$123” con ambos convenios y a la derecha, el ejemplo de almacen de un real. 193 3.2.4. Caracteres La representación de caracteres, suele hacerse con 1 B, interpretado con un código ASCII, o ISO Latin 9, o Unicode, que puede usar 1 o varios bytes. 4. OPERADORES Los operadores son símbolos (+, *…) que permiten hacer operaciones sobre datos, generando un resultado. Pueden ser unarios (aplican a un operando), binarios y ternarios. 4.1. Aritméticos Son operadores binarios que aceptan operandos numéricos y devuelven otro número del mismo tipo. Suelen intervenir en el lado derecho de una asignación. Si está en el lado izquierdo, primero se evalúa el lado derecho y luego se asigna. El orden de evaluación (prioridad) es similar al matemático. Multiplicaciones y divisiones tienen más prioridad que sumas y restas. Los operadores aritméticos típicos son: + suma - resta * multiplicación / división % módulo (resto división entera) 4.2. Relacionales e Incrementales Los operadores relacionales comparan datos. En general se devuelve 0 si la relación es falsa y 1 si verdadera. El resultado suele poder asignarse a variables de tipo entero, booleano o carácter. Los operadores incrementales son unarios, incrementan o decrementan un operando numérico una unidad. En función de la posición relativa del operador respecto al operando se definen operaciones de post y pre incremento o decremento. Los operadores relacionales e incrementales típicos son: < menor que > mayor que <= menor o igual que ++ incrementa una unidad >= mayor o igual que == igual a != distinto a -- decrementa una unidad 4.3. Lógicos y de bits Los operadores lógicos implementan operaciones booleanas. Actúan sobre operandos booleanos y dan un resultado booleano. Las operaciones AND y OR son binarias (2 operandos) mientras que NOT es unaria. Los operadores lógicos se usan para combinar operadores relacionales formando expresiones lógicas complejas. Es posible que uno o ambos operandos sea una variable o constante numérica. En ese caso se evaluara como falso (0) si su contenido es 0, y verdadero (1) si su contenido es otro. && AND (Y lógico) || OR (O lógico) ! NOT(negación lógica) Los operadores de bits son operadores binarios, excepto la negación que es unario. Aplican a variables enteras, resultando otro entero aplicando operaciones lógicas correspondientes a los bits de los operandos. Al desplazar bits a la izquierda, suele rellenarse con 0 por la derecha y al desplazar a la derecha, se rellena por la izquierda con 0 si el digito más significativo es 0, y con 1 si es 1. El tipo de variable es con signo. | OR de bits >> desplazamiento de bits a la derecha ^ XOR (O exclusivo) de bits & AND de bits << desplazamiento de bits a la izquierda ~ NOT de bits 4.4. De asignación El operador básico de asignación es el signo '=', ‘:=’ o similares. Se sule poder realizar operaciones combinadas al aparecer el mismo operando en el lado izquierdo y derecho de la asignación. La expresión m=z, por ejemplo, en C, se debe interpretar como "se asigna a m el valor de z". Los operadores dobles por ejemplo x*=y equivaldría a x=x*y. Es el nivel semántico del lenguaje que se trate. 194 = asignación += incremento y asignación -= decremento y asignación %= módulo y asignación *= multiplicación y asignación /= división y asignación ^= XOR lógico de bits y asignación >>= desplazar bits a dcha. y asignación &= AND lógico de bits y asignación |= OR lógico de bits y asignación <<= desplazar bits a izqda. y asignación 5. INSTRUCCIONES CONDICIONALES Las instrucciones de programación pueden clasificarse en secuenciales, condicionales e iterativas. Secuenciales. Estructura natural de un programa. Consiste en enumerar las instrucciones en orden, limitadas con algún identificador como el ‘;’. Tras él es habitual un retorno de carro para mejorar la legibilidad. En algún lenguaje el limitador es el “Enter”. Condicionales, de bifurcación o selección. Permiten cambiar la secuencia en función de condiciones, que pueden anidarse o usar estructuras que seleccionen una operación según el valor de cierto parámetro. Se suelen delimitar con palabras reservadas como BEGIN o END. Estructuras típicas se definen con instrucciones IF... THEN... ELSE, CASE...OF o SWITCH. Se esquematizan en la figura. Iterativas o repetitivas. Implementan bucles, que repiten un proceso hasta que se cumpla una condición. El proceso puede repetirse como poco una vez, o ninguna, según la estructura que se use. Las 3 instrucciones habituales son FOR... TO, WHILE y DO...UNTIL. Subprogramas y subrutinas. Agrupan código que realiza un proceso, distinguiendo una funcionalidad. Incrementan legibilidad y reutilización. La subrutina se declara en algún lugar del programa, en general al principio, tras la declaración de variables, constantes y tipos de datos. Para identificarlas se nombran. Operaciones comunes de las estructuras es la asignación, definición de constantes y tipos de datos. La asignación consiste en dar valor a variables. Al definirlas se reserva espacio en memoria que podrá tener cualquier valor, denominado basura. Hasta que no se inicializa, su valor no está controlado. 6. BUCLES Y RECURSIVIDAD La recursividad consiste en la llamada de un programa a si mismo. Para evitar problemas existen 2 condiciones a verificar: que en cada invocación se reduzca la complejidad del problema y que exista al menos una condición de fin de recursividad. Otro tipo de recursividad es la permitida por tipos de datos, en que al definirse, incluyen su mismo tipo de datos. Es el caso de listas y variantes. El diseño de un algoritmo recursivo es diferente al de uno iterativo. El recursivo soluciona el problema suponiendo una solución atómica, con lo que al aplicarlo, usa esas suposiciones hasta alcanzarla. En ese momento se realiza el camino inverso para completar la solución total. Un algoritmo iterativo soluciona una parte del problema y repite el proceso para el resto, hasta completar la solución total. Un ejemplo clásico de comparación entre recursividad e iteratividad es el cálculo del factorial de un número. El algoritmo iterativo multiplica el número por su anterior n-1 veces. El recursivo multiplica por el factorial de n-1, hasta la solución atómica 1!=1 y devolviendo las invocaciones hasta completar n!. Un algoritmo recursivo puede tranformarse en uno iterativo. La elección dependerá del coste computacional, legibilidad del código u otros. El cálculo de la complejidad de un algoritmo iterativo es más simple que el de uno recursivo porque aumenta el coste en memoria para cada llamada recursiva. La recursividad se usa en recorrido de grafos, orden de vectores o fuerza bruta, que intentan probar todas las soluciones. En este caso se requiren controles si el algoritmo no converge a la solución. Son los algoritmos de backtracking. 195 7. PROCEDIMIENTOS, FUNCIONES Y PARÁMETROS Funciones y procedimientos son estructuras que implementan subprogramas. La diferencia es que una función, tras ejecutarse, devuelve un valor que puede usarse para asignarlo a una variable. Un procediminento no devuelve valores a su salida, sólo realiza un proceso, como una presentación en pantalla o la impresión de un archivo. En lenguajes orientados a objetos, funciones y procedimientos reciben el nombre de métodos. Las ventajas de usar subrutinas son la reducción de redundancia del código, modularidad, mejora de legibilidad, localización de puntos de fallo, reutilización de código y ocultación de la implementación, lo que permite que distintos programadores se centren en la función y se “comuniquen” mediante interfaces. Las funciones y los procedimientos tienen una estructura definida. Suelen declararse con una cabecera que indica su nombre, para identificarlos, que incluye los datos de entrada (y salida si es una función). El cuerpo se programa siguiendo las normas del lenguaje de programación. El valor devuelto por una función se indicará con alguna palabra tipo ‘return’ o similar. La especificación de los datos que usa el subprograma se hace en su interfaz, a veces llamado prototipo. Al dato genérico se le denomina parámetro y al dato concreto que se ofrece, argumento. En el paso de variables entre programas, puede usarse el paso por valor o por referencia. El paso por valor copia el valor de la variable y se usa de forma privada en el subprograma. Es una forma de ocultación. En el paso por referencia, la variable es modificada directamente por el subprograma, con el riesgo que conlleva. En principio, lo recomendable es usar el paso por valor. 8. VECTORES Y REGISTROS Un array o vector es una estructura estática en que los datos son homogéneos (del mismo tipo) y se organizan de forma lineal. Para referenciar un dato se usa un índice que indica su posición en el array. La cantidad de datos que contendrá el array, su tamaño, se define al crearlo. P.e. en Pascal: vector1: array [1..10] of integer; definiría un array de 10 posiciones, en la que cada posición contendrá un entero. Un registro es una estructura estática heterogénea (contiene datos de distinto tipo). Otra estructura similar a los registros son las uniones. La diferencia estriba en que en una unión todos los datos internos ocupan el mismo espacio, igual al tamaño del mayor elemento. Si se modifica un elemento los demás se modifican. En C se declaran con la palabra union. 9. ESTRUCTURA DE UN PROGRAMA Añadido al tema del epígrafe se desarrollan aspectos adicionales que suelen ser materia de examen. 9.1. Proceso de creación de un programa ejecutable La figura representa el proceso de creación de un programa ejecutable. Preprocesado. El código es un fichero de texto. La máquina no lo entiende. Así, lo primero es preprocesar el código (fuente), realizando tareas como sustituir partes del código por otras equivalentes o analizar dependencias entre ficheros generando una versión del código también en texto. Compilado. Transforma el archivo preprocesado en uno binario (código máquina). En general visible en disco (con extensión .obj en WS y .o en Linux). Estos archivos ya son parte del programa binario que ejecutaría el equipo. Son una parte, no el total. Enlazado estático (linkado). Es normal necesitar código adicional, ya compilado en, por ejemplo, bibliotecas estáticas (.liben en WS y .a en Linux). El enlazador o linker realiza la composición del archivo ejecutable (.exe en WS, sin extensión en Linux). El código de biblioteca necesario, queda embebido en el ejecutable, por lo que no es necesario adjuntar la biblioteca para su ejecución. Enlazado dinámico. Al ejecutar un programa puede necesitarse más código no enlazado en la etapa anterior y que se encuentre en bibliotecas dinámicas (o compartidas, extensión .dll en WS y .so en Linux). Cuando estas bibliotecas se encuentran en el SO (caso general) no es necesario distribuirlas con el ejecutable. Al enlazar dinámicamente el SO, el programa se convierte en un proceso completo en ejecución. 196 9.2. Estructura La estructura de un programa suele identificar 3 partes, al igual que en un texto. Si el texto consta de una introducción, cuerpo del escrito y conclusión, un programa constaría de cabecera, declaraciones y cuerpo del programa; y fin, que será una instrucción END o similar. Las declaraciones incluyen las de unidades externas, variables, constantes, tipos de datos y subprogramas. Como ejemplo, la estructura de un programa sencillo en C sería la mostrada en el cuadro. Declaración de importaciones. Indica al compilador qué acciones y funciones de las mencionadas en el programa no se encuentran implementadas en este. Estos procedimientos externos al programa se hallan disponibles en estructuras llamadas módulos que tienen asociado un fichero con extensión.h que contiene la lista de procedimientos importables. Para incluirlos se escribe una instrucción del tipo #include. declaración de importaciones definición de constantes definición de tipos prototipos de función declaración de variables globales void main (void) { declaración variables locales instrucciones ejecutables} implementación de funciones /* Archivos de cabecera de funciones. */ #include <stdio.h> #include <stdlib.h> #include <conio.h> void main() { /* programa principal*/ clrscr(); // Borra la pantalla printf("Hola World"); // Imprime getch(); // espera pulsación teclado } Definición de constantes. Se asigna a un identificador un valor. Un proceso previo a la compilación substituirá el identificador por la cadena de caracteres cada vez que lo encuentre. Se usa #define. Macros. La directiva #define también permite definir macros, operaciones sobre datos o variables. Se muestra su sintaxis. #define nombre_macro (param1, ... , paramN) [código] En la sintaxis, “código” son sentencias en C y los parámetros (param1,…,paramn) son símbolos en código. El preprocesador sustituye cada llamada a la macro por su código y los parámetros por los valores que tengan cuando se llama a la macro. Como en el ejemplo. En el código ejemplo, se define la macro CUBO con el parámetro "x". Cuando el preprocesador encuentra la macro, la sustituye por su código sustituyendo la "x" por el parámetro entre paréntesis (en el código, variable “a”). Usar macros se desaconseja a menos que sea necesario. En caso de uso se recomienda que el código sea sencillo. Definición de tipos. Permite definir tipos de datos que serán usados en todo el programa. El ejemplo muestra un ejemplo de definición de booleano en C. Prototipos de función. Indican las funciones a las que hacer referencia más adelante en el código. #include <stdio.h> #define CUBO(x) x*x*x void main () { float a=3,b; b=CUBO (a); printf("b= %f\n",b); } Declaración de variables globales. Se declaran las variables que podrán usarse por cualquier procedimiento del programa, sin necesidad de pasarlas como parámetro. La función principal o main(). Es el único requisito indispensable del programa. Es el cuerpo del programa. typedef enum {FALSE=0, TRUE=1} booleano; Declaración de variables locales. Se declaran variables que se usarán dentro del programa. char c; Instrucciones. Comandos de proceso de datos. booleano error; Implementación de funciones. Se define el cometido de cada función que usa el programa. Coincide con el prototipo de función. ... void main (void) { int larg, num_palabras; } 9.3. Factores y métricas de calidad Los factores de calidad permiten establecer criterios de control y medida de la garantía del código. Su definición, como la idea de calidad, posee una parte subjetiva, originando diversas clasificaciones como la de McCall, que distingue tres grupos, operativos, de mantenimiento y evolutivos. Factores operativos. Afectan al uso del sw. Distingue factores de corrección (se cumplen especificaciones), fiabilidad (libre de errores), eficiencia, seguridad (acceso a sw y datos) y facilidad de uso. 197 Factores de mantenimiento. Aplicados a la capacidad de modificación o adaptación del sw. Destacan 3; flexibilidad (esfuerzo para modificar un programa), facilidad de prueba y facilidad de mantenimiento (esfuerzo de localización y reparación de errores). Factores evolutivos. Indican posibilidad de ejecutar sw de forma eficiente en distintas plataformas. Pueden ser la portabilidad o facilidad de migración entre entornos, capacidad de reutilización o la interoperabilidad. Las métricas de calidad son técnicas aplicadas en la valoración cuantitativa de un factor de calidad sw. Toda métrica debe poseer al menos la propiedad de ser empírica, objetiva, simple, fácil de calcular, independiente del lenguaje y tal que proporcione información útil. Algunos ejemplos de métricas representativas en cada fase del ciclo de vida sw son las siguientes. Análisis. El alto nivel conceptual y la dificultad de cuantificación limitan la existencia de métricas. Suelen medir el tamaño del sw a desarrollar. Ejemplos son la métrica punto-función (PF de Albrecht, contemplada como técnica en Métrica v3), usada para cuantificar la funcionalidad de un sistema a partir de su descripción y basada en la ponderación de las E/S de usuario, peticiones, archivos e interfaces externas. La métrica bang, propuesta por DeMarco, calcula el tamaño del sw a desarrollar a partir del análisis. La métrica de calidad de especificación, propuesta por Pressman, mide la calidad del análisis y la captura de requisitos. Aunque sean factores cualitativos, se intentan cuantificar, midiendo p.e. el número de requisitos donde los revisores coinciden. Encajan en la fase de Estudio de Viabilidad del Sistema (EVS) en Métrica v3. Diseño. En esta fase, las métricas son morfológicas; suelen trabajar con parámetros de la estructura de los programas o medidas del grado de cohesión, acoplamiento y complejidad de los algoritmos. Son de caja negra en el sentido que no se evalúa la estructura de los módulos. Ejemplos son la métrica de complejidad de Card y Glass, basada en complejidad estructural (número de módulos que controla uno dado) y de dados (variables de E/S en relación a la complejidad estructural), calculados para cada módulo a partir del diagrama de estructuras. La complejidad total es la suma de la estructural y la de datos de cada módulo. La métrica de cohesión y acoplamiento pretende cuantificar esos aspectos en módulos a partir de factores como los parámetros de E/S o variables globales. Las métricas usadas en POO, se basan en clases y miden aspectos como la profundidad de herencia (jerarquía de herencias; cuanto mayor profundidad, menor calidad) o el acoplamiento entre clases (cuanto mayor, menor calidad). Codificación. Las métricas en codificación intentan medir la complejidad sw. Ejemplos son la medida de la complejidad ciclomática y esencial, desarrolladas por McCabe para medir la complejidad lógica. Se basan en la representación del flujo de control como un grafo. La complejidad ciclomática mide el número de ciclos del grafo de control. La esencial, el máximo anidamiento de las estructuras de control. Las métricas usadas en POO se usan para medir parámetros como el número de métodos de la clase o similares. Por fin, la documentación es otro parámetro de calidad difícil de medir. Lo que demuestra la experiencia, es que es crucial para en el desarrollo y mantenimiento sw. 9.4. Estrategias de prueba Las pruebas sw aplican una estrategia centrífuga, desde las partes internas del código al sistema completo. Se distinguen pruebas unitarias, de integración, de validación y de sistema. La figura pretende trazar el tipo de prueba con el ciclo de vida del desarrollo sw para facilitar su comprensión. Pruebas unitarias. Se refieren a partes del código. Se puede trazar a la etapa de codificación. Se prueban caminos de control importantes, interfaces, estructuras y coherencia de datos, condiciones límite y aspectos del estilo. Pruebas de integración. Se refieren al funcionamiento del código en conjunto, en su totalidad, de la arquitectura sw. Se traza con la fase de diseño del ciclo de vida. Se buscan errores de interacción, interfaces. La táctica es la integración incremental, de sólo algunos módulos e ir avanzando a la totalidad. Las pruebas de sistema se refieren a requisitos funcionales y están ligadas muchas veces a entornos de operación. Por tanto se trazan con la fase de análisis del ciclo de vida. Siguen una táctica general de prueba de caja negra, que oculte la implementación pero muestre la conformidad con los requisitos. Las pruebas de validación se refieren al cumplimiento de requisitos de usuario. Se podría trazar con el análisis de viabilidad del sistema. El objetivo es la prueba real del sistema. Algunas pruebas pueden ser las de recuperación, forzando el fallo del software, las de seguridad, resistencia, rendimiento, etc. 198 Las pruebas de validación y de sistema pueden entenderse como una misma cosa. El matiz estaría en que las de sistema tendrían un carácter interno a la organización que desarrolla el SI y las de validación las hace el peticionario. También se distinguen pruebas alfa y beta. Las primeras, en un entorno de preproducción (maqueta) y las segundas en uno de producción o no controlado por el equipo de desarrollo. Las pruebas beta pueden incluir otro tipo de pruebas como las de vulnerabilidades. Métrica v3, en su interfaz de aseguramiento de la calidad también identifica estos tipos de pruebas. Métrica v3 indica que en el proceso de construcción del SI, el grupo de calidad es el encargado de revisar los estándares de nomenclatura y normativa aplicada en la generación del código de componentes, la evaluación de los resultados de las pruebas, los manuales de usuario y el esquema de formación. Con respecto a las pruebas, se revisa que se han llevado a cabo las pruebas unitarias, de integración y del sistema según los criterios de selección de verificaciones y casos de prueba asociados fijados en el plan de aseguramiento de la calidad. El esquema muestra la correspondencia entre las actividades del proceso CSI (Construcción del SI) y las del interfaz de Aseguramiento de la Calidad. Preguntas de EXAMEN En relación a las técnicas de prueba del sw, el análisis de valores límite: a) Es una métrica que proporciona una medición cuantitativa de la complejidad lógica de un programa b) Pertenece a las pruebas de caja negra c) Pertenece a las pruebas de caja blanca Según MÉTRICA 3, las pruebas de Regresión: a) Tratan de evitar que los cambios provocados por una petición no introduzcan un comportamiento no deseado en otros componentes no modificados b) Se especifican durante el estudio de viabilidad del Sistema de Información c) Nunca implican la repetición de pruebas que ya se han realizado previamente d) Son definidas por los usuarios del sistema y su objetivo es conseguir la aceptación final del sistema Según MÉTRICA 3, el objetivo de las pruebas de implantación es: a) Permitir que el usuario determine, desde el punto de vista de operación, la aceptación del sistema instalado en su entorno real b) Verificar la correcta implantación del sistema conforme a las verificaciones del plan de pruebas para el nivel de pruebas unitarias c) Verificar si los componentes interactúan correctamente a través de sus interfaces internos y externos d) Comprobar la integración de los componentes del sistema de información y su interacción con otros 199