c++ design-by-contract

c++ - Biblioteca para facilitar el uso del principio de "diseño por contrato"



design-by-contract (7)

Algunos patrones de diseño, como la interfaz no virtual, hacen que sea natural escribir condiciones previas / posteriores para un método determinado:

#include <cassert> class Car { virtual bool engine_running_impl() = 0; virtual void stop_impl() = 0; virtual void start_impl() = 0; public: bool engine_running() { return engine_running_impl(); } void stop() { assert(engine_running()); stop_impl(); assert(! engine_running()); } void start() { assert(! engine_running()); start_impl(); assert(engine_running()); } } class CarImpl : public Car { bool engine_running_impl() { /* ... */ } void stop_impl() { /* ... */ } void start_impl() { /* ... */ } }

¿Hay alguna biblioteca que ayude a implementar el principio de diseño por contrato en una aplicación C ++?

En particular, estoy buscando una biblioteca que facilite el uso del principio, algo como this .


Más simple?

Afirmar declaraciones al inicio de su función para probar sus requisitos. Afirmar declaraciones al final de su función para probar sus resultados.

Sí, es crudo, no es un sistema grande, pero su simplicidad lo hace versátil y portátil.


Prueba este: Contract++ . Se ha aceptado a Boost (pero aún no se ha enviado).


Seguí las enseñanzas de los siguientes artículos:

  • ¿Una excepción o un error? (Miro Samek, C / C ++ Users Journal, 2003)
  • Soporte simple para diseño por contrato en C ++ (Pedro Guerreiro, TOOLS, 2001)

Lo que finalmente apliqué fue el enfoque de Samek. La creación de macros para REQUIRE, ENSURE, CHECK y INVARIANT (en base a la macro de assert existente) fue muy útil. Por supuesto, no es tan bueno como el soporte de idioma nativo, pero de todos modos, le permite obtener la mayor parte del valor práctico de la técnica.

En cuanto a las bibliotecas, no creo que valga la pena usar una, porque un valor importante del mecanismo de afirmación es su simplicidad.

Para ver la diferencia entre depuración y código de producción, consulte ¿ Cuándo deben permanecer las aserciones en el código de producción? .


Si no le importa usar las funciones de C ++ 0x , puede implementar condiciones previas y condiciones posteriores utilizando las lambdas y RAII.

Un ejemplo simple de postcondición:

struct __call_on_destructor { std::tr1::function<void()> _function; template<class Func> inline __call_on_destructor(Func func) { _function = func; } inline ~__call_on_destructor() { _function(); } }; #define on_scope_exit(function) / __call_on_destructor PP_UNIQUE_LABEL(on_exit) (function) #define ensures(expression) / on_scope_exit([&] () { assert(expression); })

Del mismo modo, podría implementar condiciones previas e invariantes. El código se tomó de una biblioteca de contratos C ++ 0x extremadamente simple.


Tengo un pequeño encabezado de c ++ con requisitos, seguros e invariantes. Tiene menos de 400 loc y debe satisfacer sus necesidades. Puede encontrarlo en dhc.hpp Informa los errores de una manera útil y se puede compilar a través de define.

#include <dbc.hpp> class InvarTest { public: int a = 0; int b = 9; INVARIANT_BEGIN Inv(RN(0,a,32)); Inv(RN(0,b,10)); INVARIANT_END inline void changeMethod() { Invariant(); // this runs the invariant block at the beginning and end of the method a = 33; } }; int testFunc(int a, double d, int* ip) { // RN = a in range 0 to 10, NaN = not a number, NN = not null Rqr(RN(0,a,10), NaN(d), RN(0.0,d,1.0), NN(ip)); // Enr return the passed value return Esr(RN(0.0,a+d,20.3)); } void testFunc2(std::vector<int>& a, std::shared_ptr<int> sp) { Rqr( SB(a,0), TE(a.size() % 12 == 0), NN(sp)); }


Utilice ASSERT / Q_ASSERT estándar , pero tenga cuidado con las aserciones "inválidas", especialmente si deja estos diagnósticos en pruebas externas (compilación sin NDEBUG).

Pequeña historia sobre la implementación de DBC (utilizando aserciones) en un proyecto de C ++ y la política de "depuración siempre habilitada".

Estábamos usando herramientas bastante estándar (ASSERT () / Q_ASSERT ()) como implementación de DBC hasta que llegamos a la siguiente situación en las pruebas de integración: nuestra última compilación siempre fallaba justo después del inicio . No fue muy profesional lanzar dicha versión (después de una semana de esfuerzos internos de control de calidad).

¿Cómo se introdujo el problema?

  • Un desarrollador dejó una aserción incorrecta (expresión lógica no válida) en el código fuente
  • Todas nuestras compilaciones previas al lanzamiento tenían activaciones habilitadas (para rastrear errores en las pruebas de integración)
  • El control de calidad interno tiene configuraciones de entorno diferentes a las pruebas de integración, por lo que el "error de aserción" no fue visible

Como resultado, se culpó a un desarrollador deficiente por este error (obviamente sin este ASSERT no se produciría un fallo) y tuvimos que lanzar una revisión para permitir que las pruebas de integración continuaran.

En primer lugar: necesito que las aserciones estén habilitadas en las pruebas de integración para realizar un seguimiento de las condiciones fallidas (cuantas más afirmaciones, mejor), por otra parte, no quiero que los desarrolladores teman que algún ASSERT "adicional" se bloquee en la pila de software.

Encontré, probablemente una resolución interesante basada en C ++ para este problema: afirmaciones débiles. La idea es no detener toda la aplicación en una afirmación fallida, sino grabar el seguimiento de pila para un análisis posterior y continuar. Podemos verificar tantas expectativas como queramos sin temor a fallar y obtenemos retroalimentación (stacktraces) de la integración. La ejecución de un solo proceso puede proporcionar muchos casos de aserciones fallidas para el análisis en lugar de solo uno (porque no se llama abort ()).

La implementación de esta idea (usando algo de magia LD_PRELOAD) se describe brevemente aquí: http://blog.aplikacja.info/2011/10/assert-to-abort-or-not-to-abort-thats-the-question/