契约式设计(Design by Contract)总结及应用
前言
本周学习了契约式设计的基础知识,这篇文章是对契约式设计的一个简单总结及简单的应用举例,希望对想学习这部分内容的同学有所帮助,如果发现错误的地方,请留言指正,不胜感激。一、契约式设计基础知识总结
1.契约式设计的定义
契约式设计,英文名:Design by Contract,简称 DbC,是一种设计计算机软件的方法。这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了前置条件、后置条件和不变式。这种方法的名字里用到的“契约”或者说“契约”是一种比喻,因为它和商业契约的情况有点类似。契约式设计是面向对象软件大师Bertrand Meyer对软件构造方法的一个重大贡献,无论是在形式化的数学证明中,还是在实践运用中,都被证明是大幅改善软件工程质量的有效手段。该方法在Eiffel编程语言中获得直接支持。
2.契约式设计的三个关键词
1、前置条件(precondition):为了调用函数,必须为真的条件,在其违反时,函数决不调用,传递好数据是调用者的责任。不过的设计过程中,函数的开始部分一般也会加入对入参的检查代码,如果入参不满足条件,函数会直接结束,不会执行后续的代码。如果调用者自己判断了入参,决定是否调用函数,这是最理想的状态,能提高程序的执行效率。
2、 后置条件 (postcondition):
函数保证能做到的事情,函数完成时的状态,函数有这一事实表示它会结束,不会无休止的循环。我们写函数的时候,一般也是只写了前置条件,很少会加入后置条件,这是为了提高函数执行效率考虑的,不过我们在做单元测试时,最好加上后置条件,表明函数的执行,达到了期望的效果。
3、不变式(invariant):
从调用者的角度来看,该条件总是为真,在函数的内部处理过程中,不变式可以为变,但在函数结束后,控制返回调用者时,不变式必须为真。这对于面向对象设计来说,类的任何实例,在调用函数之前和之后,不变式的值是不变的。
3.契约式设计的六大原则
1、 区分命令和查询。查询返回一个结果,但不改变对象的可见性质。命令改变对象的状态,但不一定返回结果。2、 将基本查询同派生查询分开。派生查询可以用基本查询来定义。
3、针对每个派生查询,设定一个后验条件,使用一个或多个基本查询的结果来定义它。这样我们只要知道基本查询的值,也就能知道派生查询的值。
4、 对于每个命令都撰写一个后验条件,规定每个基本查询的值。结合“用基本查询定义派生查询”的原则,我们已经能够知道每个命令的全部可视效果。
5、 对于每个查询和命令,采用一个合适的先验条件。先验条件限定了客户调用查询和命令的时机。
6、 撰写不变式来定义对象的恒定特性。类是某种抽象的体现,应当将注意力集中在最重要的属性上,以帮助读者建立关于类抽象的正确概念模型。
二、契约式设计应用举例
下面举的例子非常简单,是一个C++语言的例子,目的是往容器内加入一个新的元素。在这个例子当中,展示了前置条件、后置条件及不变式的应用。bool append_new_data(vector<const char*>& str_vec, const char* input) {
/* 前置条件(require) */
assert(input != NULL);
/* 不变式(invariant) */
assert(str_vec.size() >= 0);
int before_size = str_vec.size();
//把新数据插入容器
str_vec.push_back(input);
/* 后置条件(require) */
int after_size = str_vec.size();
assert(after_size == before_size + 1);
assert(str_vec.at(before_size) == input);
/* 不变式(invariant) */
assert(str_vec.size() >= 0);
return true;
}
总结
对于面向对象程序设计,契约式设计一般使用断言来实现。像C++、C有指针类型的语言来说,对入参指针的判断非常重要,如果忽略了对指针的判断,自己使用空指针会引起程序崩溃,后果极其严重。所以一般的在函数的开始部分,我们都会对指针做判断,检查指针是否为空。这种检查的行为,在契约式设计中,就是一种前置条件的检查方式。使用契约式设计方法,我们能更清晰的懂得,我们需要做什么事,需要预防什么错误,需要确保得到什么样的结果,以及我们做了一些操作过后,什么东西是保持不变的。有了这些思考后,我们设计出来的程序,逻辑清晰,程序健壮,阅读性可测试性也大大增强,同时也能缩短我们调试程序所需要花费的精力和时间。在一些对性能要求非常高的程序当中,我们在编写代码的时候,是需要加上后置条件和不变式的代码的,为的是我们调试测试方便,同时也是一种编码规范。不过由于性能要求,我们在测试通过后,是可以把后置条件和不变式注释掉。我们用注释而不是删除,为的是告诉读者,我们写代码的时候,是有全面考虑的,代码编写是有质量保证的,只是由于性能要求比较苛刻,我们让后置条件不执行罢了。