对于开发 0 bug 代码的思考——Design by Contract 契约设计

前言

最近在开发一个验证框架,希望能够降低代码的bug率,提升质量;不知不觉就来到了Design By Contract,感觉这是个方向。

本文主要是批判一下现有的契约设计问题,提出自己的看法,很希望得到一些牛人的指教。

 

研究现状简单分析

Design by Contract(DbC)是个天才(我觉得)叫Bertrand Meyer提出来的。那个家伙同时还搞出了个Eiffel的东西,是对DbC的实践(Practise)。我先简单介绍下DbC, 下文是一个范型字典DICTIONARY [ELEMENT]:的put方法定义:

     put (x: ELEMENT; key: STRING) is
                     -- Insert x so that it will be retrievable through key.
             require
                     count <= capacity
                    
not key.empty
            
do
                     ... Some insertion algorithm ...
             ensure
                     has (x)
                     item (key) = x
                     count =
old count + 1
            
end
大概意思是,调用这个方法,要求(require)是xxxx;这个方法干了什么(do),这个方法结束后,保证了什么输出(ensure)。

具体可以看:http://www.eiffel.com/developers/knowledgebase/design_by_contract.html

换句话说,一个方法有了前置条件(pre condition)、后置条件(post condition),调用起来就有了保证。

 

 

然后,一些人就开始做文章了。在c#领域里面,ms也没有闲着。首先是Spec#,个人感觉是一种模仿Eiffel的语言,一个例子:

Spec#

代码
     static   void  Main( string ! [] args)
        requires args.Length 
>   0
    {
        
foreach ( string  arg  in  args)
        {
            Console.WriteLine(arg);
        }
    }

这个例子算简单了,(该死的找不到一个恶心的例子,也许ms的工程师们也觉得恶心,没放上来)。用了一个repuires去约束了args.具体可以看:

http://en.wikipedia.org/wiki/Spec_Sharp

http://research.microsoft.com/en-us/projects/specsharp/#documentation

 

没有这么牛逼的,就在.net基础上去做,比如使用断言(Assert)和属性去实现(Attribute)。比如:

http://research.microsoft.com/en-us/projects/contracts/

代码
1  usin} System;
2  usin} System.Diagnostics .Contracts;
3
4   namespace  ContractExample1 {
5
6   class  Rational {
7
8   int  numerator;
9   int  denominator;
10
11   public  Rational(  int  numerator,  int  denominator)
12  {
13  Contract.Requires( denominator  !   =   0  );
14
15   this  .numerator  =  numerator;
16   this  .denominator  =  denominator;
17  }
18
19   public   int  Denominator {
20  }et {
21  Contract.Ensures( Contract. Result < int > ()  !=   0  );
22
23   return   this  .denominator;
24  }
25  }
26
27  [ContractInvariantMethod]
28   protected   void  ObjectInvariant () {
29  Contract. Invariant (  this  .denominator  !   =   0  );
30  }
31  }
32  }

这就是个例子,或者直接用TestDriven里面的Assert()去实现。例子太多了,比如:

  1. http://geekswithblogs.net/Podwysocki/archive/2008/01/22/118770.aspx
  2. http://devlicio.us/blogs/billy_mccafferty/archive/2006/09/22/design_2d00_by_2d00_contract_3a00_-a-practical-introduction.aspx
  3. http://puzzleware.net/nContract/nContract.html#ConfiguringContractChecks

这些实践的一个极端的例子:

代码

[FormallySpecified]
[ModelField(
typeof (List < char > ),  " Contents " ,
  
@" new List<char>(this.ToString().ToCharArray()) " )]
[RepresentationalInvariant(
" numberOfChars == stringBuilder.Length " )]
public   class  CharBuffer 
{
  [Pre(
" value != null " )]
  [Post(
" Contents.Count == value.Length " )]
  
protected  CharBuffer( string  value) { }

  

  [Pre(
@" index >= 0 && index <= Contents.Count && value != null " )]
  [Post(
@" Contents.Count == old.Contents.Count + value.Length " )]
  [ExceptionalPost(
typeof (ArgumentOutOfRangeException),
    
" index < 0 || index > Contents.Count " )]
  
public   virtual   void  Insert( int  index,  string  value) { }

  
  
  
//  Member fields 
   protected  StringBuilder stringBuilder;
  
protected   int  numberOfChars;
}

不知道大家怎么去想的,我看见了就想吐。。。

 

当然,微软里面有个比较牛的家伙,开发了个叫LinFu的框架,使用了AOP去操作。这个家伙牛在直接用Emit自己实现了aop,号称性能比其他框架好很多。

  1. http://www.codeproject.com/KB/cs/LinFu_Part5.aspx
  2. http://www.codeproject.com/KB/cs/LinFuPart1.aspx

感觉是差不多了,可是就是心里还是觉得有道砍,非常的不爽。

 

我对Design by Contract的实践

DbC提出来是1986年,现在都什么年代了,为什么还停滞不前。引用柯南的一句话:真相只有一个。因为路走错了。各位ms大牛们,每天埋头钻牛角尖的,因为他们把契约设计看成了一种程序代码、一种语言

结果导致了与业务毫不相干的、难看的(spec#)、奇怪的代码充斥我们优美的业务逻辑,然而DbC真正要解决的问题却没有解决。正如一个笑话说的:

苏联的优势在哪里?在于他解决了其他制度国家不存在的问题。(仅笑话,不要扯上政治)

我个人认为Design by Contract是一种设计模式!是一种习惯!是一种开发中的辅助语言

 

先是一个简单的调用例子:

     class  A
    {
        
public   void  Foo()
        {
            
int  interval  =   1 ;
            B b 
=   new  B();
            b.Foo(interval);
        }
    }
    
class  B
    {
        
public   void  Foo( int  interval) { }
    }

A调用了B的Foo方法,传入参数interval。

 

如果B对interval的约束很简单,比如要求interval>0,这样很轻松,用之前的spec#、aop、attribute、assert之类的都容易实现。可是现实生活不是这样,假设:

B要求interval非负数,当小于10的时候必须连续、当大于10的时候,必须每连续2个数字之后断开1个数字。

这怎么办??亲爱的spec#们,傻眼了吧。因为他们的工具对precondition的描述太有限了,而我们的需求又太复杂了,所以导致了design by contract停滞不前。

 

针对这些问题,我们为什么要用程序语言去约束?为什么就不用自然语言?为什么设计的时候内部的类(internal)使用自然语言规定了传入的要求,然后最终暴露在外部的类(public)再去针对这些要求去做验证?比如:

代码

     class  B
    {
        [Contract(
" interval小于零,大于0的时候,10以内连续,10以外每连续2次就断开1次 " )]
        
public   void  Foo( int  interval) { }
    }

的确,对于B.Foo我什么都没有做,只是添加了语言去描述约束。但是,当我编写A的时候,我亲爱的VS20xx就会自动的去检测调用对象的情况,然后汇总contract。比如:

是不是感觉清晰了很多?如果A是个最终暴露给用户的类,我们只要在调用A.Foo的时候,对他的方法的contract都做个验证,就足够了。

Design by Contract理论形态

我们开发设计的时候,一定会分interval/public class去写,暴露给用户的public class要尽量的少,剩余的工作全部交给内部的类去实现。这样一般会采用Facet的设计模式,由他负责提供方法、提供对象,而不是让用户自己去new。这个是我DbC的前提。

 

Design by Contract深入的去思考,实际上是对类方法的传入参数的约束(请先不要考虑返回值,让我先解决50%的问题)。对于内部类而言,会默认传入参数符合调用要求,不会对传入参数进行验证。

这样,当类与类之间调用后,暴露在最外面的类就负责起了最终参数传入的验证工作,所谓一夫当关,只要最外面的类把好关,那么剩下的业务逻辑我们会默认"在正确的输入下,会得到正确的输出"。

因此,如果我们知道外部类的某个方法需要负责哪些contract,这样我的design by contract就完成了。

 

因此,首先技术上要解决的是,我的外部类的方法如何知道需要的contract。目前.net的语言来看,反射还不足以完成任务,也许需要使用emit等高级工具。因为有时候有些内部类的contract会被另外的内部类保证了,这样外部类需要负责的contract就少了。

 

其次,就是如何去负责这些contract,这个就可以使用合适的设计模式了,针对外部类每一个参数,进行一个contract的严格验证,验证过程可以在新的类完成。比如以下伪代码:

代码

     class  A
    {
        
public   void  Foo(
            [Supervise(CheckVar1)] 
//  对传入参数进行验证
             string  var1,
            [Supervise(CheckVar2)] 
//  对传入参数进行验证
             string  var2)
        {
            
int  interval  =   1 ;
            B b 
=   new  B();
            b.Foo(interval);
        }
    }

然后验证过程在新的方法实现了。这样开发,就变得非常的清晰了。

 

小结与后续

  1. 在design by contract的框架开发下,大部分的类的方法会使用自然语言去描述contract;到了关键的边缘区域(内部与外部交互的区域),会查询此区域的contract(当然是自然语言描述的集合),然后我们再针对这些contract去检视传入参数。
  2. 如果有些内部类会履行某个类的contract,那么这个类的履行也需要使用检视。

如果我的想法能够在现有的技术下实现了,我觉得出现一个全新的开发过程,一种新的practise。以后的程序员会在class标注各种contract,然后最终会在某些类上使用Supervise,同时在某些类可以查看他需要履行的contract是什么。

这样开发起来,bug会降低到0,不是梦想。

 

(偶狂敲了1个小时,吐了几千字的废话,希望各位支持一下,能给点思路,指出我的错误。在此感谢了!)

 

技术支持

reborn_zhang@hotmail.com

zc22.cnblogs.com

 

 

 

转载于:https://www.cnblogs.com/zc22/archive/2009/11/30/1614142.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值