Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Introducción al Diseño por Contrato

Documentación estructurada, precondiciones, postcondiciones e invariantes informales

Universidad Nacional de Río Negro

Prerrequisitos: funciones, assert, condicionales y lectura de firmas en C.

Objetivos: 1. Diferenciar precondición, postcondición e invariante. 2. Documentar el contrato de una función pequeña.

Comprobación de salida: señalá qué obligación corresponde al llamador y cuál a la función en un contrato propuesto.

Introducción al Diseño por Contrato

Introducción

El Diseño por Contrato (Design by Contract, DbC) es una metodología de desarrollo de software formalizada por Bertrand Meyer. Propone que la interacción entre diferentes partes de un sistema (por ejemplo, el código que llama a una función y la función misma) se debe modelar como un contrato formal entre clientes y proveedores.

Un contrato establece derechos y obligaciones mutuas. En el contexto de las funciones, los componentes clave de este acuerdo son las precondiciones, postcondiciones e invariantes.


Precondiciones y Postcondiciones Informales

Precondiciones y postcondiciones son las dos caras de una misma obligación: qué debe garantizar quien llama a una función antes de invocarla, y qué debe garantizar la función a cambio, si esa condición de entrada se cumplió.

Precondiciones (El Derecho del Proveedor, Obligación del Cliente)

Una precondición especifica las condiciones que deben ser obligatoriamente verdaderas antes de que una función comience su ejecución.

1
2
3
4
5
6
7
// Precondición: 'b' no debe ser cero.
int dividir(int a, int b)
{
    // Si b es cero, la división por cero provocará un fallo físico en el
    // hardware.
    return a / b;
}
Postcondiciones (El Derecho del Cliente, Obligación del Proveedor)

Una postcondición describe las garantías lógicas que la función promete cumplir después de terminar su ejecución, siempre y cuando la precondición haya sido satisfecha al inicio.

1
2
3
4
5
6
7
// Precondición: 'radicando' >= 0
// Postcondición: El valor retornado es 'r' tal que r * r <= radicando y
// (r+1)*(r+1) > radicando
int raiz_entera(int radicando)
{
    // Implementación del algoritmo
}

Verificación Dinámica de Contratos en C

En el lenguaje C, el contrato se documenta formalmente en la cabecera de la función de acuerdo con la regla 0x2003h: Todas las funciones deben incluir documentación completa y estructurada (que exige documentar precondiciones y postcondiciones de manera estructurada). Sin embargo, además de documentarse de forma textual, es una excelente práctica de diseño defensivo verificar dinámicamente las precondiciones usando la macro assert() de <assert.h>.

Por ejemplo, la función raiz_entera puede implementarse validando su precondición:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <assert.h>
/**
 * @brief Calcula la raíz cuadrada entera de un número.
 * @pre radicando >= 0 (Regla 0x2003h)
 * @post retorna 'r' tal que r * r <= radicando y (r+1)*(r+1) > radicando
 */
int raiz_entera(int radicando)
{
    // Verificación dinámica de la precondición
    assert(radicando >= 0);
    int r = 0;
    while ((r + 1) * (r + 1) <= radicando)
    {
        r++;
    }
    return r;
}

Invariantes Elementales e Invariantes de Lazo

Invariantes de Lazo (Loop Invariants)

Un invariante de lazo es una condición lógica (una afirmación sobre las variables del programa) que es verdadera:

  1. Antes de ingresar al lazo de control.

  2. Antes y después de cada iteración individual del lazo.

  3. Inmediatamente después de salir del lazo de control.

Los invariantes de lazo son la herramienta didáctica fundamental para razonar lógicamente sobre la correctitud de los algoritmos iterativos. Nos permiten verificar de forma inductiva que un lazo de control realmente cumple su objetivo paso a paso.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Precondición: arr != NULL y n >= 0
// Postcondición: retorna la suma de todos los elementos de arr[0] a arr[n-1]
int sumar_arreglo(const int arr[], int n)
{
    int suma = 0;
    int i = 0;
    // Invariante: suma es la suma acumulada de arr[0] a arr[i-1] AND 0 <= i <=
    // n
    while (i < n)
    {
        suma += arr[i];
        i++;
    }
    return suma;
}

Ejercicios de Autoevaluación

Precondiciones y Postcondiciones

Invariantes de Lazo


Glosario

Diseño por Contrato (DbC)
Metodología de diseño de software que formaliza las interfaces entre componentes mediante derechos y obligaciones mutuas (contratos lógicos).

Síntesis y Resumen

En esta unidad te introdujiste en el formalismo del Diseño por Contrato y su aplicación en la verificación de algoritmos:


Referencias y Lecturas Complementarias