使用“契约设计”原则的库

20

有没有一个库可以帮助在C++应用程序中实现契约设计原则?

特别是,我正在寻找一种能够方便地使用该原则的库,类似于这个


2
你应该澄清使用assert(小写)宏的简单机制中令你不满意的部分。 - Daniel Daranas
1
请参阅https://dev59.com/anVC5IYBdhLWcg3wz0h9。 - Daniel Daranas
4
你刚才链接了一个恰好满足你要求的库,我们还能说什么呢?“你可以尝试一下 http://www.codeproject.com/KB/cpp/DesignByContract.aspx ”吗?如果你需要的东西超出了那个库所提供的范围,请不要将它作为你想要的示例。告诉我们它没有提供什么你想要的功能。 - jalf
6个回答

10

我遵循以下文章的教导:

  • An exception or a bug? (Miro Samek, C/C++ Users Journal, 2003)
  • Simple Support for Design by Contract in C++ (Pedro Guerreiro, TOOLS, 2001)

最终,我采用了Samek的方法。只需创建REQUIRE、ENSURE、CHECK和INVARIANT宏(基于现有的assert宏)就非常有用。当然,这并不像本地语言支持那样好,但无论如何,它都允许您从该技术中获得最大的实际价值。

至于库的选择,我认为不必使用一个库,因为断言机制的一个重要价值在于其简单性。

关于调试和生产代码之间的区别,请参见When should assertions stay in production code?


6

一些设计模式,例如非虚拟接口,使得为给定方法编写前/后置条件变得更加自然:

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

1
实际上,为了符合DbC,前置条件应该是虚函数(派生类可以通过添加额外的代码来减少前置条件)。例如:void stop() { stop_prec(); stop_impl(); assert(!engine_running()) },其中stop_prec()是带有实现的虚函数(最严格的前置条件)。但是你的建议听起来不错! - Tobias Langner

6

最简单的方法?

在函数开头添加Assert语句以测试您的要求。 在函数结尾添加Assert语句以测试您的结果。

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


比这个更好的东西 http://www.codeproject.com/KB/cpp/DesignByContract.aspx - yesraaj
6
DbC 的主要特征是前/后置条件被继承,而这一点 Assert 无法模拟。 - Tobias Langner
4
当你在几个地方使用"return"时,“在函数结尾处”变得非常复杂,更不用说异常情况了。 - Adam Badura
1
确实,你应该至少在开始和每次从其他地方(非本地)获取数据时使用asserts,例如当你获取指针时,必须检查它是否有效,当你获取一些值时,必须检查它们是否符合你的假设等等。开始的asserts目的是检查函数调用的上下文,其他asserts检查关于外部数据和本地操作的假设。 - Klaim

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

2
我有一个小的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));
}

1

使用标准的ASSERT/Q_ASSERT,但要注意“无效”的断言,特别是如果您在外部测试中保留这样的诊断(使用没有NDEBUG的构建)。

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

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

问题是如何引入的?

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

结果,可怜的开发人员被指责犯了这个错误(显然,如果没有这个ASSERT,就不会崩溃),我们不得不发布热修复程序来允许继续进行集成测试。

首先,我需要在集成测试中启用断言以跟踪失败的条件(断言越多越好)。另一方面,我不希望开发人员担心某些“额外”的ASSERT会导致整个软件堆栈崩溃。
我发现了一个可能有趣的基于C++的解决方案:弱断言。这个想法是不要停止整个应用程序在断言失败时,而是记录堆栈跟踪以供后续分析并继续执行。我们可以检查尽可能多的期望而不担心崩溃,并从集成中获得反馈(堆栈跟踪)。单个进程运行可以提供许多失败的断言情况进行分析,而不仅仅是一个(因为没有调用abort())。
这个想法的实现(使用一些LD_PRELOAD魔法)在这里简要描述:http://blog.aplikacja.info/2011/10/assert-to-abort-or-not-to-abort-thats-the-question/

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接