sábado, 20 de abril de 2013

Página 3. Estructura de datos en C++



  • 1.2.3. Diseño
La fase de diseño de la solución se inicia una vez que se tiene la especificación del problema y consiste en la formulación de los pasos o etapas para resolver el problema. Dos metodologías son las más utilizadas en el diseño: diseño descendente o estructurado que se apoya en programación estructurada y diseño orientado a objetos que se basa en la programación orientada a objetos.

El diseño de una solución requiere el uso de algoritmos. Un algoritmo es un conjunto de instrucciones o pasos para resolver un problema. Los algoritmos deben de procesar los datos necesarios para la resolución del problema. Los datos se organizan en almacenes o estructuras de datos. Los programas se compondrán de algoritmos que manipulan o procesan las estructuras de datos.


Una buena técnica para el diseño de un algoritmo, como se ha comentado (diseño descendente), es descomponer el problema en subproblemas o subtareas más pequeñas y sencillas, a continuación descomponer cada subproblema o subtarea en otra más pequeña y así sucesivamente, hasta llegar a subtareas que sean fáciles de implantar en C++ o en cualquier otro lenguaje de programación.

Los algoritmos se suelen escribir en pseudocódigo en otras herramientas de programación como diagramas de flujo o diagramas N-S. Hoy en día la herramienta más utilizada es el pseudocódigo o lenguaje algorítmico, consistente en un conjunto de palabras --en español, inglés, etcétera-- que representan tareas a realizar y una sintaxis de uso como cualquier otro lenguaje.

Técnica de diseño descendente. 1) Descomponer una tarea en subtareas; a continuación cada subtarea en tareas más pequeñas. 2) Diseñar el algoritmo que describe tarea.

Otra técnica o método de diseño muy utilizado en la actualidad es el diseño orientado a objetos. El diseño descendente se basa en la descomposición de un problema en un conjunto de tareas y en la realización de algoritmos  que resuelven esas tareas, mientras que el diseño orientado a objetos se centra en la localización de módulos y objetos del mundo real. Estos objetos del mundo real están formados por datos y operaciones que actúan sobre los datos y modelan, a su vez, a los objetos del mundo real, e interactúan entre sí para resolver el problema concreto.

El diseño orientado a objetos ha conducido a la programación orientada a objetos, como uno de los métodos de programación más populares y utilizados en el pasado siglo XX y actual XXI.

Consideraciones prácticas de diseño

Una vez que se ha realizado el análisis y una especificación del problema se puede desarrollar una solución. El programador se encuentra en una situación similar a la de un arquitecto que debe dibujar los planos de una casa; la casa debe de cumplir con ciertas especificaciones y cumplir las necesidades de su propietario, pero puede ser diseñada y construida de muchas formas posibles.

La misma situación se presenta al programador y al programa a construir. En programas pequeños, el algoritmo seleccionado suele ser muy simple y consta de unos pocos cálculos  que deben realizarse. Sin embargo, lo más normal es que la solución inicial deber ser refinada y organizada en subsistemas más pequeños, con especificaciones de como interactuar entre sí y los interfaces correspondientes. Para conseguir este objetivo, la descripción de la solución comienza desde el requisito de nivel más alto y prosigue en modo descendente hacia las partes que deben construir para conseguir este requisito.

Una vez que se ha desarrollado, una estructura inicial se refinan las tareas hasta que estás se encuentren totalmente definidas. El proceso de refinamiento de una solución continúa hasta que los requisitos más pequeños se incluyan dentro de la solución. Cuando el diseño se ha terminado, el problema se resuelve mediante un programa o un sistema de módulos (funciones, clases...), bibliotecas de funciones o de clases, plantilla, patrones, etc.


  • 1.2.4. Implementación (codificación)
La codificación o implementación implica la traducción de la solución de diseño elegida en un programa de computadora escrito en un lenguaje de programación tal como C++ o Java. Si el análisis y las especificaciones han sido realizadas con corrección y los algoritmos son eficientes, la etapa de codificación normalmente es un proceso mecánico y casi automático de buen uso de las reglas de sintaxis del lenguaje elegido.

El código fuente, sea cual sea el lenguaje de programación en el cual se haya escrito, debe ser legible, comprensible y correcto (fiable). Es preciso seguir buenas prácticas de programación. Los buenos hábitos de la escritura de programas facilita la ejecución y prueba de los mismos. Tenga presente que los programa, subprogramas (funciones) y bibliotecas escritos por estudiantes suelen tener pocas líneas de programa (decenas, centenas...); sin embargo, los programas que resuelven problemas del mundo real contienen centenares y millares e incluso millones de líneas de código fuente y son escritos por equipos de programadores. Estos programas se usan, normalmente, durante mucho tiempo y requieren un mantenimiento que en muchos casos se realiza por programadores distintos a los que escribieron el programa original.

Por estas razones, es muy importante escribir programas que se pueden leer y comprender con facilidad así como seguir hábitos y reglas de lecturas que conduzcan a programas correctos (fiables).

Recuerde
Escribir un programa sin diseño es como construir una casa sin un plano (proyecto).

  • 1.2.4. Implementación (codificación)
La prueba y corrección de un programa pretende verificar que dicho programa funciona correctamente y cumple realmente todos los requisitos. En teoría las pruebas revelan todos los errores existentes en el programa. En la práctica, esta etapa requiere la comprobación de todas las combinaciones posibles de ejecución de las sentencias de un programa, sin embargo, las pruebas requieren, en muchas ocasiones, mucho tiempo y esfuerzo, que se traduce a veces en objetivo posible excepto en programas que son muy sencillos.

Los errores pueden ocurrir en cualquiera de las fases del desarrollo de software. Así, puede suceder que las especificaciones no contemplen de modo preciso la información de entrada, o los requisitos dados por el cliente; también puede suceder que los algoritmos no estén bien diseñados y contengan errores de lógicos por el contrario que los módulos o unidades de programa no estén bien codificados o la integración de las mismas en el programa principal no se haya realizado correctamente. La detección y corrección de errores es una parte importante del desarrollo de software, dado que dichos errores pueden aparecer en cualquier fase del proceso.

Debido a que las pruebas exhautivas no suelen ser factibles ni viables en la mayoría de los programas se necesitan diferentes métodos y filosofías de prueba. Una de las responsabilidades de la ciencia de ingeniería de software es la construcción sistemática de un conjunto de pruebas (test) de entradas que permitan descubrir errores.

Si las pruebas revelan un error (bug), el proceso de depuración -detección, localización, corrección y verificación- se puede iniciar. Es importante advertir que aunque las pruebas puedan detectar la presencia de un error, no necesariamente implica la ausencia de errores. Por consiguiente, el hecho de que una prueba o test, revele la existencia de un error no significa que uno indefinible pueda existir en otra parte del programa. 

Para atrapar y corregir errores de un programa, es importante desarrollar un conjunto de datos de prueba que se puedan utilizar para determinar si el programa proporciona respuestas correctas. De hecho, una etapa aceptada en el desarrollo formal de software es planificar los procedimientos de prueba y crear pruebas significativas antes de escribir el código. Los procedimientos de prueba de un programa deben de examinar cada situación posible bajo la cual se ejecutará el programa. El programa debe de ser comprobado con datos en un rango razonable así como en los límites y las áreas en las que el programa indique al usuario que los datos no son válidos. El desarrollo de buenos procedimientos y datos de prueba en problemas complejos pueden ser más difíciles que la escritura del propio código del programa. 

Verificación y validación

La prueba del software es un elemento de un tema más amplio que suele denominarse verificación y validación. Verificación es el conjunto de actividades que aseguran que el software funciona correctamente con una amplia variedad de datos. Validación es el conjunto de diferente de actividades que aseguran que el software  construido se corresponde con los requisitos del cliente.

En la práctica, la verificación pretende comprobar que los documentos del programa, los módulos y restantes unidades son correctos, completos y consistentes entre sí y con los de las fases precedentes; la validación a su vez, se ocupa de comprobar que estos productos se ajustan a la especificación del problema. Boehm estableció que la verificación era la respuesta a: "¿Estamos construyendo el producto correctamente?" mientras que la validación era la respuesta a: "¿Estamos construyendo el programa correcto?".

En la prueba de software convencional es posible aplicar diferentes clases de pruebas. Pressman distingue los siguientes:
  • Prueba de  unidad: se centra el esfuerzo de verificación en la unidad más pequeña del diseño del software, el componente o módulo (función o subprograma) de software que se comprueban individualmente.
  • Prueba de integración: se comprueba si las distintas unidades del programa se han unido correctamente. Aquí es muy importante descubrir errores asociados con la interfaz.
En el caso de software orientado a objetos cambia el concepto de unidad que pasa a ser la clase o la instancia de una clase (objeto) que empaqueta los atributos (datos) y las operaciones (funciones) que manipulan estos datos. La prueba de la clase en el software orientado a objetos es la equivalente a la prueba de unidad para el software convencional. La prueba de integración se realiza sobre las clases que colaboran entre sí.

Otro tipo de pruebas importantes son las pruebas del sistema. Una prueba del sistema comprueba que el sistema global del programa funciona correctamente; es decir las funciones, las clases, las bibliotecas, etc. Las pruebas del sistema abarcan una serie de pruebas diferentes cuyo propósito principal es  verificar que se han integrado adecuadamente  todos los elementos del sistema y que realizan las funciones apropiadas. Estas pruebas se corresponden con las distintas etapas del desarrollo del software.

Elección de datos de prueba

Para que los datos de prueba sean buenos, necesitan cumplir dos propiedades :

  1. Se necesita saber cuál es la salida que debe de producir un programa correcto para cada entrad de prueba.
  2. Las entradas de prueba deben de incluir aquellas entradas que más probabilidad tengan de producir errores.
Aunque un programa se compile, se ejecute y produzca una salida que parezca correcta no significa que el programa sea correcto. Si la respuesta correcta es 211492 y el programa obtiene 211491, algo está equivocado. A veces, el método más evidente para encontrar el valor de salida correcto es utilizar lápiz y papel utilizando un método distinto al empleado con el programa. Puede ayudarle a utilizar valores de entrada más pequeños o simplemente valores de entrada cuya salida sea conocida.

Existen diferentes métodos para encontrar datos de prueba que tengan probabilidad de producir errores. Uno de los más utilizados se denomina valores de frontera. Un valor frontera de un problema es una entrada que produce un tipo de comportamiento diferente, por ejemplo la función C++.

int comprobar_hora(int hora)

// la hora de día esta en el rango 0 a 23, tiene dos valores frontera 0
// (referir a 0, no es válido) y 23 (superior a 23 no es válida ya que 24
// es un nuevo día); de igual modo si se deja contemplar el hacho de
// mañana (AM) o tarde (PM), los valores frontera serán 0 y 11, 12 y 23, //
respectivamente.

En general, no existen definiciones de valores frontera y es en las especificaciones del problema donde se pueden obtener dichos valores. Una buena regla suele ser la siguiente "si no puede comprobar todas las entradas posibles, al menos compruebe los valores frontera. Por ejemplo si el rango de entrada va de 0 a 500.000, asegure la prueba de 0 y 500.000, y será buena práctica probar 0, 1 y -1 siempre que sean valores válidos".







No hay comentarios:

Publicar un comentario