OO9~11次作业总结

一、规格化设计的发展历史

  第一代计算机软件出现在1946~1953年,使用机器语言编写,由0和1组成。不同的计算机使用不同的机器语言,程序员必须记住每条及其语言指令的二进制数字组合,因此,只有少数专业人员能够为计算机编写程序,这就大大限制了计算机的推广和使用。用机器语言进行程序设计不仅枯燥费时,而且容易出错。

  在这个时代的末期出现了汇编语言,它使用助记符表示每条机器语言指令,例如ADD表示加,SUB表示减,MOV表示移动数据。相对于机器语言,用汇编语言编写程序就容易多了。

  1954年后,计算机硬件变得更加强大,需要更强大的软件工具使计算机得到更有效地使用。IBM公司从1954年开始研制高级语言,同年发明了第一个用于科学与工程计算的FORTRAN语言。1959年,宾州大学的霍普(Grace Hopper)发明了第一个用于商业应用程序设计的COBOL语言。1964年达特茅斯学院的凯梅尼(John Kemeny)和卡茨(Thomas Kurtz)发明了BASIC语言

  此时,计算机软件实际上就是规模较小的程序,程序的编写者和使用者往往是同一个(或同一组)人。由于程序规模小,程序编写起来比较容易,也没有什么系统化的方法,对软件的开发过程更没有进行任何管理。这种个体化的软件开发环境使得软件设计往往只是在人们头脑中隐含进行的一个模糊过程,除了程序清单之外,没有其他文档资料。

  1965年后,计算机的性能进一步大幅提升,因此出现了分时操作系统,负责组织和安排各个作业。1968年荷兰计算机科学家狄杰斯特拉(Edsgar W.Dijkstra)发表了论文《GOTO语句的害处》,指出调试和修改程序的困难与程序中包含GOTO语句的数量成正比,从此,各种结构化程序设计理念逐渐确立起来。随着计算机应用的日益普及,软件数量急剧膨胀,在计算机软件的开发和维护过程中出现了一系列严重问题,例如:在程序运行时发现的问题必须设法改正;用户有了新的需求必须相应地修改程序;硬件或操作系统更新时,通常需要修改程序以适应新的环境。上述种种软件维护工作,以令人吃惊的比例消耗资源,更严重的是,许多程序的个体化特性使得他们最终成为不可维护的,“软件危机”就这样开始出现了。1968年,北大西洋公约组织的计算机科学家在联邦德国召开国际会议,讨论软件危机问题,在这次会议上正式提出并使用了“软件工程”这个名词。

  20世纪70年代出现了结构化程序设计技术,Pascal语言和Modula-2语言都是采用结构化程序设计规则制定的,Basic这种为第三代计算机设计的语言也被升级为具有结构化的版本,此外,还出现了灵活且功能强大的C语言。同时,许多不同的形式规格说明语言和软件开发方法也在不断发展。1974到1975年间,B.Liskow/S.N. ZillesJ. Guttag引入了"抽象数据类型"的概念。1976年E.W. Dijkstra定义了"最弱前置条件"的概念。1977年R.BurstallJ.Goguen提出了第一个代数规格说明语言:Clear。1980到1986年间C.Jones定义了VDM语言,也就是维也纳开发方法。19851992年间牛津大学的程序研究小组开发了Z规格说明语言。与此同时BP研究室开发了称之为B方法的面向模型的规格说明语言。从1991年开始,面向对象的形式规格说明语言开始发展,例如,Object-Z, VDM++, CafeOBJ等语言。随着时间的推进以及软件规模的扩大,规格化设计越来越得到人们的重视。

二、规格BUG及产生原因

  在最近三次作业中,互测同学均没有指出我的规格BUG。但是我自己认为还是存在一些问题的。

  出现规格问题的原因我认为主要有以下三点:

  (1)先完成了方法,后完成规格。当方法比较复杂时,完成的规格无法全面反映方法编写时的全部效果;

  (2)debug时对方法进行了修改,急于验证效果,当编程程序问题解决之后,忘记了更新修改方法的规格;

  (3)对于JSF的具体实现细节了解的不多,对于复杂的方法不清楚应当如何描述规格。

三、规格写法以及改进

前置条件:

  1、

/**
    * 读入测试文档(Load命令),如果文档符合要求,那么就将需要初始化的信息存储到Smap,Sflow,Staxi,Sreq中,如果不符合要求,退出
    * @REQUIRES: path!=NULL , path!="" , Smap!=NULL , Sflow!=NULL , Staxi!=NULL , Sreq!=NULL , light!=NULL
    * @MODIFIES : System.out,Smap,Sflow,Staxi,Sreq,light;
    * @EFFECTS : 
    * (File(path).exists && File(path).meetrequirement)==>(Smap.size!=0||Sflow.size!=0||Staxi.size!=0||Sreq.size!=0);
    * (!(File(path).exists && File(path).meetrequirement))==>System.out(error information);
    */
    public static void loadInit(String path,Vector<String> Smap,Vector<String> Sflow,Vector<String> Staxi,Vector<String> Sreq,int[][] light)

  前置条件应是一个可以判断的逻辑表达式,连接符应当是&& :

/**
    * 读入测试文档(Load命令),如果文档符合要求,那么就将需要初始化的信息存储到Smap,Sflow,Staxi,Sreq中,如果不符合要求,退出
    * @REQUIRES: path!=NULL && path!="" && Smap!=NULL && Sflow!=NULL && Staxi!=NULL && Sreq!=NULL && light!=NULL;
    * @MODIFIES : System.out,Smap,Sflow,Staxi,Sreq,light;
    * @EFFECTS : 
    * (File(path).exists && File(path).meetrequirement)==>(Smap.size!=0||Sflow.size!=0||Staxi.size!=0||Sreq.size!=0);
    * (!(File(path).exists && File(path).meetrequirement))==>System.out(error information);
    */
    public static void loadInit(String path,Vector<String> Smap,Vector<String> Sflow,Vector<String> Staxi,Vector<String> Sreq,int[][] light)

  2、

1 /**
2     * 初始化一个出租车对象,随机生成出租车的位置,给各项属性赋值
3     * @MODIFIES : this.x,this.y,this.num,this.state,this.credit,gui;
4     * @EFFECTS : 
5     * 给出租车对象的各种属性赋初值并在GUI上显示;
6     */
7     public TAXI(int i,TaxiGUI _gui,Graph _g,Output _out)

  这是一个普通出租车类的构造方法,起初没有考虑构造方法的前置条件,但实际上应当考虑各项参数的有效性,后修改为:

/**
    * 初始化一个出租车对象,随机生成出租车的位置,给各项属性赋值
    * @REQUIRES: 0<=i<=100 && _gui!=NULL && _g!=NULL && _out!=NULL;
    * @MODIFIES : this.x,this.y,this.num,this.state,this.credit,gui;
    * @EFFECTS : 
    * 给出租车对象的各种属性赋初值并在GUI上显示;
    */
    public TAXI(int i,TaxiGUI _gui,Graph _g,Output _out)

  3、

/**
    * 求(x,y)到各个可能方向上的流量值,存储到_flow[]中
    * @REQUIRES:0<=x<80 && 0<=y<80 && _flow!=NULL && dir!=NULL;
    * @MODIFIES: _flow[];
    * @EFFECTS: 
    * _flow[]中存储于dir[]对应的方向的流量值
    */
    public synchronized void getFlow(int x,int y,int[] _flow,int[] dir,int count)

  前置条件的要求不够细致全面,应当补全为:

/**
    * 求(x,y)到各个可能方向上的流量值,存储到_flow[]中
    * @REQUIRES:0<=x<80 && 0<=y<80 && _flow!=NULL && _flow.length==count && dir!=NULL && dir.length==count && count>0;
    * @MODIFIES: _flow[];
    * @EFFECTS: 
    * _flow[]中存储于dir[]对应的方向的流量值
    */
    public synchronized void getFlow(int x,int y,int[] _flow,int[] dir,int count)

  4、

/**
    * 出租车为等待服务状态,将出租车变为接单状态并修改出租车的req属性;
    * @REQUIRES: _req!=NULL && this.gui!=NULL;
    * @MODIFIES : this.state,this.req,this.gui;
    * @EFFECTS : 
    * (this.state==2)==>(this.state==1 && this.req==_req) && \result==true;
    * (this.state!=2)==>\result==false;
    */
    public synchronized boolean getandsetState(CRequest _req)

  没有对参数的类型进行判断,应当改为:

/**
    * 出租车为等待服务状态,将出租车变为接单状态并修改出租车的req属性;
    * @REQUIRES: _req!=NULL && this.gui!=NULL && _req instanceof CRequest;
    * @MODIFIES : this.state,this.req,this.gui;
    * @EFFECTS : 
    * (this.state==2)==>(this.state==1 && this.req==_req) && \result==true;
    * (this.state!=2)==>\result==false;
    */
    public synchronized boolean getandsetState(CRequest _req)

  5、

/**
    * 根据spfa计算的最优路径信息,返回目标为no1号点的出租车下一步应该去的位置;
    * @MODIFIES: None;
    * @EFFECTS: 
    * 返回目标为no1号点的出租车下一步应该去的点的编号;
    */
    public int printPath(int no1,int no2,int pre[])

  没有明确前置条件,应当改为:

/**
    * 根据spfa计算的最优路径信息,返回目标为no1号点的出租车下一步应该去的位置;
    * @REQUIRES: (0<=no1<6400 && 0<=no2<6400 && pre!=NULL);
    * @MODIFIES: None;
    * @EFFECTS: 
    * 返回目标为no1号点的出租车下一步应该去的点的编号;
    */
    public int printPath(int no1,int no2,int pre[])

后置条件:

  1、

/**
    * 读入测试文档(Load命令),如果文档符合要求,那么就将需要初始化的信息存储到Smap,Sflow,Staxi,Sreq中,如果不符合要求,退出
    * @REQUIRES: path!=NULL && path!="" && Smap!=NULL && Sflow!=NULL && Staxi!=NULL && Sreq!=NULL && light!=NULL;
    * @MODIFIES : System.out,Smap,Sflow,Staxi,Sreq,light;
    * @EFFECTS : loadfile中的信息存储到Smap,Sflow,Staxi,Sreq,light中,并输出错误信息;
    */
    public static void loadInit(String path,Vector<String> Smap,Vector<String> Sflow,Vector<String> Staxi,Vector<String> Sreq,int[][] light)

  尽量避免使用自然语言,应当改为:

/**
    * 读入测试文档(Load命令),如果文档符合要求,那么就将需要初始化的信息存储到Smap,Sflow,Staxi,Sreq中,如果不符合要求,退出
    * @REQUIRES: path!=NULL && path!="" && Smap!=NULL && Sflow!=NULL && Staxi!=NULL && Sreq!=NULL && light!=NULL;
    * @MODIFIES : System.out,Smap,Sflow,Staxi,Sreq,light;
    * @EFFECTS : 
    * (File(path).exists && File(path).meetrequirement)==>(Smap.size!=0||Sflow.size!=0||Staxi.size!=0||Sreq.size!=0);
    * (!(File(path).exists && File(path).meetrequirement))==>System.out(error information);
    */
    public static void loadInit(String path,Vector<String> Smap,Vector<String> Sflow,Vector<String> Staxi,Vector<String> Sreq,int[][] light)

  2、

/**
    * 判断输入的字符串,如果输入的字符串符合规定的指令,那么就解析相关指令信息,存储到req中,返回1;如果指令是测试接口指令,那么返回相应的数值;如果是非法指令,返回0;
    * @REQUIRES: str!="" && req!=NULL;
    * @MODIFIES : System.out,req;
    * @EFFECTS : 
    * 如果字符串是合法请求,存入req中,并返回1;如果是非法请求,返回其他数值;
    */
    public static int inputcheck(String str,CRequest req)

  避免使用自然语言,并且表达应当覆盖所有可能分支,改为:

/**
    * 判断输入的字符串,如果输入的字符串符合规定的指令,那么就解析相关指令信息,存储到req中,返回1;如果指令是测试接口指令,那么返回相应的数值;如果是非法指令,返回0;
    * @REQUIRES: str!="" && req!=NULL;
    * @MODIFIES : System.out,req;
    * @EFFECTS : 
    * (str.isLegalRequest)==>(req.time!=0 && (!(req.x==0 && req.tx==0 && req.y==0 && req.ty==0)))&&\result==1;
    * (str.isTrequest)==>\result==-2;
    * (str.isSrequest)==>\result==-3;
    * (str.isRrequest)==>\result==-4;
    * (str.isillegal)==>\result==0;
    * (str.equals("END"))==>\result==-1;
    */
    public static int inputcheck(String str,CRequest req)

  3、

/**
    * 拷贝指令到t
    * @REQUIRES: this!=NULL && t!=NULL;
    * @MODIFIES: t.x,t.y,t.tx,t.ty,t.time;
    * @EFFECTS: 
    * t.x==this.x , t.y==this.y , t.tx==this.tx , t.ty==this.ty , t.time==this.time;
    */
    public void copy(CRequest t) 

  应当使用&&连接符连接多个条件,改为:

/**
    * 拷贝指令到t
    * @REQUIRES: this!=NULL && t!=NULL;
    * @MODIFIES: t.x,t.y,t.tx,t.ty,t.time;
    * @EFFECTS: 
    * (t.x==this.x && t.y==this.y && t.tx==this.tx && t.ty==this.ty && t.time==this.time);
    */
    public void copy(CRequest t) 

  4、

/**
    * 判断(x1,y1)和(x2,y2)是否相连
    * @MODIFIES: None;
    * @EFFECTS: 
    * graph[x1*80+y1][x2*80+y2]==1 ==> \result==true;
    * !(graph[x1*80+y1][x2*80+y2]==1) ==> \result==false;
    */
    public boolean isConnected(int x1,int y1,int x2,int y2) 

  遗漏某些条件,应当改为:

/**
    * 判断(x1,y1)和(x2,y2)是否相连
    * @MODIFIES: None;
    * @EFFECTS: 
    * (x1>=0 && x1<80 && y1>=0 && y1<80 && x2>=0 && x2<80 && y2>=0 && y2<80 && graph[x1*80+y1][x2*80+y2]==1) ==> \result==true;
    * (!(x1>=0 && x1<80 && y1>=0 && y1<80 && x2>=0 && x2<80 && y2>=0 && y2<80 && graph[x1*80+y1][x2*80+y2]==1)) ==> \result==false;
    */
    public boolean isConnected(int x1,int y1,int x2,int y2) 

  5、

/**
    * 将存储在this.flows中有用的流量信息存储到_flows中
   * @REQUIRES: 0<=x<80 && 0<=y<80 && _flows!=NULL;
    * @MODIFIES: _flows;
    * @EFFECTS: 
    * 有用的流量信息被存储到_flows中;
    */
    public synchronized void flows2flow(int x,int y, ArrayList<Flow> _flows)

  尽量避免使用自然语言,应当改为:

/**
    * 将存储在this.flows中有用的流量信息存储到_flows中
   * @REQUIRES: 0<=x<80 && 0<=y<80 && _flows!=NULL; * @MODIFIES: _flows; * @EFFECTS: * (\all int i;0<=i<flows.size; flows.get(i).from==x*80+y && (abs(flows.get(i).to-(x*80+y))==1 || abs(flows.get(i).to-(x*80+y))==80); _flows.contains(flows.get(i))==true )
*/ public synchronized void flows2flow(int x,int y, ArrayList<Flow> _flows)

四、功能bug与规格bug在方法上的聚集关系

  在最近三次作业中,没有被报规格BUG,并且除了第10次作业,其余两次作业没有出现功能性BUG。

  第十次作业中,被报的三个BUG均是由于一个获取上一个500ms流量的方法在实现时出现了BUG。对于这个获取流量的方法,由于可能出现多个出租车线程同时访问,所以存在线程安全问题。我在设计时考虑了此问题,对此将其中对于地图信息操作的部分加了锁。可能是我的测试不够充分,本地运行无误的情况下,在互测同学运行时出现了错误。在后续的作业中,我采用了新的流量计算方法,解决了这个问题。

  方法名 错误原因 功能BUG数 规格BUG数
OO10 graph.getFlow(); 对于线程安全问题考虑不全 3 0

  对于这个出错的方法,其JSF并不存在问题,但是我没有按照JSF准确无误的实现这个方法。这主要是由于先写代码,后补充规格的设计顺序造成的。可以看到,规格在程序设计中是多么重要!

五、感想体会

  我认为课上对于规格的讲解过于缺乏。以JSF为例,我们在一次课堂上能获得的知识实在过于有限,而且大多是理论层面的,课上仅仅给出几个简单的例子,然后课下作业就要求我们将所有的方法都书写JSF。我认为在这种情况下要将JSF写好,并且真正起到正面作用是不可能的。课上给出的例子都是类似数值计算,标准的数据结构的操作等等容易使用逻辑表达式进行表达的例子。但是我们的作业是模拟出租车运行这种比较抽象的活动,对于我们来说其中存在太多太多不知道该怎么表达的情况。举个例子,一个将地图文件转化为图的邻接矩阵的方法,我应当如何表示邻接矩阵中存储了正确的文件中图的信息呢?每个元素都是0或者1么?但是每个元素都是0或者1也不能表达正确的图的全部信息。再比如说如何表示向控制台输出某些信息?main(),run()这种方法运行起来之后没有特殊情况不会停止的方法又应当如何表示后置条件呢?上述的几个问题仅仅是完成作业中遇到问题的冰山一角。对于这些问题,连助教都无法做出统一有效的回复。由于给出的JSF标准中没有这些情况的具体例子,我们无奈只能使用自然语言或者其他方法进行表达。让规格应当起到的作用大打折扣。

  如果情况至此结束倒还好,但更加令我无奈的是我们在没有统一标准的情况下需要对其他同学的规格进行评测,并且可以拿到分数。这难道不是一种不公平么?心狠手辣就可以拿到很高的分数,放宽标准就可能被别人狠宰一刀。这已经偏离了这门课的目的。

  在课下作业已经结束之后的OO实验课上我们才看到了规范合理的JSF,但是此时提交的作业已经无法修改,而且我认为OO实验课上给出的JSF以及其对应的方法仍然是数值修改,标准数据结构操作之类十分容易进行逻辑表达的方法,与我们的作业情况仍是相距甚远。我相信大多数同学对于OO实验课上给出的JSF都能够正确的写出对应的方法,也能够根据方法写出规范的JSF。但是对于我们的作业,要做到一样的程度实在是过于艰难。

  种种问题导致我认为规格设计并不能起到课上宣传的所谓积极作用,最起码在我们的作业中看来是这样。书写规范成为了应付作业,敷衍了事,甚至因为可能被扣分而影响了正常的程序设计,成为了一个很大的负担。我希望课程组能够广泛听取同学们的意见,将OO课程越办越好。

posted @ 2018-05-30 16:56  sincesummer  阅读(225)  评论(0编辑  收藏  举报