Cuando hablamos de contratos o programación por contratos, nos referimos a la necesidad de estipular tanto lo que necesita como lo que devuelve nuestro código.
Las condiciones que deben estar dadas para que el código funcione las llamamos precondiciones y las condiciones sobre el estado en que quedan las variables y él o los valores de retorno, las llamamos postcondiciones.
En definitiva, este concepto es similar al ya mencionado con respecto a la documentación de funciones, es decir que se debe documentar cómo deben ser los parámetros recibidos, cómo va a ser lo que se devuelve, y qué sucede con los parámetros en caso de ser modificados.
Esta estipulación es mayormente para que la utilicen otros programadores, por lo que es particularmente útil cuando se encuentra dentro de la documentación. En ciertos casos, además, puede quererse que el programa revise si las condiciones realmente se cumplen y de no ser así, actúe en consecuencia.
Existen herramientas en algunos lenguajes de programación que
facilitan estas acciones, en el caso de Python, es posible utilizar la
instrucción assert
.
10.1.1. Precondiciones
Las precondiciones son las condiciones que deben cumplir los parámetros que una función recibe, para que esta se comporte correctamente.
Por ejemplo, en una función división las precondiciones son que los
parámetros son números, y que el divisor sea distinto de 0
. Tener una
precondición permite asumir desde el código que no es necesario lidiar
con los casos en que las precondiciones no se cumplen.
10.1.2. Postcondiciones
Las postcodiciones son las condiciones que cumplirá el valor de retorno, y los parámetros recibidos, en caso de que hayan sido alterados, siempre que se hayan cumplido las precondiciones.
En el ejemplo anterior, la función división con las precondiciones asignadas, puede asegurar que devolverá un número correspondiente al cociente solicitado.
10.1.3. Aseveraciones
Tanto las precondiciones como las postcondiciones son aseveraciones (en inglés assert). Es decir, afirmaciones realizadas en un momento particular de la ejecución sobre el estado computacional. Si llegaran a ser falsas significaría que hay algún error en el diseño o utilización del algoritmo.
Para comprobar estas afirmaciones desde el código en algunos casos
podemos utilizar la instrucción assert
, está instrucción recibe una
condición a verificar y, opcionalmente, un mensaje de error que
devolverá en caso que la condición no se cumpla.
>>> n=0
>>> assert n!=0, "El divisor no puede ser 0"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: El divisor no puede ser 0
Advertencia Es importante tener en cuenta que assert
está pensado para ser usado en la etapa de desarrollo. Un programa terminado nunca debería dejar de funcionar por este tipo de errores.
10.1.4. Ejemplos
Usando los ejemplos anteriores, la función division nos quedaría de la siguiente forma:
def division(dividendo, divisor):
""" Calculo de la división
Pre: Recibe dos números, divisor debe ser distinto de 0.
Post: Devuelve un número real, con el cociente de ambos.
"""
assert divisor != 0, "El divisor no puede ser 0"
return dividendo / ( divisor * 1.0 )
Otro ejemplo, tal vez más interesante, puede ser una función que implemente una sumatoria. En este caso hay que analizar cuáles van a ser los parámetros que recibirá la función, y las precondiciones que estos parámetros deberán cumplir.
La función sumatoria a escribir, necesita de un valor inicial, un valor final, y una función a la cual llamar en cada paso. Es decir que recibe tres parámetros.
def sumatoria(inicial, final, f):
Tanto inicial
como final
deben ser números enteros, y dependiendo de
la implementación a realizar o de la especificación previa, puede ser
necesario que final
deba ser mayor o igual a inicial
.
Con respecto a f
, se trata de una función que será llamada con un
parámetro en cada paso y se requiere poder sumar el resultado, por lo
que debe ser una función que reciba un número y devuelva un número.
La declaración de la función queda, entonces, de la siguiente manera.
def sumatoria(inicial, final, f):
"""Calcula la sumatoria desde i=inicial hasta final de f(i)
Pre: inicial y final son números enteros, f es una función que
recibe un entero y devuelve un número.
Post: Se devuelve el valor de la sumatoria de aplicar f a cada
número comprendido entre inicial y final.
"""
Ejercicio 10.1.1. Realizar la implementación correspondiente a la
función sumatoria
.
En definitiva, la documentación de pre y postcondiciones dentro de la documentación de las funciones es una forma de especificar claramente el comportamiento del código de forma que quienes lo vayan a utilizar no requieran conocer cómo está implementado para poder aprovecharlo.
Esto es útil incluso en los casos en los que el programador de las funciones es el mismo que el que las va a utilizar, ya que permite separar responsabilidades. Las pre y postcondiciones son, en efecto, un contrato entre el código invocante y el invocado.