L8. Ejemplo de diseño modular en C++
Presentamos un ejemplo de diseño modular completo que hace uso de las clases stack
y list
.
En el ejemplo que vamos a ver, os proporcionamos una parte del diseño para que lo completéis en varias fases. Al final de cada fase, diseñaréis juegos de pruebas y probaréis el programa resultante en situaciones especiales.
Por otra parte, presentaremos la herramienta de documentación doxygen
. La hemos usado para generar las especificaciones de las clases del ejemplo. En documento aparte, os mostraremos una pequeña introducción a su uso.
Parte I: Implementación
Proyecto: Gestión de una lavadora
Consideremos una lavadora y una cubeta de ropa para lavar. Su operativa normal permite depositar una prenda de ropa tanto en la lavadora como en la cubeta. También existe la posibilidad de completar la lavadora con ropa de la cubeta.
Una lavadora puede estar inicializada o no. Todas las operaciones sobre lavadoras, salvo la inicialización y la escritura, se pueden aplicar solamente sobre lavadoras inicializadas. Los datos relevantes para inicializar una lavadora son el peso máximo que se pretende cargar en ella y si va a ser de ropa blanca o de color. Las prendas de ropa también tienen como atributos su peso y su color. Todos los pesos serán números naturales y la información del color puede representarse con un booleano (por ejemplo, el blanco mediante el valor false
y el color con el valor true
).
Cuando se desea completar una lavadora, se extrae de la cubeta la mayor cantidad posible de ropa del tipo correspondiente (blanco o color) que no se pase del peso máximo de la lavadora y sacando primero las prendas de ropa introducidas en último lugar.
Por último, se dispone de una operación que simula el lavado de las prendas que se encuentren en la lavadora en un momento dado. Se podrá aplicar incluso si la lavadora no está llena. Su resultado es que la lavadora queda no operativa y lista para inicializarse con nuevos datos.
El programa principal creará (o, mejor dicho, instalará) la cubeta y la lavadora y se encargará de aplicar las operaciones descritas, ofreciendo un menú de opciones:
Esquema de programa principal:
instalar lavadora
instalar cubeta
leer opción
while (opcion != final) {
leer datos opción
aplicar opción
leer opción
}
Según lo expuesto anteriormente, habrá cinco opciones básicas: inicializar una lavadora, depositar una prenda en la lavadora, depositar una prenda en la cubeta, completar la lavadora y realizar un lavado. Además, durante el proceso se podrán aplicar operaciones de escritura del contenido de la lavadora o la cubeta para supervisar el funcionamiento del programa.
Ejercicio 1: programa principal
Implementad el programa principal suponiendo que están disponibles las clases Lavadora
, Cubeta
y Prenda
. Los ficheros .hh
están en la carpeta de esta sesión (incluyen comentarios especiales para que doxygen pueda generar la documentación, ver sección 7.4). Los ficheros .o
se encuentran en la carpeta obj
. La documentación de las clases se encuentra en formato doxygen en la subcarpeta DOC
. Utilizad como plantilla el fichero pro2_especif.cc
que viene preparado para integrarse en la documentación. Utilizad el fichero Makefile
(que incluye su propia documentación) para compilar y enlazar este programa.
Probadlo con las entradas que queráis, incluyendo el fichero datos.txt
, a partir del cual tendréis que deducir la manera de organizar la lectura de los datos. Los resultados correctos para éste se encuentran en salida.txt
, a partir del cual tendréis que deducir la manera de organizar las escrituras. Los elementos de formato de salida que no se desprendan de las operaciones de escritura de las clases han de incorporarse en el programa principal. Respecto a la entrada, se han tomado los siguientes valores negativos como códigos de las diversas operaciones:
- inicializar lavadora (datos: peso máximo y color)
- añadir una prenda a la lavadora (datos: peso y color de la prenda)
- añadir una prenda a la cubeta (datos: peso y color de la prenda)
- completar la lavadora
- realizar un lavado
- escribir el contenido de la cubeta
- escribir el contenido de la lavadora
- fin del proceso
Por último, podéis suponer que los datos de la entrada son correctos. Si no lo fueran, habría que aplicar las protecciones correspondientes a las operaciones antes de utilizarlas, para garantizar que se cumplen sus precondiciones.
Ejercicio 2: juegos de pruebas especiales
Escribid y probad una serie de ficheros de datos que exploren diversas situaciones límite de la operación completar_lavadora
, por ejemplo:
- que no se pueda sacar ninguna prenda de la cubeta, aunque en ella haya prendas del color correspondiente, porque la primera prenda posible hace que se pase del peso máximo (la lavadora no se modifica)
- que se saquen de la cubeta todas las prendas del color correspondiente
- que la lavadora quede llena (se alcance el peso máximo exacto)
- que la primera prenda que no se pueda sacar de la cubeta haga que se alcance el peso máximo más 1.
Este tipo de pruebas, basadas solamente en las especificaciones de un programa, se denominan pruebas de caja negra (en inglés "black box testing").
Ejercicio 3: Implementación de clases.
- X64677: Gestión de una lavadora
Implementad vuestra propia versión de la clase Cubeta
de modo que el programa principal siga funcionando si en vez de enlazarlo con el Cubeta.o
de la carpeta obj
lo enlazáis con vuestra versión. Usad el fichero Cubeta.hh
, añadidle el invariante de la representación de la clase y generad vuestro propio Cubeta.cc
. Eso obligará a modificar el fichero Makefile
para que siga siendo útil en la nueva situación. Se han de añadir una regla de compilación para el nuevo fichero .cc
y se ha de revisar la regla de enlazado.
Para la operación completar_lavadora
tenemos una operación auxiliar completar_lavadora_pila
que extrae las prendas de una pila de prendas y las pase a la lavadora. La operación original simplemente elegirá sobre qué pila ha de aplicarse la auxiliar, en función del color de la lavadora. Proponemos dos versiones de dicha auxiliar, una recursiva y otra iterativa, ambas definidas como static
. Implementad y probad las dos versiones.
Aplicamos una descomposición similar para la operación escribir
, que requiere una operación auxiliar escribir_pila_prenda
, también static
. Es este caso, implementad solo la versión iterativa.
Realizad las pruebas con los mismos juegos empleados para probar el principal y después con otros nuevos, que tendréis que crear para probar situaciones especiales de la implementación que aún no hayan sido probadas. Este otro tipo de pruebas, que ya tienen en cuenta la implementación de un programa, se denominan pruebas de caja blanca (en inglés "white box testing").
Repetid el proceso con la implementación de las clases Lavadora
y Prenda
. El fichero Makefile
también se tendrá que modificar.
Ejercicio 4: Un ejercicio alternativo
Modificad la operación completar_lavadora
para que no pare de pasar ropa de la cubeta a la lavadora cuando encuentre una prenda que no quepa en ésta, sino que siga probando las prendas situadas por debajo de dicha prenda, pasando a la lavadora cada prenda encontrada que quepa en ella. Las prendas que queden en la cubeta han de mantener el orden en el que estaban.
Ejemplo: si en la lavadora faltan por llenar 7 unidades de peso de ropa blanca y en la cubeta tenemos las siguientes prendas blancas
1
2
2
4
1
3
2
al terminar han de quedar en la cubeta sólo las prendas de pesos
4
3
2
Diseñad una nueva serie de juegos de pruebas, con criterios similares a los de la anterior, para esta versión alternativa.
Parte II: Doxygen
Introducción al doxygen
doxygen es una aplicación que permite extraer y formatear documentación a partir de programas escritos en C++, Java, Python, etc. Su uso es muy fácil si no se aspira a personalizar demasiado el formato del documento resultante.
Si programamos con clases de C++, el input del Doxygen son nuestros ficheros .hh
y .cc
.
El uso de Doxygen se basa en colocar estratégicamente una serie de comentarios especiales en dichos ficheros. Dentro de los comentarios se pueden usar unas ciertas palabras reservadas o comandos de formateo tipo html. En PRO2 usaremos el Doxygen para documentar especificaciones y también para documentar programas completos.
En el primer caso, los comentarios se colocarán solo en los .hh
de las clases y en el .cc
que contenga el main, pues lo que documentaremos principalmente serán las operaciones de las clases, ya sea para facilitar su uso, o para guiar su diseño, en el caso de la práctica. En el segundo caso, también intervendrán los ficheros .cc
de las clases. La gran ventaja en ambos casos es que no tendremos que escribir un documento aparte del código y evitaremos mucha duplicidad de textos. Además, con muy poco esfuerzo por nuestra parte conseguiremos una documentación con un aspecto muy profesional.
Aspecto de la documentación generada por doxygen
El documento resultante con la documentación del proyecto se encuentra en la subcarpeta DOC
. Ésta, a su vez, contiene las carpetas HTML y LaTeX. Si queréis ver la documentación en HTML, id a la carpeta correspondiente y abrid el fichero index.html
. Si queréis ver una versión en pdf, id a la carpeta LaTeX y abrid el fichero refman.pdf
.
En la sesión mencionada se propone un ejercicio consistente en obtener el programa principal del proyecto. Más adelante os pediremos actualizar el documento de especificación mediante doxygen.
Uso de doxygen
En esencia, documentar una clase o un programa mediante doxygen solo requiere introducir en éstos unos comentarios especiales, acotados por los caracteres /**
y */
. Dichos comentarios serán procesados por doxygen para producir la documentación en los formatos que habéis visto en las carpetas HTML y LaTeX. Por ejemplo, el comentario típico de doxygen para una operación de una clase (en este caso, el afegir_nota
de la clase Estudiant
) tiene la forma:
/**
* @brief Modifica la nota d'un estudiant amb nota
*
* @pre el parametre implicit no te nota, 0 <= "nota" <= nota_maxima()
* @post la nota del parametre implicit passa a ser "nota"
*/
void afegir_nota(double nota);
Notad que solamente se diferencia de un comentario normal en que hay dos asteriscos en lugar de uno al abrir comentario.
Lo que empieza por @
o por \
son palabras reservadas, que han de ir seguidas de textos. @brief
viene seguida por una descripción breve de la operación, que será usada en varios lugares de la documentación. Si se quiere incluir una explicación más completa, ésta ha de ir separada por una línea en blanco. Dicha explicación adicional solo se usará en lo que se considera la "descripción detallada" de la operación.
Si no queremos usar las comillas para diferenciar el parámetro nota en los textos de este ejemplo, podemos usar <em>
, <b>
, etc. como en html
/**
* @brief Modificadora de la nota, para estudiantes sin nota
*
* @pre El parámetro implícito no tiene nota, <em>nota</em> es una nota válida
* @post La nota del parámetro implícito pasa a ser <em>nota</em>
*/
void afegir_nota(double nota);
La documentación generada por doxygen sale por defecto en los formatos html y latex. Se crean sendas carpetas con todos los ficheros necesarios. La carpeta de latex incluye un Makefile
para obtener una versión en pdf sin saber nada de latex.
Dicha documentación está estructurada alrededor de las clases y los ficheros que constituyen nuestro programa. Para cada clase sale un resumen y la ya mencionada "descripción detallada". Hay mecanismos para ocultar la implementación de la clase y otros detalles que no queramos que salgan en la documentación. Para cada fichero, se incluye información sobre la clase con la que está relacionado y, si se desea, un gráfico con las dependencias definidas por los #includes, que puede ser manipulado para parecerse a los diagramas modulares clásicos.
Las opciones de configuración de doxygen se definen en un fichero habitualmente llamado Doxyfile
. El que os incluimos en la carpeta de la sesión contiene todas las opciones necesarias para documentar las especificaciones del laboratorio y de la práctica de la asignatura. En esta sesión solo debéis tocarlo para modificar los datos identificativos de vuestro programa, definir las rutas a vuestras carpetas de trabajo o el idioma de vuestros documentos.
Para cada proyecto ha de prepararse un fichero que contendrá las rutas a los inputs y a los outputs, todas las opciones de formateo, ocultación de información, uso de gráficos, idioma, etc. Si ya hemos definido una "norma" para nuestra documentación, podemos utilizar copias de un mismo fichero, modificando solamente la identificación del proyecto (nombre, versión, etc) y las rutas a los inputs y a los outputs.
Supongamos que nuestro fichero de configuración se llama Doxyfile
(nombre típico y habitual) y que estamos en el directorio donde lo hemos copiado. Si escribimos esto en la línea de comandos:
doxygen Doxyfile
el programa irá a la carpeta donde le hemos dicho que está nuestro código C++, lo procesará y creará las carpetas html y latex en la ruta que le hayamos configurado.
Dentro de la carpeta html
encontraremos el fichero index.html
. Lo abrimos con un navegador y veremos la documentación generada. Dentro de la carpeta latex
encontraremos el fichero Makefile
. Ejecutando el comando
make
se generará el fichero refman.pdf
con la misma documentación, organizada como informe.
Como ejemplo, estas podrían ser la primeras opciones del Doxyfile
que definiríamos
DOXYFILE_ENCODING = utf-8
PROJECT_NAME = "Laboratori de PRO2. Exercici de prova del Doxygen."
PROJECT_NUMBER = "versio primavera 2023"
OUTPUT_DIRECTORY = "./DOC"
...
OUTPUT_LANGUAGE = Catalan
varias líneas más abajo aparece la otra parte que puede ser necesario retocar
INPUT = "."
INPUT_ENCODING = utf-8
Obviamente, cada uno tendrá que introducir los datos pertinentes de cada proyecto: identificación, carpetas de entrada y salida e idioma base de sus documentos.
Notad que hay dos lugares donde se especifican formatos de caracteres. Hay que tener mucho cuidado de usar los valores adecuados a nuestro entorno de trabajo, ya que de lo contrario pueden salir documentaciones con caracteres ilegibles en lugar de letras acentuadas, ñ, ç, etc. Por ejemplo, si vuestro editor de textos utiliza el formato unicode, deberéis usar el juego de caracteres utf-8
; si utiliza ascii
, tendréis que usar iso-8859-15
.
Por último, existe la posibilidad de seleccionar las extensiones y los nombres de los ficheros que doxygen ha de procesar dentro de la carpeta INPUT: por ejemplo para documentar la especificación de un proyecto basta con tratar ficheros .hh
o, todo lo más, ficheros .cc
cuyos nombres respondan a los siguientes esquemas: pro2*.cc
, main*.cc
, principal*.cc
. Obviamente, para documentar proyectos completos se han de procesar todos los ficheros .hh
y .cc
. Veremos como indicárselo a doxygen en una sesión posterior.
Observad que la documentación resultante incluye un diagrama modular de toda la solución y otro por cada clase. Dichos diagramas se construyen a partir de los #include de los ficheros implicados. Si queremos añadir o eliminar elementos tendremos que usar algún #include redundante o decirle a doxygen que no procese los #include que no deseamos ver reflejados en los diagramas.
Ejercicio 5: añadir un programa principal a la documentación de un proyecto
Generad una nueva documentación para el proyecto que incluya también el programa principal completo del primer ejercicio de la sesión, contenido en el fichero pro2_especif.cc
. Tanto éste como los ficheros Lavadora.hh
, Cubeta.hh
y Prenda.hh
ya contienen instrucciones para ser procesados por doxygen. Notad, por lo tanto, que para usar doxygen no es necesario que los programas y las clases sean definitivos, porque de momento no estamos usando los ficheros .cc
de las mismas. Sí que es deseable que el programa principal junto a los ficheros .hh
se compile sin errores.
Ejercicio 6: actualizar la documentación de una clase
Siguiendo las pautas del ejercicio alternativo de la sesión, modificad la especificación de la operación completar_lavadora
y actualizad la documentación usando doxygen. Examinad el resultado y comprobad que los cambios quedan reflejados correctamente.
Apéndice
La "biblia" del doxygen es el siguiente sitio web:
Es muy exhaustivo pero puede resultar un poco árido. Buscando en google se pueden encontrar fácilmente ejemplos sencillos, reportes de experiencias, etc.
La lista de palabras reservadas se puede encontrar en
http://www.doxygen.nl/manual/commands.html
Programas auxiliares:
- Para que sean visibles los diagramas modulares, se ha de instalar el paquete gráfico dot, lo podéis encontrar en http://www.graphviz.org.
- Para obtener la versión pdf de la documentación se ha de instalar el sistema de edición LaTeX y, en particular, los paquetes
pdflatex
yepstools
.