Dice un conocido adagio que quien no sabe adónde va llega a otro lado. También es bien sabido que es necesario apuntar antes de disparar. ¿Cómo se puede construir programas sin especificarlos primero? Sin la especificación, no es posible asegurar que se ha obtenido lo que se necesita. En la práctica se recurre al método de prueba y error para determinar si la funcionalidad de una pieza de programación es adecuada. Aunque esta estrategia pospone la etapa de documentación de los módulos construidos, muchas veces no sirve para lograr producir sus especificaciones correctas y completas porque al final del proyecto se recorta el presupuesto para documentar el código [Boehm-1981].
La especificación puede verse como un contrato en el que están definidos todos los servicios que la implementación del módulo es capaz de dar. La firma o prototipo del módulo siempre forma parte de la especificación, pues si no se definen los tipos y parámetros con que trabaja el módulo es imposible compilarlo o reutilizarlo. Algunos autores consideran que una especificación correcta debe estar escrita en un lenguaje formal, matemático. Pero otros sólo requieren de la especificación un alto rigor, con estas tres cualidades:
En pocas palabras, en la especificación no debe sobrar ni faltar nada. Debe estar toda la información esencial para implementar un módulo, y también para usarlo. Además, es conveniente que la especificación sea clara y sencilla de entender, de forma que para usarla el programador no requiera hacer un esfuerzo intelectual más grande de lo necesario.
{ 1 } | template< class T> void list<T>::insert( list<T>::iterator p, const value_type & v ) | { 2 } | Agrega una copia del valor "v" al contenedor. |
{ 3 } | - El valor agregado queda antes de la posición "p" en el contenedor. - Si p==this->end() el valor queda al final de la lista. Precondición: <<< no tiene >>> Complejidad: O ( this->size() ) |
{ 4 } | {{ // test::insert() list<long> L = makeList_long( "(3)" ); L.push_front( 2 ); assertTrue( L == makeList_long( "(2 3)" ) ); L.push_front( 1 ); assertTrue( L == makeList_long( "(1 2 3)" ) ); L.push_back( 4 ); assertTrue( L == makeList_long( "(1 2 3 4)" ) ); L.push_back( 5 ); assertTrue( L == makeList_long( "(1 2 3 4 5)" ) ); L.insert( L.begin() , 0 ); L.insert( L.end() , 6 ); assertTrue( L == makeList_long( "(0 1 2 3 4 5 6)" ) ); list<long>::iterator it = L.begin(); while ( it != L.end() && (*it != 4) ) { ++it; } L.insert(it,4); assertTrue( L == makeList_long( "(0 1 2 3 4 4 5 6)" ) ); }} |
list<T>::insert()
En la Figura 1 está una especificación bastante completa porque incluye las partes que generalmente forma parte de un buen sistema de documentación:
{ 1 } El encabezado, firma o prototipo de un
módulo contiene la información que necesita el
compilador para traducir el programa. Incluye: (1) el tipo del
valor retornado, (2) el tipo de los parámetros y (3) el
nombre calificado del módulo. En el ejemplo de la
Figura 1 el nombre calificado de la
rutina es list<T>::insert()
por lo que el
compilador deduce que insert()
es un método de la clase
parametrizada list<T>
. Como el valor
retornado por insert()
es void
el
compilador reconoce que este método no retorna
ningún valor, que recibe una copia del objeto
list::iterator<T>
pero que, aunque recibe por
referencia el parámetro "v
", no puede
modificarlo porque "v
" es un parámetro
const
. El nombre calificado del tipo
"value_type
" es
list<T>::value_type
pues el lenguaje C++
permite usar abreviaciones en muchos contextos.
{ 2 } La descripción corta de la funcionalidad
del módulo generalmente cabe en un solo renglón y
dice qué hace el módulo. Usualmente con esta
descripción se define o refleja la
abstracción o esencia del componente de
programación, pero debido a que es una descripción
concisa usualmente no es suficientes para
reutilizarlo. En el ejemplo de la
Figura 1 la descripción corta
dice que "Agrega una copia del valor "v
" al
contenedor".
{ 3 } La descripción detallada contiene la
documentación que los programadores necesitan para usar el
módulo. Ahí se indican todos los detalles y reglas
que deben respetarse para que obtener los resultados correctos al
usarlo. Este es el sitio en donde el programador indica
cuáles son las
pre-condiciones que deben cumplir los valores antes de usar el
módulo y también deben quedar indicados
cuáles son los resultado o
post-condiciones. En el ejemplo de la
Figura 1 es en la descripción
detallada de la funcionalidad de la rutina en donde se aclara que
la inserción se realiza antes de la posición
"p
", pues sin esta aclaración esta lista no
funcionaría como lista sino que sería más
bien un conjunto
(std::set<>
).
{ 4 } El ejemplo de uso le permite al programador apreciar inmediatamente la funcionalidad del módulo. También le permite copiar el texto del ejemplo para reutilizarlo inmediatamente. Cuando los programadores usan una biblioteca están acostumbrados a ponerle más atención a los ejemplos de uso que a la narrativa que define el comportamiento de cada módulo. Dice un conocido adagio que una imagen vale por mil palabras; en computación, un ejemplo vale más o menos eso mismo. Si la especifiación no incluye ejemplos, en muchos casos los programadores encuentran difícil utilizarla.
Cualquier programador tiene el entrenamiento necesario para
producir los ítemes { 1 } y { 2 }
(prototipo + descripción corta). Para producir el ejemplo
de uso { 4 } se requiere usar alguna herramienta que
permita sirva para construir pruebas unitarias de módulos.
En un contexto universitario, conviene usar un el módulo
C++
BUnit
que tiene la gran
ventaja de que su implementación completa cabe en un
único archivo de encabezado
[DiM-2008c]. La implementación C++
BUnit.h
es un sencillo marco de prueba unitaria de módulos basado
en la herramienta
JUnit para Java
[BG-1998].
|