Conceptos Básicos en Ingeniería de Software II

Principios de la Ingeniería Software


Abstracción

Es siempre importante e incluso imprescindible en muchos casos dividir un proyecto en partes de acuerdo a los aspectos y objetivos de este, como estrategia para su resolución. Cuando nos enfrentamos a un problema complejo, dividir el mismo en partes adecuadamente elegidas suele ser la diferencia entre resolverlo o no. La Abstracción es el proceso mediante el cual se identifican los aspectos importantes de un fenómeno ignorando sus detalles. De esta forma el diseñador puede reducir un problema a interacciones entre un grupo de entidades con comportamientos establecidos para cumplir con los objetivos iniciales. Estas entidades son a su vez nuevos problemas, aunque claro está de menores dimensiones que el original. Algunos de ellos posiblemente puedan resolverse en forma directa al tener una complejidad accesible al entendimiento humano, mientras que otras quizás requieran su descomposición en nuevas abstracciones, en un proceso iterativo mediante el que establecemos niveles de abstracción, para concentrarnos en los aspectos del problema que nos interesan en un momento particular. La abstracción como herramienta para hacer frente a problemas complejos es usada constantemente no solo en la Ingeniería. Para un microbiólogo una célula humana es una entidad viva que realiza complejos procesos químicos, mientras que para un médico es una unidad que interactúa con infinidad de otras para conformar otro organismo más complejo. Ambos tienen visiones diferentes de la misma entidad simplemente porque se mueven en distintos niveles de abstracción, lo que no implica que uno tenga un conocimiento más profundo de las células humanas; sencillamente ambos conocen los aspectos de esta necesarios para cumplir su trabajo.
Los lenguajes mismos de programación son abstracciones, en este caso del hardware subyacente y le permiten al programador pensar en variables en lugar de direcciones de memoria por ejemplo. Los seres humanos tienen importantes carencias para manejar muchos detalles en forma simultánea, sin embargo están bien preparados para entender abstracciones en diversos niveles. Podemos pensar en la Abstracción como el mecanismo fundamental de los que nos doto la naturaleza, para resolver problemas complejos y por lo tanto una cualidad esencial en un lenguaje de programación es que este permita definir distintos niveles de abstracción. Al poco tiempo del nacimiento de la informática, se había vuelto claro que los lenguajes debían proveer mecanismos de abstracción y fue así que los lenguajes incorporaron tipos estructurados de datos, funciones y procedimientos. Sin embargo los que hayan programado alguna vez en Pascal por ejemplo, estarán de acuerdo que los refinamientos sucesivos de funciones para resolver los problemas, no reflejan precisamente nuestra idea de abstracción. En realidad de mucho mayor éxito han sido otros lenguajes como C en su tiempo, que nos da mayor abstracción a través de las dos siguientes herramientas o como C++ en la actualidad, que nos lleva aún más lejos.

Modularidad

Un sistema complejo puede ser dividido en piezas más simples llamadas módulos. Cada módulo es una entidad que al ser elegida en forma adecuada, nos permite marcar dos niveles de abstracción: uno que concierne a los detalles de implementación de este y otro en el que lo importante es su interacción con el resto de los módulos del sistema. Para que los grados de abstracción finales sean adecuados, los módulos deben tener alta cohesión interna y baja conexión con el resto. Si existen familias de datos o funciones poco relacionadas dentro de un módulo o sea si el módulo presenta baja cohesión, es probable que el diseñador se distraiga intentando entender detalles de funcionamiento innecesarios de una de las familias, cuando en realidad su interés está en otra, atentando por ende contra la facultad de abstracción del sujeto. En este caso probablemente la mejor decisión sería crear un módulo por familia y resolver las relaciones entre ellas mediante interacciones de los módulos resultantes. El caso contrario es aquel en el que dentro de un grupo de módulos existen intensas interacciones al punto que no puedo pensar en uno de ellos como una entidad aislada del resto. La situación anterior conocida como alta conexión, exige sin dudas las limitaciones del diseñador obligándolo a considerar en forma simultánea más aspectos de los que le interesa y por ende conspirando nuevamente contra su capacidad de abstracción. Una correcta modularización ayuda a aislar los errores más fácilmente y por lo tanto mejora la Reparabilidad que a su vez contribuye a tener programas más Correctos y Confiables. También mejora la Mantenibilidad si los cambios se limitan a los módulos, pudiendo mejorar la Robustez del sistema. Por último mejora la Eficiencia del proceso de producción no solo al facilitar la resolución del sistema, sino también contribuyendo a la paralelización de la producción en grupos de trabajo responsables de módulos.
Un buen procedimiento para determinar los módulos de un sistema consiste en dividir este en un conjunto manejable de módulo de baja conexión, y subdividir los módulos de baja cohesión en otros más pequeños recursivamente, dicho en otras palabras seguir una estrategia de Divide & Conquer manteniendo en cada paso baja conexión y alta cohesión entre módulos. El lenguaje C debe parte de su popularidad, a las virtudes para definir módulos en archivos y a mecanismos de alto nivel para compilar automáticamente sistemas de numerosos módulos. Existe la idea de módulo como archivo; esto es falso y aunque bien es cierto que frecuentemente hay archivos con módulos de un sistema, existen otras entidades con la misma propiedad como las clases por ejemplo.

Ocultamiento de la Información

En el punto anterior hablamos de la modularización y como esta potencia nuestras facultades de abstracción a la hora de resolver los problemas. Sin embargo la modularización por si sola puede contribuir poco a la abstracción, si no está complementada con el Ocultamiento de la Información en los módulos. En efecto pensar en un módulo como entidad abstracta, no tiene sentido si el uso de los recursos de este implica conocerlo en detalle. Pensemos en un módulo que maneja un puñado de consultas de un sistema en una base de datos; por más que toda la funcionalidad del módulo se encuentre en un entidad aislada (e.g. un archivo) será poca la abstracción de este que podré lograr, si para leer un dato en particular tengo que escribir una compleja consulta que involucra diversas tablas en una base distribuida. Lo ideal sería que el módulo presentara una interfaz de alto nivel, que se encargara de procesar el pedido al nivel de abstracción en que se piensa, sin tener que tener constantemente presente la implementación elegida. La posibilidad de crear puntos de acceso de alto nivel a los módulos no es suficiente para cumplir con el Ocultamiento de la Información. Es importante sobre todo en proyectos que involucran a más de una persona, que el lenguaje proporcione mecanismos de protección para evitar que por alguna razón otros usuarios no usen la interfaz estándar del módulo. Si esto pasara, la visión del módulo dependería desde que otro módulo lo uso y por lo tanto perdería carácter de entidad abstracta.
El ocultamiento de la información de un módulo mejora sin duda su Amigabilidad y en consecuencia la Eficiencia en el uso del mismo por otra persona. También mejora la Legibilidad en términos de arquitectura, contribuyendo a su vez a incrementan la Reparabilidad, Correctitud y Confiabilidad. El lenguaje C proporciona directivas para establecer cuales de las funciones y datos son visibles desde el exterior, pero solo en forma desasociada. Es aquí donde comienzan a marcarse las diferencias más importantes con su sucesor, el C++.

Tipos abstractos de datos

Un tipo abstracto de datos, es una estructura de datos asociada con sus operaciones en un módulo que oculta los detalles de implementación de todos los componentes. Por ejemplo el tipo "Lista Ordenada" es un una estructura que almacena una secuencia de datos para los que existe la comparación, o sea factible de ser ordenada y un conjunto de operaciones para su manejo como ser: Menor, Insertar, Mayor, etc. siendo estas funciones la única forma de alterar el estado de la lista. Tener tipos abstractos de datos sería como llegar al ideal de Modularización y Ocultamiento de Información. En forma similar podríamos hablar de los tipos abstractos "Lista", "Cola", etc pero el tipo "Lista Ordenada" presenta una particularidad, sus elementos deben ser a su vez Tipos Abstractos de Datos para los que este definida la función MayorQue, aún cuando los tipos almacenados puedan ser distintos. La propiedad anterior conocida como Polimorfismo será vista recurrentemente durante la documentación ya que es uno de los pilares en que se base nuestra biblioteca. Formalmente el polimorfismo significa que si alguna entidad envía un estímulo a otra, no necesita conocer en tipo de la misma; la entidad que recibe el estímulo se encarga de interpretarlo. El C++ nos acerca sustancialmente al concepto de tipo abstracto de datos, relajando un poco el principio de Ocultamiento de Información y limitando el Polimorfismo en aras de la herencia y el chequeo estricto de tipos respectivamente.

Generalidad

El principio de Generalidad consiste en atacar un problema más genérico que el que tengo entre manos. Como estrategia a largo plazo para el re-uso de código la Generalidad suele ser una buena inversión, mejorando directamente la Evolutividad y Flexibilidad, lo que en orden mejora a su vez la Mantenibilidad y la Robustez. Siempre es bueno en la medida que no comprometa demasiado la Performance por ejemplo, o retrase demasiado los tiempos de desarrollo en un proyecto de corta vida. Si el sistema tiene que funcionar por un largo tiempo ( que es lo común ) el tiempo invertido en Generalidad se recupera con creces y por lo tanto es un factor vital para la Eficiencia del ciclo de producción.
La generalidad en C++ está basada en tres potencialidades del lenguaje: El Polimorfismo, la herencia y el uso de Templates.