31 de septiembre de 2003, Vol. 4, No. 5. ISSN: 1607-5079
 
 
[contenidos de RDU...] [Ver ejemplares anteriores de RDU...] [Volver a la portada de RDU] [Busca en los archivos de RDU] [Recomienda RDU a un amigo]  

Gerardo Rossel
grossel@dc.uba.ar
[¿Cómo citar este artículo]
Andrea Manna
amanna@dc.uba.ar
[Bajar este artículo]

 

El diseño y la programación por contratos es en cierto sentido opuesto a lo que se denomina programación defensiva (Meyer,1992). Si bien existen diversas acepciones para el concepto de programación defensiva, se tomará aquí la idea por la cual la programación defensiva incita a los programadores a realizar todas las verificaciones posibles en sus rutinas de tal forma de prevenir que una llamada pueda producir errores. Como resultado, las rutinas deben ser lo más generales posible evitando aquellas que funcionen sólo si se las llama con determinadas condiciones. Si bien esto puede parecer razonable, tiende a aumentar drásticamente la complejidad del código. Como lo plantea Meyer en (Meyer,1992), los chequeos ciegos (aquellos hechos por las dudas) agregan más software por lo cual agregan más lugares donde las cosas pueden andar mal, lo cual da la necesidad de agregar más verificaciones, que a su vez agrega más software, lo cual.....así al infinito. Básicamente la idea de programación defensiva no apunta a que los elementos de software garanticen especificaciones bien conocidas sino que sean textos ejecutables en condiciones arbitrarias. Otro de los problemas (asociado al reuso de software) es que generalmente no queda claro que hacer con lo valores incorrectos. Con un ejemplo se pueden clarificar las cosas. Supóngase una clase A que implementa una rutina que a partir de la suma de valores que tiene guardados en un vector y un parámetro entero, devuelve la parte correspondiente (es decir la división de la suma de valores por el parámetro). A continuación, el código en Eiffel:

Por razones de simplicidad, sólo se muestra el código necesario para entender el ejemplo. La rutina get_parte, de acuerdo a la programación defensiva, verifica que el parámetro sea mayor que cero para de esta forma poder hacer la división sin problemas. ¿Qué tiene de malo?. Suponiendo que los clientes saben que el parámetro debe ser mayor que cero, necesitan hacer el mismo chequeo. Si no lo saben, se enterarán en tiempo de ejecución. Al llamarla con un valor menor a cero, obtendrán un mensaje de error. Por otro lado ¿cómo puede reusarse la clase en otro contexto? es decir: ¿un mensaje es la mejor solución para un parámetro erróneo en todos los contextos? Si se quiere reusar la clase para una aplicación que corra en modo background, el mensaje de error no es la mejor alternativa. Si la clase pertenece a una biblioteca de clases ¿es correcto que interactúe con los usuarios mediante mensajes?. La solución podrá ser que devuelva un código de error , por ejemplo –1, si el llamado es erróneo. Ahora bien ¿dónde se documenta eso?. ¿Y si se retorna una excepción?. En ese caso no hace falta el chequeo ya que la división por cero provocará la excepción. Pero bien ¿dónde está el error que provocó la excepción, en la rutina o en el llamador? Si está en la rutina, se debería corregir y si está en el llamador ¿cómo sabe éste cual es la forma correcta de llamar a la rutina?. La moraleja es que solamente el cliente tiene la información necesaria para operar en caso de tener un valor menor que cero.

La solución del Diseño por Contratos, basado en el principio de no redundancia, implica que se debe especificar claramente bajo que condiciones debe llamarse a la rutina (mediante las precondiciones) y a partir de ello se establece cual es el resultado (las poscondiciones). Las precondiciones deben ser parte de la interfaz de la rutina. Para el ejemplo anterior:

En este, caso el responsable de verificar que el valor del parámetro sea mayor a cero es el cliente de la rutina. La precondición forma parte de la interfaz y por lo tanto es visible al cliente. El siguiente es un cuadro de los contratos de software considerando las pre y poscondiciones.


Es posible establecer aserciones más fuertes que otras. Ahora bien ¿qué significa que una aserción es más fuerte que otra? Se puede definir, que dadas dos aserciones P y Q, P es más fuerte o igual que Q si P implica Q. El concepto de fortificar o debilitar aserciones es usado en la herencia cuando es necesario redefinir rutinas. En Eiffel (y en general en las herramientas disponibles para otros lenguajes) el lenguaje para soportar aserciones tiene algunas diferencias con el cálculo de predicados: no tiene cuantificadores (aunque el concepto de agentes provee un mecanismo para especificarlos) , soporta llamadas a funciones y además existe la palabra reservada old (usada en las poscondiciones) para indicar el valor anterior a la ejecución de la rutina de algún elemento. Supóngase una clase que representa una cuenta bancaria que cuenta con una rutina depositar que recibe un importe como parámetro y agrega ese importe al saldo:


La expresión old balance en este caso, indica el valor anterior a la ejecución de la rutina del balance. Es importante ver la diferencia existente entre la sentencia

agregar_deposito(sum)

y la expresión

balance = old balance + sum

El primer caso es prescriptivo y operacional, mientras el segundo es descriptivo y denotacional. Uno representa el cómo y otro representa el qué.

Como consecuencia directa de que las precondiciones forman parte de la interfaz de una rutina, surge la siguiente regla: Toda función que aparezca en la precondición de una rutina r debe estar disponible a todo cliente al cual esté disponible dicha rutina r. Si esto no fuera así, podría ser imposible para el cliente garantizar que se cumple la precondición antes de llamar a la rutina.

 

D.R. © Coordinación de Publicaciones Digitales. DGSCA-UNAM