Conceptos
Básicos en Ingeniería de Software I
Cualidades del Software
Correctitud,
Confiabilidad y Robustez
Comenzaremos
la presentación de los conceptos básicos con tres cualidades
íntimamente ligadas entre sí: Correctitud, Confiabilidad
y Robustez.
Todo
proyecto de desarrollo de software debe ya en sus primeras etapas especificar
el alcance y los requerimientos a cumplir en el producto terminado. En
este sentido la más básica de las cualidades es la Correctitud,
que simplemente significa que el programa cumple con los requerimientos
especificados en el análisis.
Sería
ingenuo pensar que el análisis de requerimientos hecho en primera
instancia cubre todos las alternativas posibles. De hecho la realidad siempre
supera nuestras previsiones por más cuidadosas que éstas
sean y aunque claro está un análisis riguroso puede prevenirme
de más problemas que uno exiguo, un buen diseño no puede
acabar en él. Es precisamente aquí donde entran la segunda
y tercera de las cualidades.
En
un caso ideal diremos que un software es confiable si cumple con los requerimientos
especificados y no ocasiona graves problemas frente a situaciones imprevistas.
La idea detrás de la Confiabilidad es que quizás aunque en
ciertas ocasiones el programa reaccione en forma inesperada, el usuario
se encuentra cómodo usándolo ya que estos fallos no traen
aparejados problemas graves. Todo el mundo es consciente de los bugs presentes
en muchos programas populares como ms-word pero aún así la
mayoría de la gente los usa entre otras cosas porque son confiables.
Distinta sería la situación si una caída en el ms-word
implicara la corrupción del file system por ejemplo; sin duda la
popularidad del programa no sería la misma aún cuando los
fallos se produjeran por las mismas causas. En un caso idílico podemos
suponer que todo proyecto de software implica un cuidadoso análisis
de requerimientos especificado entre usuarios y analistas; por lo tanto
a nuestros efectos todo programa confiable es correcto.
La
Confiabilidad implicaba un comportamiento aceptable frente a situaciones
inesperadas; pensemos cuanto mejor sería si pudiéramos en
la fase de desarrollo fijar comportamientos frente a situaciones no previstas
en la fase de análisis, pero ya evidentes durante el desarrollo.
Esto es precisamente lo que definiremos como Robustez. En otras palabras
un programa es robusto si reacciona en forma adecuada frente a situaciones
a priori imprevistas. Claramente un programa robusto es también
confiable y por ende correcto.
En
un proyecto de propósito muy general como el nuestro, es difícil
garantir cualquiera de estas propiedades. Aún así hemos puesto
mucho esmero en cumplirlas, apoyándonos en un desarrollo sumamente
modular que ha sido exigentemente verificado, delegando una importante
parte de los chequeos al compilador, realizando cada clase sus propios
chequeos de consistencia, apoyándonos en componentes de alto nivel
para la implementación de diversas estructuras y algoritmos (e.g.
STL y Design Patterns) y complementado todo lo anterior con mecanismos
de manejo de excepciones para los casos más patológicos.
Legibilidad
Cuando
hablamos de Legibilidad nos referimos a la facilidad para interpretar como
funciona el programa. Podemos dividirlo en varias partes: el código,
la arquitectura, etc. En general la Legibilidad concierne exclusivamente
a los desarrolladores ya que al usuario final rara vez le interesa ver
el código o estudiar la arquitectura del programa que usa. En nuestro
caso la situación es radicalmente diferente a la expuesta; de hecho
es poco probable que un usuario de Bicoti pueda explotar adecuadamente
el potencial de la biblioteca sin tener un buen conocimiento de como está
estructurada. Como veremos en el diseño, los requerimientos solo
pueden cumplirse razonablemente usando el código fuente en todo
momento para crear aplicaciones; e incluso agregando clases y módulos
en ciertos casos. Por lo anterior es claro que la Legibilidad es una cualidad
esencial a incluir si aspiramos a concluir el proyecto con éxito.
En
Bicoti hemos incluido diversos elementos que mejoran la Legibilidad.
En el código hemos hecho
un importante esfuerzo de estandarización en nombres de variables,
clases y funciones, estructura de módulos, comentarios varios y
hemos delegado mucho del trabajo de bajo nivel a una biblioteca estándar
del C++ como la STL.
En la arquitectura hemos invertido
mucho tiempo en diseñar la misma en forma clara y prolija dando
en todos los casos posibles, soluciones estándar de alto nivel en
la jerarquía de clases para los problemas, apostando a la abstracción
como herramienta para mejorar la claridad.
Complementamos
lo anterior con documentación detallada y precisa, tanto de alto
como de bajo nivel, así como con diversos ejemplos que esperemos
faciliten la lectura.
No
terminaremos sin remarcar que los usuarios regulares de Bicoti deben hacer
un esfuerzo de su parte por entender como y por que se han hecho las cosas
de cada forma, en complemento al puesto de nuestra parte en clarificarlas.
Evolutividad
y Flexibilidad
Evolutividad
y Flexibilidad son dos cualidades interdependientes. La primera se refiere
a la posibilidad de extender el programa para adaptarse a requerimientos
más amplios y la segunda para variarlos, dentro de lo que podríamos
llamar un dominio de aplicaciones en ambos casos. Detrás de ambas
cualidades está implícita la idea de diseño para el
cambio, básica para cumplir las metas de re-uso de código
y arquitectura.
Estas
dos cualidades son imprescindibles en nuestro proyecto ya que forman parte
de nuestros requerimientos más directos como veremos más
adelante; posiblemente hayan sido además las que presentaron mas
carencias en la biblioteca anterior trayendo como consecuencia inevitable,
la independencia en los desarrollos y reduciendo los proyectos a esfuerzos
individuales aislados que terminaban de hecho en la mayoría de los
casos, en el momento que finalizaba el proyecto en cuestión.
Reparabilidad
y Mantenibilidad
Para
presentar ambos conceptos nos apoyaremos en un ejemplo que se presentó
durante la implementación. Durante el diseño habíamos
adoptado la convención que las coordenadas de un pixel de la imagen,
fueran de tipo entero sin signo; pero de todos modos hacíamos un
chequeo de consistencia en las funciones de las clases que recibían
este tipo de parámetros. Descubrimos chequeando éstas funciones
que algunos compiladores no reaccionaban como habíamos esperado
cuando recibían los parámetros fuera del tipo predefinido
y de hecho hacían casting, cambiando completamente el efecto esperado
como resultado de estas funciones. Al notar el problema, cambiamos la convención
original y pasamos a aceptar tipos enteros como coordenadas en los cabezales
de las funciones, dejando solo los chequeos internos. En los hechos cambiar
el tipo de las variables en cuestión, se redujo a poco más
que un find & replace en un puñado de clases.
En
forma similar podríamos mencionar muchos ejemplos de las mismas
características. La nueva cualidad que en cierta forma estábamos
poniendo a prueba con la situación anterior es la que llamaremos
Reparabilidad, que es la posibilidad de corregir los defectos del software
con un limitado gasto de trabajo.
La
Mantenibilidad es similar a la anterior, pero no está vinculado
a la solución de bugs sino a cambios que no aparecían en
la especificación original o que fueron establecidos en forma incorrecta.
En
el ciclo de vida de todo producto de software, el tiempo de mantenimiento
es un componente importante del tiempo total, por lo que ambas cualidades
son vitales en cualquier programa y claro está Bicoti no es la excepción.
Performance
del Software y Eficiencia en el desarrollo
En
esencia diremos que un programa tiene una mejor Performance que otro si
realiza el mismo trabajo en menor tiempo. Esta cualidad que básicamente
es de comparación, es sin duda la más objetiva de todas.
En programas que por su naturaleza demandan pocos recursos, la Performance
no es una cualidad vital. Muchas de las aplicaciones de imágenes
implican un importantísimo costo de cálculo por lo que la
Performance ha sido sumamente contemplada en Bicoti. Se han realizado varias
pruebas de Performance contra otros softwares entre las que destacamos
Blitz y la biblioteca vieja.
Con
la biblioteca vieja, las pruebas de Performance fueron similares así
que no nos extenderemos en comentarios a este respecto.
Blitz
es una biblioteca básicamente diseñada para álgebra
de matrices; sus desarrolladores prometen una Performance similar a la
de Fortran en este tipo de operaciones, con las ventajas de programación
de C++. A modo de adelanto de diseño, podemos pensar que Bicoti
implementa todos sus algoritmos sobre una capa estándar, que me
independiza entre otras cosas de la dimensión de la imagen en que
trabajo y del tipo de implementación que se ha elegido para esta.
Las comparaciones de Performance arrojaron resultados dispares dependiendo
del tipo de operaciones realizadas. Cuando las pruebas se hicieron sobre
operaciones algebraicas en las imágenes, Bicoti perdió claramente
frente a Blitz por un factor de 2 o 3 veces; sin embargo las pruebas sobre
otros tipos de operaciones propias de las imágenes como los filtros
lineales, dejaron a Bicoti mejor parado en una relación similar.
Estos resultados pueden parecer desconcertantes a primera vista, pero en
realidad son bastante naturales si pensamos en los objetivos de cada una.
Blitz está diseñada exclusivamente para operaciones algebraicas
y las implementaciones de estas están íntimamente ligadas
a las estructuras de datos usadas. En Bicoti por el contrario las operaciones
algebraicas integran una de varias familias de algoritmos, así que
si quisiéramos comparar en un caso general, sería más
justo usar otro tipo de operaciones como los filtros lineales por ejemplo.
Esta interpretación muestra que en realidad la Performance media
en Bicoti es buena y si bien es cierto que sería aún mejor,
si los algoritmos estuvieran implementados específicamente en cada
dimensión y tipo de implementación, esto iría en desmedro
de otras cualidades a pesar a la hora del diseño.
Creemos
que la discusión anterior es muy útil para presentar otra
cualidad no menos importante, la Eficiencia en el desarrollo o Productividad.
La Eficiencia también refleja comparaciones de tiempo, pero en este
caso los tiempos tenidos en cuenta son ya no los de ejecución del
programa una vez compilado, sino los de desarrollo del mismo. Si la obsesión
de un programador fuera la Performance, quizás debería optar
por usar Fortran (o si queremos ser más dramáticos aún,
Assembler) para desarrollar sus aplicaciones de imágenes. Seguramente
nadie preferiría esta última alternativa aún cuando
la misma implique una mejora de Performance de un factor de 2 o 3, a costa
entre muchas razones de demorar semanas en escribir un programa, que probablemente
no pueda re-usarse en el futuro ni aún por el mismo programador
y que con certeza no podrá ser portado a otra arquitectura. Es en
esta cualidad donde esperamos que Bicoti supere ampliamente a las experiencias
anteriores, no tanto quizás en aplicaciones concretas sino mas bien
en el desarrollo a largo plazo, por el re-uso de algoritmos. Es también
para velar por esta cualidad que en genral hemos decido no incluir algoritmos
de ningún tipo en las estructuras que almacenan datos.
Amigabilidad
Un
sistema es Amigable, si los usuarios humanos lo encuentran fácil
de usar. Comúnmente se asocia la Amigabilidad a la interfase gráfica,
aunque el concepto es más general. En Bicoti-I no existe interfase
gráfica alguna así que nos limitaremos a la Amigabilidad
en el uso de la biblioteca. Esta cualidad en contraste con las anteriores
es sumamente subjetiva y varía en su evaluación no solo de
un usuario a otro, sino también para un mismo usuario en distintos
instantes de tiempo. Existen dos formas de usar Bicoti-I; una primera que
podríamos llamar de Prototipación y la de Desarrollo.
Comentaremos
ambas formas comenzando por la segunda. El Desarrollo es la forma cruda
de usar la biblioteca. El usuario a este nivel no puede evitar enfrentarse
a la compleja arquitectura del Framework. Debe crear explícitamente
varios objetos para realizar operaciones que en principio parecieren sencillas.
Puede agregar clases nuevas al Framework pero debe hacerlo en forma consistente
con el resto, así que en general podemos pensar que este uso se
hará fundamentalmente en los casos que se intenta incorporar componentes
nuevos, una vez probados los algoritmos y siendo conscientes que estos
funcionan correctamente. Los programadores de Desarrollo deben tener un
buen conocimiento de la arquitectura y el por que de la misma, pero también
deben ser programadores experimentados en C++ y manejar ideas básicas
de Ingeniería de Software.
La
Prototipación sin duda no es tan exigente hacia los usuarios como
el Desarrollo. Aquí es mucho más importante tener un buen
conocimiento de Tratamiento de Imágenes, ya que los detalles de
arquitectura se han escondido en lo posible detrás de un conjunto
de funciones de alto nivel que resuelven internamente muchas de las dificultades.
Estos usuarios no ven a Bicoti como un complejo Framework, sino como una
biblioteca con un conjunto bien definido de datos y algoritmos. El usuario
simplemente desarrolla sus aplicaciones finales si estas solo implican
usar la algorítmica existente; o bien diseña alguna funcionalidad
nueva, o quizás especifica alguna nueva propiedad de interés.
En el segundo caso es necesario una vez seguros del correcto funcionamiento,
integrar a Bicoti los elementos nuevos para lo que no cabrá otra
posibilidad que hacerlo en modo de Desarrollo. No hay que subestimar la
utilidad de este último paso; la experiencia recogida en la biblioteca
vieja permite afirmar, que aquellos componentes que no se integren realmente
a la arquitectura, no serán usados en el futuro por otros grupos
de trabajo. La integración, de hacerse en forma correcta, obligará
al implementador a generalizar sus algoritmos para cubrir aspectos que
quizás fueron pasados por alto, debido a las circunstancias particulares
a las que este debió enfrentarse.
No
queremos que quede la idea que el modo de prototipación es la cara
amigable de Bicoti, en contraste con el de Desarrollo. En ambos casos se
ha puesto esfuerzo por hacer amigable el uso; la diferencia principal radica
en el hecho que el modo de desarrollo implica en general la resolución
de problemas mucho más generales y complejos.
Portabilidad
La
Portabilidad es la capacidad de llevar exitosamente el sistema a otro entorno.
Este fue desde el principio uno de nuestros principales objetivos, que
hemos alcanzado adaptándonos estrictamente a los estándares
de C++ en lo que a sintaxis se refiere, así como al uso de bibliotecas
como la Standard Template Library. Durante todo el proyecto hemos probado
todo el software simultáneamente sobre Unix y Windows hasta lograr
soluciones compatibles y por lo tanto tenemos la convicción que
este objetivo a sido alcanzado con éxito.