Loading

面向对象设计与构造第三次总结作业

规格化设计的发展历史

  Specification一词可追溯到16世纪末,但直到20世纪50年代才得以广泛使用。在软件工程领域,上世纪70年代左右就有关于规格化设计的研究。
Barbara Liskov

  1975年,Liskov等人发表了论文Specification Techniques for Data Abstractions,从数据抽象的角度论述了规格的优点、特性及重要性。文中主要讨论的是形式化的规格,Liskov认为形式化规格可能是程序开发所固有的特征,对于数据抽象是很有用处的。形式化规格的优点主要有:①验证程序的正确性,②帮助程序员理解程序代码,③在程序开发过程中具有重要作用,④规格是设计者与实现者、程序员之间沟通的媒介。任何的规格都应该满足6个标准:正式(Formality)、可构造(Constructability)、易于理解(Comprehensibility)、最小化(Minimal)、广泛的适用性(Wide range of applicability)、可扩展型(Extensibility)。

  1976年,在第二届国际软件工程会议上,Belford等人在Specifications a key to effective software development一文中从开发复杂系统的角度论证了完整的、一致的规格的重要性。在系统研发周期中,规格提供了在概念和定义阶段的过渡。一个清楚、无歧义的规格是取得成功的关键,同时能减少开发过程中的开销。软件需求本身具有模糊的特点,所以需要一个定义明确的规格来开发出可靠的软件。该论文的研究成果出自于美国国防部弹道导弹高级技术中心的一个课题。

  上世纪70年代是第四代计算机出现的阶段,硬件采用了大规模集成电路,软件方面出现了面向对象编程语言,从那时起计算机变得更加通用,从科学计算等应用逐步走向家庭应用。

  1993年,Liskov等人发表了Specifications and their use in defining subtypes,从类型层次的角度进一步论证了规格的重要性。论文提到了封装的必要性,它可以使修改某种类型的实现时,该类型的使用者不会受到影响,因为新实现的类与原来的类有着同样的行为,但是问题在于怎么说明新的“行为”与所需要的行为是一致的呢?她认为旧的实现不能算作定义,因为其包括太多细节。因此需要一种独立于实现的东西,它能够描述行为,这就是规格。其实,在那个时候,规格一直是数据抽象编程方法学的奠基石,只是在实际使用面向对象编程时没有得到太多的关注。

  论文使用了Larch形式化规格语言,该语言在同年出版的《Larch: Languages and Tools for Formal Specification》一书中被广泛地推广。早在1973年,Liskov就组织了一个编程语言的讨论会,会上受Steve Zilles的启发,该书的作者们提出了对抽象数据类型的操作都可以用代数方法,用简单的等式来表示,于是他们考虑是否能将其运用到规格之上。但在1974年,他们认识到用纯粹的代数方法来表示规格是不切实际的。早期的Larch语言于1983年发布,后来在1985年第一次出版了全面的关于Larch语言的描述。尽管大家做了很多努力,在那时,“Larch in Five Easy Pieces”被读者们称为“Larch in Five Pieces of Varying Difficulty”,可见当时Larch语言不是很易于接受。到了1990年,一些关于Larch的软件工具开始出现,可用于规格的检查和推理。后来就有了越来越多的工具可供使用。

bug分析

1.bug记录表格

类型 方法代码行数 产生原因
Requires逻辑错误 8 复制粘贴后忘了修改
Requires不完整 5、9、14、15、30、31、32 没有注意到一些限制条件,一些方法没有写规格
Modifies不完整 14、15、30、31、32 一些方法没有写规格
Effects不完整 14、15、30、31、32 一些方法没有写规格
红绿灯编码错误 没有方法 规格写得不完整,编码时疏忽

2.功能bug与规格bug聚集关系分析

方法名 功能bug数 规格bug数
opposite() 0 1
getPoint() 0 1
Request() 0 1
run() 0 5
main() 0 1
1 0

从表格中可见,功能bug数和规格bug数相关性不大,可能需要对更多程序的分析才能得出更准确的结论。

前置条件与后置条件不好的写法

1.出租车接收订单的一个方法

1 /**
2  * @REQUIRES: scheduler != null; order != null;
3  * @MODIFIES: this;
4  * @EFFECTS:  This taxi is ready to take order;
5  * @THREAD_EFFECTS: \locked(this);
6  */

 后置条件比较模糊,可以写得更明确些。改进如下:

1 /**
2  * @REQUIRES: scheduler != null; order != null;
3  * @MODIFIES: this;
4  * @EFFECTS:  this.status == TaxiStatus.WAITING ==> (this.order == order && this.orderReady == true && \result == true);
5          this.status != TaxiStatus.WAITING ==> \result == false;
6  * @THREAD_EFFECTS: \locked(this);
7  */

2.出租车处于等待状态的方法

1 /**
2  * @REQUIRES: this.status == TaxiStatus.WAITING;
3  * @MODIFIES: this;
4  * @EFFECTS: this taxi waits for 20 seconds;
5  */

后置条件写得比较模糊,改进如下:

/**
 * @REQUIRES: this.status == TaxiStatus.WAITING;
 * @MODIFIES: this;
 * @EFFECTS: this taxi waits for 20 seconds;
 * !orderReady during 20 seconds ==> status = TaxiStatus.STOPED;
 */

3.出租车接单的方法

/**
 * @REQUIRES: passenger != null;
 * @MODIFIES: this;
 * @EFFECTS: this.position == passenger;
 *            passenger has got in this taxi;
 *             \result == the time when passenger gets in this taxi;
 */

前置条件不够完整,除了规定乘客位置不为null外,还应该限定位置的范围。

/**
 * @REQUIRES: passenger != null; 0 <=  passenger.x < 80 &&  0 <=  passenger.y < 80;
 * @MODIFIES: this;
 * @EFFECTS: this.position == passenger;
 *               passenger has got in this taxi;
 *               \result == the time when passenger gets in this taxi;
 */

4.寻找最高信用出租车的方法

/**
 * @REQUIRES: taxiList != null; !taxiList.isEmpty();
 * @MODIFIES: taxiList;
 * @EFFECTS: \result == taxiList里信用最高的出租车们;
 */

后置条件可以写得更加严谨一些,改进如下:

/**
 * @REQUIRES: taxiList != null; !taxiList.isEmpty();
 * @MODIFIES: taxiList;
 * @EFFECTS: (\all Taxi t; \result.contains(t); \all int i; 0 <= i < taxiList.size; t.credit >= taxiList[i].credit);
 * (\all Taxi t; \result.contains(t); taxiList.contains(t));
 * (\all int i, j; 0 <= i < j < taxiList.size; taxiList[i].credit >= taxiList[j].credit);
 */

5.根据编号获得某个出租车对象的方法

/**
 * @REQUIRES: 0 <= id < 100
 * @MODIFIES: None;
 * @EFFECTS: \result == taxi whose id equals parameter id;
 */

后置条件不准确,改进如下:

/**
 * @REQUIRES: 0 <= id < 100
 * @MODIFIES: None;
 * @EFFECTS: \result.id == id; this.taxis.contains(\result);
 */

6.获取指定状态的出租车编号的方法

/**
 * @REQUIRES: status != null;
 * @EFFECTS: \result == 状态为status的所有出租车编号数组;
 */

后置条件不准确,且比较局限。改进如下:

/**
 * @REQUIRES: status != null;
 * @EFFECTS: (\all Taxi t; \result.contains(t.id); t.status == status);
 * (\all Taxi t; \result.contains(t.id); this.taxis.contains(t.id));
 */

7.出租车送乘客到目的地的方法

/**
 * @REQUIRES: dest != null; dest != order.passenger; 
 *            pseudoTime == the time when passenger got in this taxi;
 * @MODIFIES: this;
 * @EFFECTS: this.position == dest;
 *                this.credit == \old(this.credit) + 3;
 */

前置条件不完整,改进如下:

/**
 * @REQUIRES: this.status == TaxiStatus.SERVING; 
 *                    dest != null; dest != order.passenger; 
 *            pseudoTime == the time when passenger got in this taxi;
 * @MODIFIES: this;
 * @EFFECTS: this.position == dest;
 *                this.credit == \old(this.credit) + 3;
 */    

8.添加行车记录的方法

/**
 * @REQUIRES: position != null;
 * @MODIFIES: this;
 * @EFFECTS:     (\all (Long, Position) (time, position); 
 * \old(this.record).contains((time, position) ==> this.record.contains((time, position));
 *    this.record.contains((time, position));
 * @THREAD_EFFECTS: \locked();
 */

前置条件不完整,改进如下:

/**
 * @REQUIRES: position != null; time != null; 0 <= position.x < 80; 0 <= 
 * position.y < 80;
 * @MODIFIES: this; 
 * @EFFECTS:     (\all (Long, Position) (time, position); 
 * \old(this.record).contains((time, position) ==> this.record.contains((time, 
 * position));
 *    this.record.contains((time, position));
 * @THREAD_EFFECTS: \locked();
 */

9.判断能否通过红绿灯的方法

/**
 * @EFFECTS: \result == whether the taxi at pos could pass from 'from' to 'to';
 * @THREAD_EFFECTS: \locked();
 */

前置条件对的要求可以写到EFFECTS里,抛出一个异常。

/**
 * @EFFECTS: \result == whether the taxi at pos could pass from 'from' to 'to';
 * pos == null || from == null || to == null ==> exceptional_behavior(NullPointerException);
 * !(0 <= pos.x < 80 && 0 <= pos.y < 80) ==> exceptional_behavior(IllegalPositionException);
 * @THREAD_EFFECTS: \locked();
 */

 10.管理出租车的类的一个方法

/**
 * @REQUIRES: taxi != null;
 * @MODIFIES: this;
 * @EFFECTS: this.taxi.contains((taxi.id, taxi));
 */

前置条件不完成,应该对出租车编号进行限定。

/**
 * @REQUIRES: taxi != null; 0 <= taxi.id < 100;
 * @MODIFIES: this;
 * @EFFECTS: this.taxi.contains((taxi.id, taxi));
 */

撰写规格的体会

  某一次作业中要求改变道路流量的计算方式,乍一看需要修改很多地方,比如有个int getFlow(Point p, Point q)方法,用来获取两个点之间的流量,规格如下:

/**
 * @REQUIRES: p != null; q != null; 0 <= p.getX() < 80; 0 <= p.getY() < 80;
 *                0 <= q.getX() < 80; 0 <= q.getY() < 80; p.nextTo(q);
 * @MODIFIES: None;
 * @EFFECTS: \result == the flow of road between q and q;
 * @THREAD_EEFECTS: \locked(flows);
 */

  后来发现只需要在管理流量的类中修改流量的更新方法,获取流量的方法例如getFlow(),但是在调用这些方法的地方,例如出租车查询道路流量,就不需要做修改,还是老样子。可见规格与抽象的好处,就像Liskov老太太说的那样:

可修改性(modifiability)——能够实现一个抽象,而不需要更改任何使用该抽象的其他抽象。

参考文献

[1]Barbara Liskov and Stephen Zilles. 1975. Specification techniques for data abstractions. In Proceedings of the international conference on Reliable software. ACM, New York, NY, USA, 72-87. DOI=http://dx.doi.org/10.1145/800027.808426

[2]P. C. Belford, A. F. Bond, D. G. Henderson, and L. S. Sellers. 1976. Specifications a key to effective software development. In Proceedings of the 2nd international conference on Software engineering (ICSE '76). IEEE Computer Society Press, Los Alamitos, CA, USA, 71-79.

[3]Barbara Liskov and Jeannette M. Wing. 1993. Specifications and their use in defining subtypes. In Proceedings of the eighth annual conference on Object-oriented programming systems, languages, and applications (OOPSLA '93). ACM, New York, NY, USA, 16-28. DOI=http://dx.doi.org/10.1145/165854.165863

[4]John V. Guttag and James J. Horning. 1993. Larch: Languages and Tools for Formal Specification. Springer-Verlag, Berlin, Heidelberg.

[5]Barbara Liskov,John Guttag.程序开发原理:抽象、规格与面向对象设计[M].裘健,译.北京:电子工业出版社,2006:25.

posted @ 2018-05-28 11:36  Kilotron  阅读(284)  评论(0编辑  收藏  举报