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.