|

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)
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
|