oo第三次总结
规格化发展历史
在1960年代末至1970年代初期,出现了一次软件危机:一方面需要大量的软件系统,如操作系统、数据库管理系统;另一方面,软件研制周期长,可靠性差,维护困难。人们希望编写出的程序结构清晰、易阅读、易修改、易验证,即得到良好结构的程序。1968年,北大西洋公约组织(NATO)在西德召开了第一次软件工程会议,分析了危机的局面,研究了问题的根源,第一次提出了用工程学的办法解决软件研制和生产的问题。1968年,Dijkstra提出了“GOTO是有害的”,希望通过程序的静态结构的良好性保证程序的动态运行的正确性。1969年,Wirth提出采用“自顶向下逐步求精、分而治之”的原则进行大型程序的设计。其基本思想是:从欲求解的原问题出发,运用科学抽象的方法,把它分解成若干相对独立的小问题,依次细化,直至各个小问题获得解决为止。
在1970年代到1980年代,规格说明(Spec)和体(body)的分离说明是类型定义和操作描述,体是操作的具体实现。(具体的例子就是Java等面向对象语言的类说明与类实现的分离。)解决方案设计只关注说明,实现时引用或者设计体。体的更改、置换不影响规格说明,保证了可移植性。支持多机系统,但要同样环境。此时产生了划时代的面向对象技术。
从上世纪末到本世纪,对象使用和对象实现的分离基于构件开发:标准化的软件构件如同硬件IC,可插拔,使用者只用外特性,不计内部实现。Web Services:软件就是服务。分布式,跨平台,松耦合。
规格化为什么被大家重视
一方面,规格化设计可以帮助开发者理清思路,构建一个写程序的框架,这样一来开发者可以按照既定的套路来完成自己的任务,从而能够摒弃杂念,提高效率。
另一方面,一个程序中很多代码会以一块一块的形式被反复使用,这些重复的代码块有可能被封装为函数反复使用,也有可能被放入库中供他人使用。如果能按照一定的规格去完成他们,那么调用者不必大费周折的去理解代码,可以通过已有的对于格式的学习以及清晰明了的格式来快速完成对代码的理解,从而提高学习工作的效率。
还有一点,在多人合作的工程当中,不同人之间的代码可能需要不少交互,这种情况下,规格化既可以让工程人员之间更好的理解对方的代码,也可以使代码之间的接口更加规范简单。同时增加了程序的可扩展性。可见规格化必然受到广大工程人员的喜爱。
bug分析
jsf方面
1 synchronized void reqCall() 2 /** 3 * @REQUIRES:inq[i]!=null 4 * @MODIFIES:taxi 5 * @EFFECTS:taxi get call 6 */ 7 { 8 for(int i=0;i<75;++i) 9 { 10 //Output.debug("i="+i); 11 for(int j=0;j<inq[i].size();++j) 12 for(int k=0;k<100;++k) 13 { 14 //Output.debug(inq[i].get(j).toString()+" taxi:"+taxi[k].getPosX()+","+taxi[k].getPosY()); 15 if(inq[i].get(j).canTake(taxi[k]) && !inq[i].get(j).hasCall(k)) 16 { 17 taxi[k].getCall(); 18 inq[i].get(j).add(k); 19 inq[i].get(j).call(k); 20 Output.println("taxi "+k+" wants request:"+inq[i].get(j).toString()); 21 } 22 } 23 } 24 }
如以上代码中,尽管逻辑较为负载,并且使用了循环语句,但是其作用是十分显而易见的,其不需要使用自然语言。事实上可以写成
/** * @REQUIRES:inq[i]!=null * @MODIFIES:taxi * @EFFECTS:for all inq[i][j] && inq[i][j].canTake(taxi[k]) && !inq[i][j].hasCall(k)==>inq[i][j].call(k) */
由上可以看出,能够使用简明的逻辑表达式的地方尽量不要使用自然语言。
此外我的问题还有规格前置条件不明晰的错误,例如:
1 synchronized void driving(long time) 2 /** 3 * @REQUIRES:this 4 * @MODIFIES:this 5 * @EFFECTS:sleep until time ends 6 */ 7 { 8 try 9 { 10 { 11 while(atCross()) 12 { 13 time_line.taxi_wait[num] = time_line.getTime()+1; 14 while(time_line.getTime()<time_line.taxi_wait[num]) Thread.sleep(1); 15 } 16 time_line.taxi_wait[num] = time_line.getTime()+time; 17 while(time_line.getTime()<time_line.taxi_wait[num]) Thread.sleep(1); 18 } 19 } 20 catch(Exception e) 21 { 22 Output.debug("wrong while driving!"); 23 e.printStackTrace(); 24 System.exit(0); 25 } 26 //last_time+=time; 27 updateGUI(); 28 }
在上面的代码中,除了使用自然语言这个错误外,其前置条件应该更加精确,而不是一个this,而且MODIFIES也有错误,time_line并不是属于出租车的子类,所以MODIFIES写成this是错误的,正确姿势如下:
/** * @REQUIRES:time_line.taxi_wait[ his.num]!=null && this!=null * @MODIFIES:time_line.taxi_wait[ his.num] * @EFFECTS:while(time_line.getTime()<time_line.taxi_wait[num]) sleep; */
以上两个就是我在这三次作业中jsf所犯的典型错误,在以后的工程生涯中希望可以逐渐改变这一点,是代码更易懂且思路更清晰。
功能方面
1.由于使用gui所给的路径寻找算法比较慢,所以在算稍微大一些数据是就会卡,时间也会发生误差。
2.第10次作业使用gui的最短路径寻找算法时方向反了,应该从终点开始寻找最短路才能把流量考虑进去。
3.第十一次作业时使用类的继承时,父类调用了错误的子类函数,导致程序卡死。
以上就是这三次作业的功能性bug
规格不好的写法
以下是不推荐的规格写法
前置条件
(1)对于方法
public boolean method(Object[] args)
不推荐
@REQUIRES: args != null;
因为args作为数组,其内部成员有可能为null,所以应当写为
@REQUIRES:all element in args==>element!=null
(2)当方法需要某个类的所有属性都不为空时,不建议全部列出来,而应该写成
@REQUIRES:for all field in this==>field is legal
这样比较简单易读
(3)对于一些方法的要求可能在调用它的函数中就已经满足要求了,所以我认为不需要重复说明,例如这几次作业中对于出租车类中的设定位置的函数判断其位置是否合法,由于在input类中就已经判断过,再次判断则属于冗余了
(4)当有多个条件时,不建议使用空格分隔,而应该使用&&或||等逻辑关系显式联系起来
不推荐
@REQUIRES:0<=x<80 0<=y<80
应该写成
@REQUIRES:0<=x<80 && 0<=y<80
(5)当出现数组作为前置条件时,应当将数组的具体访问部分限制且需求其为合法,而不需调用的部分不应该管
例如当需要判断前30个特殊出租车的需求时
@REQUIRES:for all 0<=i<100 taxi[i].request!=null
应替换为
@REQUIRES:for all 0<=i<30 taxi[i].request!=null
而不应该画蛇添足
后置条件
(6)有同学在输出出租/车信息的函数中的后置条件是这么写的
@EFFECTS: esult = taxinum arrived pos(x,y) at time:xxxxx
实质上这并不是返回的值,我个人认为 esult只能是严格意义上的return的值,所以我认为应该改成
@EFFECTS:print taxinum arrived pos(x,y) at time:xxxxx
(7)事实上有些同学在写后置条件时,为了避免麻烦,可能会直接把代码全部贴上去,个人认为这样不可取,jsf的本意就是希望程序员能够将代码抽象为逻辑表达式从而更加简单易读,而不应该为了省事贴代码
(8)对于过长的代码逻辑,不建议强行写成逻辑表达式,反而更容易让人迷糊,建议在适当的时候可以增加自然语言,比如调度器调度出租车的逻辑
(9)对于代码的逻辑有前后顺序的函数
synchronized void relaxing()
{
/*Output.debug("relaxing");*/ cur = state.relaxing; driving(1000); cur = state.waiting;
}
其代码逻辑中cur在sleep前后都修改了,对此我一开始使用了因果关系的符号==>,但是正确来说应该把时序逻辑表达出来
@EFFECTS:(1) his.cur = relaxing (2)sleep(1000) (3) his.cur = waiting
(10)对于方法的要求如
@EFFECTS:(id<0 && id<=100)==> esults = true
其中的100建议用一个常量代替,从而增加程序的可读性和可扩展性
bug记录
第九次作业:2功能性错误,4个jsf错误
第十次作业:2个功能性错误,3个jsf错误
第十一次作业:子类方法的调用与gui交互出现错误导致程序崩溃,2个crash,2个error,无jsf错误
jsf错误在之前已经分析过了,但是我的功能性错误都是不同类和县城之间交互出现的错误,也不太能精准定位到某个了的错误,也就不赘述了。。
个人认为可以精确定位到代码的错误反而比较好发现且好挑,难的是交互之间出现的问题。。
心得体会
oo的编程就在这里学完了,首先我有一个小小的建议:那就是指导书的需求能不能详细且准确一点,一届届下来指导书都没有改进,实在让人不太好接受。。而且各种readme只会让人陷入扣细节的疯狂之中。。
在这几次的oo作业中,我着重习得了jsf规格的书写,以前代码的不规范和不好读正在渐渐改变。
而且这几次的作业一次次都是功能的扩展,这对于我们代码的可扩展性也有较高的要求,我就因为代码的不可扩展性吃了大亏,希望我能吃一堑长一智,以后能写出,易读,高效,且可扩展性强的代码。