El diseño por contrato ( DbC ), también conocido como programación por contrato , programación por contrato y programación de diseño por contrato , es un enfoque para diseñar software .
Prescribe que los diseñadores de software deben definir especificaciones de interfaz formales , precisas y verificables para los componentes de software , que amplíen la definición ordinaria de tipos de datos abstractos con precondiciones , poscondiciones e invariantes . Estas especificaciones se denominan "contratos", de acuerdo con una metáfora conceptual con las condiciones y obligaciones de los contratos comerciales.
El enfoque DbC supone que todos los componentes del cliente que invocan una operación en un componente del servidor cumplirán las condiciones previas especificadas como necesarias para esa operación.
Cuando esta suposición se considera demasiado arriesgada (como en la computación multicanal o distribuida ), se adopta el enfoque inverso , lo que significa que el componente del servidor prueba que todas las condiciones previas relevantes sean verdaderas (antes o mientras se procesa la solicitud del componente del cliente ) y responde con un mensaje de error adecuado si no es así.
El término fue acuñado por Bertrand Meyer en relación con su diseño del lenguaje de programación Eiffel y descrito por primera vez en varios artículos a partir de 1986 [1] [2] [3] y las dos ediciones sucesivas (1988, 1997) de su libro Object-Oriented Software Construction . Eiffel Software solicitó el registro de marca para Design by Contract en diciembre de 2003, y le fue concedido en diciembre de 2004. [4] [5] El propietario actual de esta marca es Eiffel Software. [6] [7]
El diseño por contrato tiene sus raíces en el trabajo sobre verificación formal , especificación formal y lógica de Hoare . Las contribuciones originales incluyen:
La idea central de DbC es una metáfora sobre cómo los elementos de un sistema de software colaboran entre sí sobre la base de obligaciones y beneficios mutuos . La metáfora proviene de la vida empresarial, donde un "cliente" y un "proveedor" acuerdan un "contrato" que define, por ejemplo, que:
De manera similar, si el método de una clase en programación orientada a objetos proporciona una determinada funcionalidad, puede:
El contrato equivale semánticamente a un triple de Hoare que formaliza las obligaciones. Esto se puede resumir en las "tres preguntas" que el diseñador debe responder repetidamente en el contrato:
Muchos lenguajes de programación tienen facilidades para hacer afirmaciones como estas. Sin embargo, DbC considera que estos contratos son tan cruciales para la corrección del software que deberían ser parte del proceso de diseño. De hecho, DbC recomienda escribir las afirmaciones primero . [ cita requerida ] Los contratos pueden escribirse mediante comentarios de código , implementarse mediante un conjunto de pruebas o ambos, incluso si no existe un soporte de lenguaje especial para contratos.
La noción de contrato se extiende hasta el nivel de método/procedimiento; el contrato para cada método normalmente contendrá la siguiente información: [ cita requerida ]
Las subclases de una jerarquía de herencia pueden debilitar las precondiciones (pero no fortalecerlas) y fortalecer las poscondiciones y los invariantes (pero no debilitarlos). Estas reglas se aproximan a la subtipificación conductual .
Todas las relaciones de clase se dan entre clases de cliente y clases de proveedor. Una clase de cliente está obligada a realizar llamadas a las características del proveedor donde el estado resultante del proveedor no sea violado por la llamada del cliente. Posteriormente, el proveedor está obligado a proporcionar un estado de retorno y datos que no violen los requisitos de estado del cliente.
Por ejemplo, un búfer de datos de un proveedor puede exigir que los datos estén presentes en el búfer cuando se llama a una función de eliminación. Posteriormente, el proveedor garantiza al cliente que cuando una función de eliminación finaliza su trabajo, el elemento de datos se eliminará, de hecho, del búfer. Otros contratos de diseño son conceptos de invariante de clase . El invariante de clase garantiza (para la clase local) que el estado de la clase se mantendrá dentro de las tolerancias especificadas al final de cada ejecución de función.
Al utilizar contratos, un proveedor no debe intentar verificar que se cumplan las condiciones del contrato (una práctica conocida como programación ofensiva) ; la idea general es que el código debe "fallar estrepitosamente" y que la verificación del contrato es la red de seguridad.
La propiedad "fail hard" de DbC simplifica la depuración del comportamiento del contrato, ya que el comportamiento previsto de cada método está claramente especificado.
Este enfoque difiere sustancialmente del de la programación defensiva , en el que el proveedor es responsable de determinar qué hacer cuando se rompe una condición previa. En la mayoría de los casos, el proveedor lanza una excepción para informar al cliente de que se ha roto la condición previa y, en ambos casos (tanto en DbC como en la programación defensiva), el cliente debe determinar cómo responder a eso. En tales casos, DbC facilita el trabajo del proveedor.
El diseño por contrato también define criterios de corrección para un módulo de software:
El diseño por contrato también puede facilitar la reutilización del código, ya que el contrato para cada fragmento de código está completamente documentado. Los contratos para un módulo pueden considerarse como una forma de documentación del software sobre el comportamiento de ese módulo.
Las condiciones del contrato nunca deben violarse durante la ejecución de un programa libre de errores. Por lo tanto, los contratos normalmente solo se verifican en modo de depuración durante el desarrollo del software. Más adelante, en el lanzamiento, las verificaciones del contrato se desactivan para maximizar el rendimiento.
En muchos lenguajes de programación, los contratos se implementan con assert . Las aserciones se compilan de forma predeterminada en modo de lanzamiento en C/C++ y se desactivan de manera similar en C# [8] y Java.
Al iniciar el intérprete de Python con "-O" (para "optimizar") como argumento, el generador de código Python también no emitirá ningún código de bytes para las afirmaciones. [9]
Esto elimina efectivamente los costos de tiempo de ejecución de las afirmaciones en el código de producción, independientemente de la cantidad y el gasto computacional de las afirmaciones utilizadas en el desarrollo, ya que el compilador no incluirá dichas instrucciones en la producción.
El diseño por contrato no reemplaza las estrategias de prueba habituales, como las pruebas unitarias , las pruebas de integración y las pruebas del sistema . Más bien, complementa las pruebas externas con pruebas internas propias que se pueden activar tanto para pruebas aisladas como en el código de producción durante una fase de prueba.
La ventaja de las pruebas internas es que pueden detectar errores antes de que se manifiesten como resultados no válidos observados por el cliente, lo que permite una detección más temprana y más específica de los errores.
El uso de afirmaciones puede considerarse una forma de oráculo de prueba , una forma de probar el diseño mediante la implementación del contrato.
Los lenguajes que implementan la mayoría de las características de DbC de forma nativa incluyen:
Además, la combinación de métodos estándar en el Sistema de Objetos Common Lisp tiene los calificadores de método :before
y :after
que :around
permiten escribir contratos como métodos auxiliares, entre otros usos.