前言

最近在开发一个验证框架,希望能够降低代码的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

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

  1. 写给程序员的软件测试指南:人人都可以开发无Bug代码

    ​点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 ​ ​参与文末话题讨论,每日赠送异步图书. --异步小编 一年前,也是端午节,很巧合,本书的一个译者为另一个译者的新书< ...

  2. 低代码开发平台建设步骤及思考

    前言 由于毕业至今一直从事于企业应用相关的工作,在整个过程中有很多思考.包括低代码最早运用于bpm行业.随着2020年疫情,推动了整个企业应用相关的进程,尤其突出的是重新提出的 低代码甚至零代码开发平 ...

  3. OPENAPI3.0 与 SpringBoot 开发实战: 新型高效开发模式,实现代码与API分离,高效开发,开发必看!!!

    什么是openapi 3.0 OpenAPI 3.0.0 是 OpenAPI 规范的第一个正式版本,因为它是由 SmartBear Software 捐赠给 OpenAPI Initiative,并在 ...

  4. 如何从零开发一个低代码平台,有哪些成熟技术组件可用

    目前国内主流的低代码开发平台有:宜搭.云程.简道云.明道云.氚云.伙伴云.道一云.JEPaaS.华炎魔方.搭搭云.JeecgBoot .RuoYi等.这些平台各有优劣势,定位也不同,用户可以根据自己需 ...

  5. ios架构与开发第二课 代码规范管理与自动化构建

    05 自动化准备:如何使用 Fatlane 管理自动化操作? 要成为一个优秀的 iOS 开发者,我们要做的事情远多于"开发",例如我们要构建和打包 App,管理证书,为 App 进 ...

  6. Android第一行代码学习思考笔记(碎片、广播、持久化技术和Android数据库)

    Android第一行代码学习思考笔记(碎片.广播.持久化技术和Android数据库 第四章 手机平板要兼顾--探究碎片 4.1碎片是什么(Fragment) 4.2碎片的使用方式 4.2.1碎片的简单 ...

  7. 开发中的代码管理工具熟知

    一. 掌握 - git 概述 1. git 简介? 1.什么是git? > git是一款开源的分布式版本控制工具 > 在世界上所有的分布式版本控制工具中,git是最快.最简单.最流行的 2 ...

  8. Java自己文章只能自己修改_文章目录Java代码俯身指南,主要为Java开发人员提供代码复审参考,快捷有效提出修改意见。目的发现代码错误:一个人写的代码可能会有一些思想和设计盲点,多个人尽...

    文章目录 Java代码俯身指南,主要为Java开发人员提供代码复审参考,快捷有效提出修改意见. 目的发现代码错误:一个人写的代码可能会有一些思想和设计盲点,多个人尽早的发现BUG. 统一代码风格:统一 ...

  9. Java开发规范之代码格式篇(上)

    在程序员的世界里有两件最讨厌的事情,第一件事情是讨厌写代码注释,第二件事情是讨厌看别人的代码不写注释.虽然这只是个段子,但也反映了当下很多程序员的心声.下面简单介绍下代码规范的重要性,第一,规范的代码 ...

  10. 设计系统(Design System),设计和开发之间的“DevOps”

    最近,我们网站的上新增了几个新功能,比如通过导航栏的QR Code可以下载App:通过Carousel的方式,显示多条信息. 以往这样的功能可能需要2-3个Sprints完成,但是现在这些功能都是在一 ...

最新文章

  1. 一行命令装下所有「炼丹」工具及依赖项,就靠这个免费软件源了|教程
  2. Python3 基础语法(笔记2)
  3. docker 部署 nginx+php+mysql
  4. 提高你的Java代码质量吧:如果有必要,使用变长数组吧
  5. 【技术综述】计算机审美,学的怎么样了?
  6. mysql5.5连接器_MySQL :: MySQL 5.1参考手册 :: 26. 连接器
  7. 更改列表的默认项标记的颜色、大小等样式的解决办法
  8. MS17-010漏洞复现
  9. mysql 动态hash_python动态渲染库_python 动态渲染 mysql 配置文件的示例
  10. 2020年中职学计算机有前途吗,2020年南昌中专计算机专业都学什么
  11. 实现DRBD的简单配置
  12. 高精度计时器 -- C++/Windows版
  13. java gbk编码_java 中文转GBK码
  14. 51学习记录基于51单片机的简单音乐盒
  15. linux版flash下载工具,Linux系统下安装Flash浏览器插件的方法
  16. 明明有QQ,凭什么微信能火?——QQ微信横向对比分析
  17. 有意思的六度分割理论
  18. 2022年我国城镇污水处理运营市场空间可达730亿元
  19. CityEngine+Python自动化建模实现【系列文章之四】
  20. HDLBits练习(三)多路复用器,算术电路,卡诺图电路

热门文章

  1. 截获webView点击事件
  2. 如何使用Movavi Video Editor Plus在Mac上制作旁白配音视频
  3. Android中ListView复用导致布局错乱的解决方案
  4. Javascript当中的RSA加解密
  5. EasyRecovery如何添加XML头文件标识
  6. GitHub 学习和使用
  7. 原生JS大揭秘—事件循环机制Event Loop
  8. 再见,Python!你好,Go语言\n\n
  9. 11月19日 数据库连接 PDO
  10. 【编程技巧】——输入输出优化