This is an old revision of the document!
Es cierto que el diseño de sistemas de software depende de la experiencia y, en alguna medida, también del talento y la creatividad. Sin embargo, existen algunas propiedades importantes en el diseño de sistemas. Por eso, estudiar y conocer estas propiedades de diseño puede ayudar en la concepción de sistemas con mayor calidad. En esta sección se estudian las siguientes propiedades de los diseños de software: integridad conceptual, ocultación de información, cohesión y acoplamiento. Además, se enunciarán a continuación algunos principios de diseño, los cuales representan directrices para garantizar que un diseño cumple con determinadas propiedades.
Integridad conceptual es una propiedad de diseño propuesta por Frederick Brooks, el mismo autor de la Ley de Brooks. El principio fue enunciado en 1975, en la primera edición del libro The Mythical Man-Month. Brooks sostiene que un sistema no puede ser una acumulación de funcionalidades sin coherencia ni cohesión entre ellas. La integridad conceptual es importante porque facilita el uso y la comprensión de un sistema por parte de sus usuarios. Por ejemplo, con integridad conceptual, el usuario acostumbrado a utilizar una parte de un sistema se siente cómodo al usar otra parte, ya que las funcionalidades y la interfaz implementadas a lo largo del producto son siempre consistentes.
Para citar un contraejemplo, es decir, un caso de ausencia de integridad conceptual, asumamos un sistema que utiliza tablas para presentar sus resultados. Dependiendo de la pantalla del sistema en la que se usen, estas tablas tienen diferentes diseños, en términos de tamaño de fuente, uso de negrita, espaciado entre líneas, etc. Además, en algunas tablas es posible ordenar los datos haciendo clic en el título de las columnas, pero en otras tablas esa funcionalidad no está disponible. Por último, los valores se muestran en diferentes monedas. En algunas tablas, los valores se refieren a reales; en otras tablas, se refieren a dólares. Esta falta de estandarización es una señal de falta de integridad conceptual y, como mencionamos, añade complejidad accidental en el uso y la comprensión del sistema
Enfatizamos el impacto de la falta de integridad conceptual en los usuarios finales de un sistema. Sin embargo, el principio también se aplica al diseño y al código de un sistema. En este caso, los afectados son los desarrolladores, quienes tendrán más dificultad para entender, mantener y evolucionar el sistema. A continuación, mencionamos ejemplos de falta de integridad conceptual a nivel de código:
Estos ejemplos revelan una falta de estandarización y, por lo tanto, de integridad conceptual. Son un problema porque dificultan que un desarrollador acostumbrado a mantener una parte del sistema pueda ser asignado para mantener otra parte.
Esta propiedad, una traducción de la expresión information hiding (ocultación de información), fue discutida por primera vez en 1972 por David Parnas, en uno de los artículos más importantes e influyentes en el área de Ingeniería de Software de todos los tiempos, cuyo título es “On the criteria to be used in decomposing systems into modules”.
Este artículo discute la modularización como un mecanismo capaz de hacer que los sistemas de software sean más flexibles y fáciles de entender y, al mismo tiempo, reducir su tiempo de desarrollo. La efectividad de una determinada modularización depende del criterio utilizado para dividir un sistema en módulos David Parnas, “On the criteria to be used in decomposing systems into modules”
Aviso: Parnas usa el término módulo en su artículo, pero esto fue en una época en que la orientación a objetos aún no había surgido, al menos como la conocemos hoy. En este capítulo, escrito casi 50 años después del trabajo de Parnas, optamos por el término clase, en lugar de módulo. El motivo es que las clases son la principal unidad de modularización en lenguajes de programación modernos como Java, C++ y Ruby. Sin embargo, el contenido del capítulo se aplica a otras unidades de modularización, incluidas aquellas más pequeñas que las clases, como métodos y funciones, y también a unidades más grandes, como paquetes y componentes.
El ocultamiento de información aporta las siguientes ventajas a un sistema:
No obstante, para alcanzar los beneficios mencionados, las clases deben cumplir con la siguiente condición (o criterio): deben ocultar decisiones de diseño que están sujetas a cambios. Debemos entender una decisión de diseño como cualquier aspecto del diseño de la clase, como los requisitos que implementa o los algoritmos y estructuras de datos que se usarán en su código. Por lo tanto, el ocultamiento de información recomienda que las clases deben ocultar detalles de implementación que estén sujetos a cambios. Modernamente, los atributos y métodos que una clase pretende encapsular se declaran con el modificador de visibilidad privado, disponible en lenguajes como Java, C++, C# y Ruby.
Sin embargo, si una clase encapsula toda su implementación, no será útil. Dicho de otra manera, una clase, para ser útil, debe hacer públicos algunos de sus métodos, es decir, permitir que puedan ser llamados por código externo. El código externo que llama a los métodos de una clase se denomina cliente de la clase. También decimos que el conjunto de métodos públicos de una clase define su interfaz. La definición de la interfaz de una clase es muy importante, ya que constituye su parte visible.
Las interfaces deben ser estables, porque los cambios en la interfaz de una clase pueden requerir actualizaciones en sus clientes. Para ser más claro, supongamos una clase Math, con métodos que realizan operaciones matemáticas. Supongamos un método sqrt que calcula la raíz cuadrada de su parámetro. Supongamos además que la firma de este método se modifica, por ejemplo, para devolver una excepción si el valor del parámetro es negativo. Esta modificación tendrá un impacto en todo el código cliente del método sqrt, que deberá ser modificado para manejar la nueva excepción.
Ejemplo:
A continuación un trecho de código para un sistema de estacionamiento en su primera versión. Identifique el problema en relación a la propiedad de diseño de ocultamiento de la información.
import java.util.Hashtable; public class Estacionamiento { public Hashtable<String, String> vehiculos; public Estacionamiento() { vehiculos = new Hashtable<String, String>(); } public static void main(String[] args) { Estacionamento e = new Estacionamiento(); e.vehiculos.put("TCP-7030", "Uno"); e.vehiculos.put("BNF-4501", "Gol"); e.vehiculos.put("JKL-3481", "Corsa"); } }
Esta clase tiene un problema de exposición excesiva de información o, en otras palabras, no oculta estructuras que pueden cambiar en el futuro. Específicamente, la tabla hash que almacena los vehículos estacionados en el estacionamiento es pública. Como resultado, los clientes —como el método main— tienen acceso directo a ella para, por ejemplo, añadir vehículos al estacionamiento. Si en el futuro decidimos usar otra estructura de datos para almacenar los vehículos, todos los clientes deberán ser modificados.
Supongamos que el sistema de estacionamiento fuera manual, con los nombres de los vehículos anotados en una hoja de papel. Haciendo una comparación, esta primera versión de la clase Estacionamiento correspondería —en el caso de este sistema manual— a que el cliente del estacionamiento, después de estacionar su vehículo, entrara en la cabina de control y escribiera él mismo la patente y el modelo de su vehículo en la hoja de control.
La siguiente versión de la clase es mejor, ya que encapsula la estructura de datos responsable de almacenar los vehículos. Ahora existe el método estaciona para estacionar un vehículo. Con esto, los desarrolladores de la clase tienen la libertad de cambiar la estructura de datos sin causar impacto en sus clientes. La única restricción es que la firma de estaciona debe ser preservada.
import java.util.Hashtable; public class Estacionamiento { private Hashtable<String,String> vehiculos; public Estacionamiento() { vehiculos = new Hashtable<String, String>(); } public void estaciona(String patente, String vehiculo) { vehiculos.put(patente, vehiculo); } public static void main(String[] args) { Estacionamiento e = new Estacionamiento(); e.estaciona("TCP-7030", "Uno"); e.estaciona("BNF-4501", "Gol"); e.estaciona("JKL-3481", "Corsa"); } }
En resumen, esta nueva versión oculta una estructura de datos - sujeta a alteraciones durante la evolución del sistem - y proporciona una interfaz estable para los clientes da la clase - representada por el método estaciona.