契约式编程是编程的一种方法。那么什么是契约式编程呢?我想这个概念是从“合同”演变过来的。
在人类的社会活动中,契约一般是用于两方,一方(供应者)为另一方(客户)完成一些任务。每一方都期待从契约中获得利益,同时也要接受一些义务。通常,一方视为义务的对另一方来说是权利。契约文档要清楚地写明双方的权利与义务。
契约合同能保障双方的利益,对客户来说,合同规定了供应者要做的工作;对供应者来说,合同说明了如果约定的条件不满足,供应者没有义务一定要完成规定的任务。
同样的道理也适合于软件。设想一个软件单元E。它要达到它的目的(履行契约), E使用的策略可能会包括一系列的子任务,t1, ... tn。如果子任务ti 不是那么简单的,它得调用另一个功能例程(routine)R。换句话说,E把子任务转包给R。这样的情形应该被一个很好定义的“登记表”(roster)来管理双方的义务与权利--契约。
假如ti是一项任务,要求把一个给定的元素插入到一个有限容量的字典中。此处字典是一个(名-值)表,每一个元素(值)通过一个作为关键字的字符串(名)存取。(译者注:这里“元素”可以理解成一个任意的数据项)。
简而言之,就是函数调用者应该保证传入函数的参数是符合函数的要求,如果不符合函数要求,函数将拒绝继续执行。如果按照契约式编程的思想编写代码,就要求我们写函数时检查函数参数。有时候是简单的判断某个参数不能为空,或者数值不能小于0。如果在项目中全面应用契约式编程,则应该有一个“契约框架”帮我们来做这些事情。
契约与我们通常所说的商业契约很相似,有以下几个特点:
- 契约关系的双方是平等的,对整个bussiness的顺利进行负有共同责任,没有哪一方可以只享有权利而不承担义务。
- 契约关系经常是相互的,权利和义务之间往往是互相捆绑在一起的;
- 执行契约的义务在我,而核查契约的权力在人;
- 我的义务保障的是你的利益,而你的义务保障的是我的利益;
将契约关系引入到软件开发领域,尤其是面向对象领域之后,在观念上给我们带来了几大冲击:
一般的观点,在软件体系中,程序库和组件库被类比为server,而使用程序库、组件库的程序被视为client。根据这种C/S关系,我们往往对库程序和组件的质量提出很严苛的要求,强迫它们承担本不应该由它们来承担的责任,而过分纵容client一方,甚至要求库程序去处理明显由于client错误造成的困境。客观上导致程序库和组件库的设计和编写异常困难,而且质量隐患反而更多;同时client一方代码大多松散随意,质量低劣。这种情形,就好像在一个权责不清的企业里,必然会养一批尸位素餐的混混,苦一批任劳任怨,不计得失的老黄牛。引入契约观念之后,这种C/S关系被打破,大家都是平等的,你需要我正确提供服务,那么你必须满足我提出的条件,否则我没有义务“排除万难”地保证完成任务。
一般认为在模块中检查错误状况并且上报,是模块本身的义务。而在契约体制下,对于契约的检查并非义务,实际上是在履行权利。一个义务,一个权利,差别极大。例如上面的代码:
1 |
if (dest == NULL) { ... }
|
这就是义务,其要点在于,一旦条件不满足,我方(义务方)必须负责以合适手法处理这尴尬局面,或者返回错误值,或者抛出异常。而:
1 |
assert(dest != NULL); |
这是检查契约,履行权利。如果条件不满足,那么错误在对方而不在我,我可以立刻“撕毁合同”,罢.工了事,无需做任何多余动作。这无疑可以大大简化程序库和组件库的开发。
契约所核查的,是“为保证正确性所必须满足的条件”,因此,当契约被破坏时,只表明一件事:软件系统中有bug。其意义是说,某些条件在到达我这里时,必须已经确保为“真”。谁来确保?应该是系统中的其他模块在先期确保。如果在我这里发现契约没有被遵守,那么表明系统中其他模块没有正确履行自己的义务。就拿上面提到的“打开文件”的例子来说,如果有一个模块需要一个FILE*,而在契约检查中发现该指针为NULL,则意味着有一个模块没有履行其义务,即“检查文件是否存在,确保文件以正确模式打开,并且保证指针的正确性”。因此,当契约检查失败时,我们首先要知道这意味着程序员错误,而且要做的不是纠正契约核查方,而是纠正契约提供方。换句话说,当你发现:
1 |
assert(dest != NULL); |
报错时,你要做的不是去修改你的string_copy函数,而是要让任何代码在调用string_copy时确保dest指针不为空。
我们以往对待“过程”或“函数”的理解是:完成某个计算任务的过程,这一看法只强调了其目标,没有强调其条件。在这种理解下,我们对于exception的理解非常模糊和宽泛:只要是无法完成这个计算过程,均可被视为异常,也不管是我自己的原因,还是其他人的原因(典型的权责不清)。正是因为这种模糊和宽泛,“究竟什么时候应该抛出异常”成为没有人能回答的问题。而引入契约之后,“过程”和“函数”被定义为:完成契约的过程。基于契约的相互性,如果这个契约的失败是因为其他模块未能履行契约,本过程只需报告,无需以任何其他方式做出反应。而真正的异常状况是“对方完全满足了契约,而我依然未能如约完成任务”的情形。这样以来,我们就给“异常”下了一个清晰、可行的定义。
一般来说,在面向对象技术中,我们认为“接口”是唯一重要的东西,接口定义了组件,接口确定了系统,接口是面向对象中我们唯一需要关心的东西,接口不仅是必要的,而且是充分的。然而,契约观念提醒我们,仅仅有接口还不充分,仅仅通过接口还不足以传达足够的信息,为了正确使用接口,必须考虑契约。只有考虑契约,才可能实现面向对象的目标:可靠性、可扩展性和可复用性。反过来,“没有契约的复用根本就是瞎胡闹。(Bertrand Meyer语)”。
由上述观点可以看出,虽然Eiffel所倡导的Design By Contract在表象上不过是系统化的断言(assertion)机制,然而在背后,确实是完全的思想革新。正如Ivar Jacoboson访华时对《程序员》杂志所说:“我认为Bertrand Meyer的方向——Design by Contract——是正确的方向,我们都会沿着他的足迹前进。我相信,大型厂商(微软、IBM,当然还有Rational)都不会对Bertrand Meyer的成就坐视不理。所有这些厂商都会在这个方向上有所行动。”
相关推荐
契约式编程是一种软件开发方法,它借鉴了形式化方法的理念,通过引入不变式、前置条件(precondition)和后置条件(postcondition)等概念,确保程序模块的语义清晰和正确性。这种方法有助于提高软件的可靠性和可...
精品教育教学资料
浅谈Spring5 响应式编程 Spring 5 中的响应式编程是指基于声明式编程的异步编程模型,它能够构建更加敏感和有弹性的应用程序。响应式编程的核心思想是管理数据生产者和消费者之间的异步数据流,以流畅的方式响应...
私募股权投资领域的契约型基金浅谈.doc
本文将介绍抽象类、接口和一种称为契约式编程的技术。使用这些OPP机制,所编写的代码就不限于只能计算或者输出内容了。这些机制能够在概念层次上定义类之间交互作用的规则,也为应用程序的扩展和定制提供了基础。
.NET4.0契约式设计,MSDN官方推荐学习视频,很好的资料。
**WCF契约编程详解** Windows Communication Foundation(WCF)是微软.NET Framework中用于构建服务导向应用程序的核心技术。它提供了一种统一的方式,用于构建、配置和部署能够在多种平台和网络协议上通信的服务。...
书中首先从OOP 采用的机制—— 抽象类、接口、契约式编程开始讲起,然后介绍了静态方法、单例模式、工厂模式和PHP 6 的新特性等内容,接着介绍了测试和文档方面的内容,还介绍了标准PHP 库SPL 方面的知识以及PHP ...
大型煤炭集团契约式协同管控模式是在对集团具体情况进行详细调查的基础上,以现代契约交易理论为指导,通过开发、设计适合集团内部具体情况的信息化软件系统,并以此现代化的信息手段为平台,量身定做集团内部运行机制,...
在中国,随着人口老龄化的加速,"看病难"的问题日益凸显,契约式社区医疗服务作为一种解决方案应运而生。这种模式旨在通过社区医生与居民签订服务契约,建立稳定的医患关系,提供更加便捷和贴心的医疗服务。然而,...
在IT行业中,插件式编程是一种常见的软件设计模式,它允许开发者通过添加或删除插件来扩展程序的功能,而无需修改原始代码。本例子是基于C#语言实现的,C#是微软公司推出的面向对象的编程语言,尤其适用于Windows...
这是模板元编程中编译期条件选择的一种实现,类似函数式编程中的条件表达式,但这里的条件是在编译时决定的。 C++模板元编程的主要特点包括: 1. **静态语言设施**:模板元编程主要使用静态的C++语言特性,如类型、...
文章以《从田忌赛马谈契约精神》为题,通过古代故事来探讨现代的契约精神。田忌赛马的故事中,孙膑利用策略帮助田忌赢得比赛,但这一胜利是在违反赛马规则的基础上实现的。按照正常的赛制,应是等级相当的马匹进行...