一、关于规格化设计
发展历史
参考:https://blog.csdn.net/jnucstan/article/details/1724259
从20世纪60年代开始,就存在着许多不同的形式规格说明语言和软件开发方法。在形式规格说明领域一些最主要的发展过程列举如下:
1969-1972 C.A.R Hoare撰写了"计算机编程的公理基础(An Axiomatic Basis for Computer Programming)"和"数据表示的正确性证明"两篇开创性的论文,并提出了规格说明的概念。
1974-1975 B.Liskow/S.N. Zilles和J. Guttag引入了"抽象数据类型"的概念。
1976 E.W. Dijkstra定义了"最弱前置条件"的概念
1977 R.Burstall和J.Goguen提出了第一个代数规格说明语言:Clear
1988 Standford的SRI开发了代数规格说明语言OBJ3
1980-1986 C.Jones定义了VDM语言,也就是维也纳开发方法。
1985-1992 牛津大学的程序研究小组开发了Z规格说明语言。与此同时BP研究室开发了称之为B方法的面向模型的规格说明语言。
1985-1993 在MIT和Digital SRC开发了代数规格说明语言Larch
从1991年开始,面向对象的形式规格说明语言开始发展,例如,Object-Z, VDM++, CafeOBJ等语言。
1996-2000年 在欧洲CoFI(Common Framework Initiative)项目资助下开发"统一"代数规格说明语言CASL(Common Algebraic Specification Language)
上述规格说明语言可以分为两大类:
l 基于代数和公理方法(Clear, OBJ, Larch, CafeOBJ)
l 基于模型的方法(VDM, Z, B, Object-Z)
受到重视的原因
形式化规格方法 可以用于软件工程的需求分析、概要设计和详细设计阶段。形式化规格说明是 发现描述错误和给出无二义性描述的极佳方法。 形式化规格说明在描述面向对象程序方法具有很多优点,基于抽象数据类型 发展来的代数规格化说明描述方法可以很好的和面向对象程序设计结合起来, 抽象数据类型和面向对象程序设计语言具有相同的封装和继承等特征。基于代 数规格化说明的程序测试可以很好的对面向对象程序特征进行测试。由于代数 规格化说明语言基于严格的数学理论,具有很好的无二义性,因此,基于代数 规格化说明语言的面向对象程序测试能够很容易的测试出程序中的错误,这是 其他面向对象程序测试方法所不能比拟的。
二、bug说明及分析
规格bug类型 | 代码行数 |
Overview是否明确抽象对象 | 26 |
Requires不完整 | 35 |
bug分析:关于自己的这两个规格bug,第一个bug产生的原因是对于Overview的书写不熟练,导致抽象层次不够,过于关注具体实现而没能从更高的角度去阐述一个类的功能职责。第二个bug的产生原因是自己对于规格语言的书写没有深入理解与掌握,没有分清什么是对方法的要求什么是对方法使用者的要求导致自己很多Requires都写成None
三、规格缺陷及改进
1、REQUIRES漏写相关约束
改进前:
/** * @REQUIRES: None; * @MODIFIES: this.x,this.y,this.preVertex,this.dis; * @EFFECTS: this.x == x; this.y == y; this.preVertex == new ArrayList<point>(); this.dis == -1; */ public point(int x,int y) { this.x = x; this.y = y; this.preVertex = new ArrayList<point>(); this.dis = -1; }
改进后:
/** * @REQUIRES: x>=0&& x<=79&& y>=0&& y<=79; * @MODIFIES: this.x,this.y,this.preVertex,this.dis; * @EFFECTS: this.x == x; this.y == y; this.preVertex == new ArrayList<point>(); this.dis == -1; */ public point(int x,int y) { this.x = x; this.y = y; this.preVertex = new ArrayList<point>(); this.dis = -1; }
2、REQUIRES考虑不全
改进前:
/** * @REQUIRES: S!=null&&E!=null; * @MODIFIES: this.RC,this.T,this.S,this.E; * @EFFECTS: this.RC == Rclass.CR; this.T == T; this.S == S; this.E == E; */ public CR(Point S,Point E,double T) { this.RC = Rclass.CR; this.T = T; this.S = S; this.E = E; }
改进后:
/** * @REQUIRES: S!=null&&E!=null&&T>0; * @MODIFIES: this.RC,this.T,this.S,this.E; * @EFFECTS: this.RC == Rclass.CR; this.T == T; this.S == S; this.E == E; */ public CR(Point S,Point E,double T) { this.RC = Rclass.CR; this.T = T; this.S = S; this.E = E; }
3、EFFECTS不是布尔表达式
改进前:
/** * @REQUIRES: None; * @MODIFIES: None; * @EFFECTS: new LightController().start(); new Controller(TL,Q).start(); new Query(TL,A).start(); new Scan(Q,A,EC).start(); new TaxiManager(TL,EC).start(); */ public static void main(String[] args) { }
改进后:
/** * @REQUIRES: None; * @MODIFIES: None; * @EFFECTS: new LightController().start() == true; new Controller(TL,Q).start() == true; new Query(TL,A).start() == true; new Scan(Q,A,EC).start() == true; new TaxiManager(TL,EC).start() == true; */ public static void main(String[] args) { }
4、EFFECTS中未使用\old关键词区分
改进前:
/** * @REQUIRES: a!=null; * @MODIFIES: this.Queue,this.Rnum; * @EFFECTS: Queue.contains(a); Rnum == Rnum + 1; */ public void Qin(Request a) { Queue.add(a); Rnum++; }
改进后:
/** * @REQUIRES: a!=null; * @MODIFIES: this.Queue,this.Rnum; * @EFFECTS: Queue.contains(a); Rnum == \old(Rnum) + 1; */ public void Qin(Request a) { Queue.add(a); Rnum++; }
5、EFFECTS中\result情况未考虑周全
改进前:
/** * @REQUIRES: S!=null; * @MODIFIES: None; * @EFFECTS: (S.equals("WFS"))==>\result == TaxiState.WFS; (S.equals("SS"))==>\result == TaxiState.SS; (S.equals("GFS"))==>\result == TaxiState.GFS; (S.equals("S"))==>\result == TaxiState.S; */ private TaxiState parse(String S) { if(S.equals("WFS")) { return TaxiState.WFS; }else if(S.equals("SS")) { return TaxiState.SS; }else if(S.equals("GFS")) { return TaxiState.GFS; }else { return TaxiState.S; } }
改进后:
/** * @REQUIRES: S!=null; * @MODIFIES: None; * @EFFECTS: (S.equals("WFS"))==>\result == TaxiState.WFS; (S.equals("SS"))==>\result == TaxiState.SS; (S.equals("GFS"))==>\result == TaxiState.GFS; (S.equals("S"))==>\result == TaxiState.S; (S.equals(null))==>\result == null; */ private TaxiState parse(String S) { if(S.equals("WFS")) { return TaxiState.WFS; }else if(S.equals("SS")) { return TaxiState.SS; }else if(S.equals("GFS")) { return TaxiState.GFS; }else { return TaxiState.S; } }
6、线程EFFECTS遗漏
改进前:
/** * @REQUIRES: x1>=0&&x1<=79&&y1>=0&&y1<=79&&x2>=0&&x2<=79&&y2>=0&&y2<=79&&time>0; * @MODIFIES: this.flowmap; * @EFFECTS: (this.flowMap.get(Key(x1,y1,x2,y2)) == null) ==> this.flowMap.get(Key(x1,y1,x2,y2))==new ArrayList<Long>(); !(this.flowMap.get(Key(x1,y1,x2,y2)) == null) ==> flows.add(time)&&this.flowMap.put(Key(x1,y1,x2,y2), flows)==true&&this.flowMap.put(Key(x1,y1,x2,y2), flows)==true; */ public static void AddFlow_Init(int x1,int y1,int x2,int y2,long time){ synchronized (flowMap) { ArrayList<Long> flows; boolean nullflag = false; if((flows = flowMap.get(Key(x1,y1,x2,y2))) == null) { nullflag = true; flows = new ArrayList<Long>(); } flows.add(time); if(nullflag) { flowMap.put(Key(x1,y1,x2,y2), flows); flowMap.put(Key(x2,y2,x1,y1), flows); } } }
改进后:
/** * @REQUIRES: x1>=0&&x1<=79&&y1>=0&&y1<=79&&x2>=0&&x2<=79&&y2>=0&&y2<=79&&time>0; * @MODIFIES: this.flowmap; * @EFFECTS: (this.flowMap.get(Key(x1,y1,x2,y2)) == null) ==> this.flowMap.get(Key(x1,y1,x2,y2))==new ArrayList<Long>(); !(this.flowMap.get(Key(x1,y1,x2,y2)) == null) ==> flows.add(time)&&this.flowMap.put(Key(x1,y1,x2,y2), flows)==true&&this.flowMap.put(Key(x1,y1,x2,y2), flows)==true; * @THREAD_EFFECTS: \locked(this.flowMap); */ public static void AddFlow_Init(int x1,int y1,int x2,int y2,long time){ synchronized (flowMap) { ArrayList<Long> flows; boolean nullflag = false; if((flows = flowMap.get(Key(x1,y1,x2,y2))) == null) { nullflag = true; flows = new ArrayList<Long>(); } flows.add(time); if(nullflag) { flowMap.put(Key(x1,y1,x2,y2), flows); flowMap.put(Key(x2,y2,x1,y1), flows); } } }
四、bug聚集关系分析
暂无功能bug与规格bug在同一个方法出现的案例。
规格bug分析:
/* * Overview: 线程类,线程类,如果index等于R.getRnum执行wait方法,否则为index下标到R.getRnum下标的每个请求开启一个监视线程 */ public void run() { while(true) { synchronized(R) { if(index==R.getRnum()) { try { R.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { while(index!=R.getRnum()) { new Monitor((CR)R.Get(index++),TR).start(); } } } } }
这里的Overview写的过于具体,针对了具体实现而没有上升到抽象层次
更正:Overview:用来为每个乘客请求开启一个7.5s的线程,无新的乘客请求时执行等待方法
/** * @REQUIRES: None; * @MODIFIES: this.x,this.y,this.preVertex,this.dis; * @EFFECTS: this.x == x; this.y == y; this.preVertex == new ArrayList<point>(); this.dis == -1; */ public point(int x,int y) { this.x = x; this.y = y; this.preVertex = new ArrayList<point>(); this.dis = -1; }
这里的REQUIRES没有指明x,y的取值范围,这应该是对方法使用者的约束,应该说明
更正: @REQUIRES: x>=0&&x<=79&&y>=0&&y<=79;
五、规格撰写思路与体会
1、对于REQUIRES要明确什么是对方法使用者的要求,什么是对方法的要求,否则会漏掉相关的说明,除此之外要涵盖所有的传入参数做到全面
2、对于MODIFIES要明确哪些修改的变量是用户可见的,对于中间变量不需要作多余说明
3、对于EFFECTS要把所有的返回值情况考虑清楚,比如对于一个boolean类型方法不能只考虑返回值为true的情况
4、对于后置条件要区分修改变量的新旧,注意使用\old关键词
5、后置条件必须为布尔表达式,类似于线程启动的例子,要在new Thread().start后加上==true
6、写新的方法的时候如果先书写规格可以减少方法出错的概率,能帮助自己思考更多的方面,还可以对完成的方法进行检验,有助于及时发现bug
7、规格相当于方法的书写思路起到纲领性的作用,有助于提高方法的简洁程度
8、鸡蛋里挑骨头,暴扣JSF 影响别人游戏体验的人真的很LOW