Programación basada en contratos (II)

Introducción


Diagrama basado en commons.wikimedia.org/wiki/File:Design_by_contract.svg (CC0 1.0)
Un contrato es un conjunto de precondiciones, postcondiciones y aserciones (todas ellas predicados) asociadas a una función. En concreto [1]:
  • Las precondiciones establecen los requisitos a verificar por los argumentos de una función (y/o el estado de otros objetos de la que dependa) en el punto de entrada de su ejecución. La responsabilidad de su cumplimiento recae en el cliente de la función (caller).
  • Las postcondiciones fijan los requisitos a cumplir por los valores retornados por la función (y/o el estado de otros objetos) al finalizar su ejecución. Son una garantía ofrecida por la función (callee) al cliente.
  • Las aserciones, por su parte, establecen condiciones que deban satisfacerse en puntos específicos del cuerpo de la función. Suelen emplearse para verificar el mantenimiento de los invariantes de una clase.

Programación basada en contratos (I)

Introducción


Un programador de C++ debe lidiar continuamente con múltiples desafíos: garantizar una correcta recuperación de los programas ante excepciones, prevenir bugs tales como accesos fuera de rango o referencias colgantes, adoptar estrategias ante un posible agotamiento de la memoria y un largo etcétera.  En este post trataremos de arrojar algo de luz sobre cómo manejar tales situaciones de forma correcta. No en vano, una de las características definitorias de cualquier lenguaje de programación es, precisamente, el conjunto de herramientas que pone a nuestra disposición para realizar esta importante labor.

Centraremos nuestra atención, en particular, en las llamadas a funciones. Distinguiremos dos categorías principales de incidencias por las que una función puede no alcanzar sus objetivos: recuperables e irrecuperables. Un lenguaje más reciente, Rust, maneja dichas categorías de forma claramente diferenciada, proporcionando un tipo de retorno Result<T,E> para errores recuperables (el cual contiene un valor de tipo esperado T o bien un elemento de error E) y una macro panic! para detener la ejecución del proceso en caso de producirse una incidencia irrecuperable [1]. Por defecto, C++ trata la primera categoría mediante el uso de excepciones, cuya utilidad pondremos en valor en este post, aunque el empleo de tipos variantes sea sin duda factible gracias a bibliotecas como Boost.Outcome [2]. Como discutiremos también, el tratamiento de errores irrecuperables debería descansar en el uso de contratos.