【问题标题】:Library to facilitate the use of the "design by contract" principle [closed]图书馆促进使用“按合同设计”原则[关闭]
【发布时间】:2009-07-24 06:58:57
【问题描述】:

是否有任何库可以帮助在 C++ 应用程序中实现按契约原则设计?

特别是,我正在寻找一个可以使用该原理的库,例如this

【问题讨论】:

  • 您应该用 assert(小写)宏阐明您对简单机制不满意的地方。
  • 您刚刚链接到一个完全符合您要求的库。你希望我们说什么? “你可以给codeproject.com/KB/cpp/DesignByContract.aspx 一个机会”?如果您想要的不仅仅是该库提供的东西,那么不要将其用作您所追求的示例。告诉我们你想要什么它没有提供。

标签: c++ design-by-contract


【解决方案1】:

我遵循了以下文章的教义:

  • An exception or a bug?(米罗 Samek,C/C++ 用户杂志,2003 年)
  • 对合同设计的简单支持 在 C++ 中(Pedro Guerreiro,TOOLS,2001)

我最终应用的几乎是 Samek 的方法。只需为 REQUIRE、ENSURE、CHECK 和 INVARIANT(基于现有的 assert 宏)创建宏就非常有用。当然它不如母语支持好,但无论如何,它可以让您从该技术中获得大部分实用价值。

至于库,我认为使用一个库并不值得,因为断言机制的一个重要价值是它的简单性。

调试代码和生产代码的区别见When should assertions stay in production code?

【讨论】:

    【解决方案2】:

    最简单?

    在函数开始时断言语句以测试您的要求。 在函数末尾断言语句以测试结果。

    是的,它很粗糙,它不是一个大系统,但它的简单性使它具有通用性和便携性。

    【讨论】:

    • 而且 DbC 的主要特性——前置/后置条件被继承——不会被 Assert 模拟。
    • 当您在少数地方使用“return”时,“在函数的末尾”也会变得非常复杂。更不用说例外了。
    • 没错,你至少应该在开始时使用断言,每次你从 elsewher(非本地)获取数据时,比如当你得到一个指针时,你必须检查它是否有效,当你得到有些值你必须检查它们是否符合你的假设等。开始断言的目的应该是检查函数调用的上下文,其他断言检查关于外部数据和本地操作的假设。
    【解决方案3】:

    一些设计模式,例如non-virtual interface,可以很自然地为给定方法编写前置/后置条件:

    #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() {
            /* ... */
        }
    }
    

    【讨论】:

    • 其实,为了符合DbC,前置条件应该是虚函数(派生类可以通过增加额外的代码来减少前置条件)。示例: void stop() { stop_prec(); stop_impl(); assert(!engine_running()) } 和 stop_prec() 是一个具有实现的虚函数(最严格的前置条件)。但是你的提议听起来不错!
    【解决方案4】:

    试试这个:Contract++。它已被 Boost 接受(但尚未发货)。

    【讨论】:

      【解决方案5】:

      我有一个带有要求、保险和不变量的小 c++ 标头。它的位置少于 400,应该可以满足您的需求。您可以在dhc.hpp 下找到它,它以一种有用的方式报告错误,并且可以通过定义编译出来。

      #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));
      }
      

      【讨论】:

        【解决方案6】:

        如果您不介意使用C++0x features,您可以使用lambdas 和RAII 实现前置条件和后置条件。

        后置条件的一个简单例子:

        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); })
        

        同样,您可以实现先决条件和不变量。 代码取自一个极其简单的C++0x Contracts library

        【讨论】:

        • 主要问题:前置条件/​​后置条件/不变量是如何继承的?
        【解决方案7】:

        使用标准的 ASSERT/Q_ASSERT,但要注意“无效”断言,尤其是如果您将此类诊断留在外部测试中(在没有 NDEBUG 的情况下构建)。

        关于在 C++ 项目中实现 DBC(使用断言)和“始终启用调试”策略的小故事。

        我们使用非常标准的工具 (ASSERT()/Q_ASSERT()) 作为 DBC 实现,直到我们在集成测试中遇到以下情况:我们的最新构建总是在启动后失败。发布这样的版本不是很专业(经过一周的内部 QA 工作)。

        问题是如何引入的?

        • 开发人员在源代码中留下了错误的断言(无效的逻辑表达式)
        • 我们所有的预发布版本都启用了断言(用于跟踪集成测试中的错误)
        • 内部 QA 的环境设置与集成测试不同,因此“断言错误”不可见

        因此,这个错误归咎于可怜的开发人员(显然没有这个 ASSERT 就不会发生崩溃),我们不得不发布修补程序以允许继续进行集成测试。

        首先:我需要在集成测试中启用断言以跟踪失败的情况(断言越多越好),另一方面我不希望开发人员害怕 一些“额外”的断言会使整个软件堆栈崩溃。

        我发现,对于这个问题,基于 C++ 的解决方案可能很有趣:弱断言。这个想法是在断言失败时不停止整个应用程序,而是记录堆栈跟踪以供以后分析并继续。我们可以根据需要检查尽可能多的期望,而不必担心崩溃,并且我们会从集成中获得反馈(堆栈跟踪)。单个进程运行可以提供许多失败的断言案例来分析,而不仅仅是一个(因为没有调用 abort())。

        这里简要描述了这个想法的实现(使用一些 LD_PRELOAD 魔法):http://blog.aplikacja.info/2011/10/assert-to-abort-or-not-to-abort-thats-the-question/

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多