一、调研
面向对象方法是一个抽象过程,它把一系列对象共性特征形成的类型作为类提取出来,并识别类的操作和类的属性,从结构上和行为上实现抽象。面向对象语言提供了结构抽象和行为抽象,通过类从结构上把数据和操作聚合在一起,接口把一组类的公共行为抽象出来,通过继承可以形成不同的抽象层次。然而,面向对象语言并没有提供规格抽象,规格是对一个方法/类/程序的外部可感知的行为的抽象表示,合理使用规格可以帮助程序员规划代码和维护代码,关于如何表示规格抽象,也已经有很多的研究。
1、总结介绍规格化设计的大致发展历史
保证程序的正确性以及减少软件错误一直是研究者们关注的问题。1959年,Hoare提出了基于“前置后置条件”的接口规格方法,即若一种方法在执行前满足前置条件,运行后满足后置条件,那么这种条件就是正确的。Hoare提出的这个结论为规格化发展起到了奠基的作用。
针对面向对象的方法,人们需要发展Hoare的理论以适应面向对象的特殊性。1957年之后,出现了使用代数公理验证规格的理论。Guttag使用抽象的数据类型来降低设计和实现大型软件的复杂度,用代数公理证明规格的正确性。Goguen和Zilles都着重强调代数化方法,使用代数方法验证抽象数据类型的规格化的正确性。使用抽象数据类型的代数规格法虽然可以描述方法之间的相关关系,但是失去了对单个方法的准确规格定义。
针对各方法的精确规格定义,1992年,Meyer提出契约式编程延续了对方法的精确定义,但其不足是依赖内部状态来体现方法之间的相互依赖。Robert也引入契约来表达接口的前置条件和后置条件,在调用这个接口时可以用前置和后置条件验证正确性,在调用这个接口时可以用前置条件和后置条件验证正确性。但Robert提出使用带有断言的lambda计算来检测系统运行,这使得断言和程序语句难以区分,影响阅读也影响了程序的运行效率。
Yoonsik也尝试使用模型方法和模型变量来解决上述问题,并建立了规格方法JML,JML不需要直接引用具体的程序状态,而是使用抽象的模型变量来代表内部数据状态,模型方法使用模型变量进行规格,从而避免了实际代码和抽象规格的混乱。但这种方法中的模型变量难以定义,模型变量与实际代码之间的关系也难以界定。
也有研究者使用状态抽象的方法进行类的规格定义。1995年,Hoffman提出了一种状态抽象模块,它提供了一个好的数学模型,并且提高了抽象规格的清晰性和易用性。状态的抽象提高了接口的抽象程度,但是状态的变化使得模块的接口难以定义。堆方法是基于抽象状态方法扩充出来的另一种思路。堆方法是在对象引用和对象状态之间建立映射,堆中可以找到由区域应用的对象的状态,通过在程序中检验对状态,可以验证程序的正确性和完备性。但是堆方法也有缺陷,它定义的对象过多,容易造成空间爆炸。
2、规格为什么得到了人们的重视
规格实际上是程序员应该遵守的“契约”,对于代码的使用者和创作者都有帮助。对于编程人员,他们可以分工合作,根据统一的规格来完成一个程序。从局部来看,针对一个规格抽象的视线与针对其他规格抽象的实现无关,相互之间不会产生影响。当修改一个规格抽象的实现时,不需要对使用该抽象的其他任何规格抽象及其实现进行调整。这样既保证代码的相异性设计又保证了程序的可维护性。而对于程序的使用者,阅读规格,可以迅速了解程序的功能和作用,提高效率。
二、规格bug
规格bug数 | 规格bug类别 | 涉及的方法 | 功能bug数 | 涉及的方法 | |
第九次作业 | 1 | 不符合JSF规范 | Scheduler类的构造器 | 1 | parseLoad()没有判断字符串的长度 |
第十次作业 | 0 | 0 | |||
第十一次作业 | 0 | 3 |
Map类的run方法关于流量的定义,没有改为[当前时间-500,当前] Taxi类的randomDrive()方法选择下一个点时有错误 100台出租车为300个同一点发出的请求,请求派单时间有2分钟的延迟 |
规格bug只有在第九次作业的时候被发现了一个,是我应该写“==”的地方写了“=”,后来的作业我交之前都仔细检查了JSF的格式,所以之后几次没有什么规格上的bug了,但是在写规格的不足还是存在。
这几次作业都是在第七次作业的架构的基础上写出来的,没有什么较大的改动。第九次作业在实现Load filename的时候,提取第一条指令的前四个字符时,忽略了输入的指令可能不足四个字符,所以导致第一条指令什么也不输入就回车会有异常。第十次作业的时候,助教说流量的定义改为[当前时间-500,当前]的流量,但是我当时没有改,在第十次作业没有被发现,结果第十一次就被发现了(在违法的边缘试探.jpg)。另外我的调度器包揽了派单和抢单的工作,没有顶住压力测试,导致有的乘客等了有点久,这就很bad。
功能bug和规格bug涉及的方法之间并没有什么聚集关系,这可能是因为我的测试者没有特别细看我的JSF吧,其实我有规格bug的parseLoad()函数的规格是有问题的,是我在设计的时候没有想清楚导致我出现了功能bug。第十一次的bug主要都是设计上的问题,与规格和代码实现也没什么关系了。
三、规格改进
1、前置条件缺少对传入参数的限制
/** * @REQUIRES:none; * @MODIFIES: his.curLocation; * @EFFECTS: his.curLocation == loc; * @THREAD_REQUIRES:locked( his); * @THREAD_EFFECTS:locked(); */
修改后:
/** * @REQUIRES:0<=loc<6400; * @MODIFIES: his.curLocation; * @EFFECTS: his.curLocation == loc; * @THREAD_REQUIRES:locked( his); * @THREAD_EFFECTS:locked(); */
2、后置条件不符合JSF规范
/**@REQUIRES:RQ!=null&&t!=null&&p!=null&&(all int x;0<=x<100)==>t[x]!=null; * @MODIFIES: his.reqQueue, his.taxi, his.pr; * @EFFECTS: his.reqQueue = RQ; * his.taxi = t; * his.pr = p; * @THREAD_REQUIRES: * @THREAD_EFFECTS: */
修改后:
/**@REQUIRES:RQ!=null&&t!=null&&p!=null&&(all int x;0<=x<100)==>t[x]!=null; * @MODIFIES: his.reqQueue, his.taxi, his.pr; * @EFFECTS: his.reqQueue == RQ; * his.taxi == t; * his.pr == p; * @THREAD_REQUIRES: * @THREAD_EFFECTS: */
3、减少自然语言的使用,以及避免出现程序的具体实现
/** * @REQUIRES:none; * @MODIFIES:none; * @EFFECTS: (getLength == true) ==> esult == snap[5]; * 其中snap[0]== his.status;snap[1]== his.credit;snap[2]=map.spfa(curLocation,dst).size();snap[3]== his.curLocation; * (getLength == false) ==> esult == snap[5]; * 其中snap[0]== his.status;snap[1]== his.credit;snap[3]== his.curLocation; * @THREAD_REQUIRES:locked( his); * @THREAD_EFFECTS:locked(); */
修改后:
/**
* @REQUIRES:none; * @MODIFIES:none; * @EFFECTS: esult[0] == his.status; esult[1] == his.credit;
* esult[3] == his.curLocation;
* (getLength == true)==> esult[2] == Distance(curLocation,dst);
* (getLength == false) ==> esult[2]==0; * @THREAD_REQUIRES:locked( his); * @THREAD_EFFECTS:locked(); */
4、错误的表达式
/** * @REQUIRES: his.n < his.servicePath.size(); * @MODIFIES: his.n; * @EFFECTS: * normal_behavior: * esult == his.servicePath.get(n); * his.n == old( his.n)+1; * exceptional_behavior(Exception e): * esult == -1; */
修改后:
/** * @REQUIRES:old( his.n) < his.servicePath.size(); * @MODIFIES: his.n; * @EFFECTS: * normal_behavior: * esult == his.servicePath.get(n); * his.n == old( his.n)+1; * exceptional_behavior(Exception e): * esult == -1; */
5、逻辑错误
/** * @REQUIRES:s!=null; * @MODIFIES:none * @EFFECTS: * normal_behavior: * (s==null||s.length()<=4)==> esult==false; * (s!=null)==> esult == (s.substring(0, 4).equals("Load")); * exceptional_behavior(Exception e): * esult == false; */
修改后:
/** * @REQUIRES:s!=null; * @MODIFIES:none * @EFFECTS: * normal_behavior: * (s==null||s.length()<=4)==> esult==false; * (s!=null&&s.length()>4)==> esult == (s.substring(0, 4).equals("Load")); * exceptional_behavior(Exception e): * esult == false; */
四、总结
1)规格这个东西算是初次接触了,开始还很懵逼,不知道这玩意有啥用,也不知道咋写,后来看了PPT上的示例,结合自己的代码尝试写了之后就慢慢知道了。
2)现在我在写程序之前,会初步想好要怎么规划,写几个类,每个类实现什么功能,具体有什么方法,但是因为类的属性还没有完全固定下来,所以规格只能有一个初步的雏形,还不能完全写出来,在程序差不多写好的时候,规格也不会变了。虽然这种模式与老师说的先写规格再写代码的模式还有些差别但是我觉得还是挺有帮助的。
3)平心而论,这门课还是让我学到了很多,改变了我写程序时一些不好的习惯(最直观的的就是我的变量名不那么随便了盒盒盒),现在编程类的作业结束了吧,希望自己能好好消化,继续提高自己的能力吧盒盒盒。