一、规格的历史与发展
说起规格,我们需要先了解一下基本的程序设计方法的演变进程,最原始的程序设计是没有任何设计方法可言的,随着程序功能逐渐的复杂,催生出了结构化程序设计的方法,这种方法在当时解决一些相对复杂的问题还是起到了很有效的作用。
但是随着计算机技术的发展,结构化设计语言和结构化分析无法满足用户的需求,OOP由此应运而生,即面向对象的程序设计。OOP的诞生是程序设计方法学的一场革命,大大提高了开发效率,减少了软件开发的复杂性,提高了软件的可维护性,可拓展性。1990年以来,面向对象分析、测试、度量和管理研究都得到长足发展。
规格化设计伴随OOP而生,是OOP出现之后的一大重要产物,为了提高程序的规范性,对类、方法等进行规范化设计,有利于程序的模块化划分。这样设计程序的数据更加安全可控,测试也变得容易,软件的维护性得到提高,因而受到程序设计人员的重视。
"分而治之"是处理大型问题的一般性原则,面向对象也是如此,面向对象的设计通过模块组织程序,将一个庞大的程序切割成很多较小的模块,通过接口来互相联系。但是如何进行分解成了主要难题。分解大程序一大利器是抽象,这也就引出了规格化抽象。规格化抽象是将执行细节(即模块如何实现)抽象为用户所需求的行为(即模块做什么)。这是从具体实现中抽象出模块,需要的仅仅是模块的实现能符合我们所依赖的表述形式。通过规格化抽象,可以把一个软件分成多个组件,使这些组件可以最终组合起来解决最初的问题。这些组件的开发由于有规格化的描述,开发较为容易,并且后期如果需要更新维护只需要对相应的组件进行更改即可,极大的简化了修改和维护的难度,对大型工程的开发有极大的帮助。因而受到程序员的热捧。
相比于代码本身,规格的确有更强的规范性和更好的概括性,能够在多人合作的工程中让大家保持应有的默契,所以规格和设计现在在软件开发领域得到了越来越多人的支持和重视。
二、bug分析以及列表
第九次作业的bug片段:
1 synchronized static boolean has_rq(int node){//判断某个节点是否有请求可以抢单 2 /** 3 * @REQUIRES:None; 4 * @MODIFIES:None; 5 * @EFFECTS: 6 * \result==!(rq_map[node].isEmpty()); 7 * @THREAD_REQUIRES: 8 * \locked(\this); 9 * @THREAD_EFFECTS: 10 * \locked(); 11 */ 12 if(rq_map[node].isEmpty()) return false; 13 return true; 14 }
这个对面同学说是对@THREAD_REQUIRES理解不正确,这个要求的是外面调用这个函数的方法需要加锁的限制,但是这个方法自己已经同步,也不要求外面方法调用的时候上锁,所以书写是不合适的,后来自己仔细读了文档发现的确是自己理解出现了错误。
第11次代码片段
a.repOK片段:
1 /** 2 * @REQUIRES:None; 3 * @MODIFIES:None; 4 * @EFFECTS: 5 * \result== (taxi_sys!=null)&& 6 * (rq_queue!=null)&& 7 * (end_input==true||end_input==false); 8 */ 9 public boolean repOK(){ 10 return (taxi_sys!=null)&& 11 (rq_queue!=null)&& 12 (end_input==true||end_input==false); 13 }
被报的原因是对数据抽象还不够rq_queue应该有更多限制,我觉得这个事情repOK究竟写到什么程度还真的没法一概而论
b.jsf缺少require片段
1 /** 2 * @REQUIRES:None; 3 * @MODIFIES:LIGHT; 4 * @EFFECTS: 5 * LIGHT==(LIGHT==SN_GREEN)? EW_GREEN:SN_GREEN; 6 * 设置gui 7 */ 8 private synchronized void change_light(){ 9 //改变所有的灯的方向 10 int lightStatus = (LIGHT==SN_GREEN)? EW_GREEN:SN_GREEN; 11 LIGHT=lightStatus; 12 for(int i=0;i<NODE_MAX;i++) { 13 if (has_light[i]) Main.gui.SetLightStatus(new Point(i / 80, i % 80), LIGHT); 14 } 15 }
这个是因为个人疏忽,写规格的时候没有意识到要上锁,后来debug的时候上了锁,忘记修改对应规格了。
三、前置后置条件的进一步分析
5个还可以改进的requires:
方法名 | 原写法 | 改进写法 |
Map : change_road | 0<=x1,x2,y1,y2<80 |
0<=x1<80&&0<=y1<80&& 0<=x2<80&&0<=y2<80 |
TestInquiry:TestInquiry | None |
(s1=="GET-ID"&&s2可以转化一个0-99整数)|| (s1=="GET-STATE"&&s2属于集合{STOP,WFS,GET_ORDER,IN_SERVE} &&time>=0 |
TestInquiry:get_id | None | s可以转成0-99的整数 |
Map:get_matrix | None | 0<=node1<6400,0<=node2<6400 |
Taxi: setTaxi_time | None | taxi_time>0 |
5个还可以改进的effects:
方法名 | 原写法 | 改进写法 |
VIPTaxi:VIPTaxi | 初始化各种属性 | serve_list==new LinkedList<History>(); toPassengerRoute==new Vector<Integer>(); toObjRoute==new Vector<Integer>(); |
Map: run |
保持线程运行并更新流量 |
\all int i,j;0<=i,j<80;flow[i][j] ==\old(flow[i][j])-original_flow[i][j]; |
TaxiThread:to_obj |
vip==>根据vip出租车规则选择从当前点到(obj_x,obj_y) 最短流量和最小的路走(遇到红绿灯需要等待红灯), 过程中及时设置gui;
的最合适路径行走,并设置gui
|
taxi.get_locationx()==obj_x && taxi.get_locationy()==obj_y |
Request:close_file | 关闭文件 | logger.STATE==close |
InquireTaxi:get_id | 写入文件查询指定编号出租车的各种信息 | OutputTestInfo.contains("查询时刻:"+Time.millis_time()+"****"+taxi_sys[id].toString()) |
四、规格bug与程序功能bug分析
就这几次作业从我个人角度看来,功能bug和规格bug没有什么太大关联,大部分bug的原因很多是我没能及时了解到另外其他客服群的需求。
五、规格训练总结
1.撰写过程规格的方法和思路
这单元最重要的训练是过程规格的训练,下面就我自己在这几次的训练中的感想和思路简单做一下总结。
REQUIRES是前置条件,是调用者传入参数等方面所必须满足的约定。在这个地方我第一次书写JSF的时候有误区,错误理解了文档上所说的如果保证传入的参数一定是合法的,就无需书写REQUIRE。我最开始站在了用户的层面理解这个传入参数,认为我的代码中的方法是只有我的代码在调用,因而自己会保证传入参数的合法性,但是后来跟几个同学一交流发现自己理解错误。JSF更多是给程序开发人员看的,他们可能需要使用你现有代码中的某个方法(不一定整个系统代码全部使用),那么就必须把每个方法独立抽出来,让未来可能使用你代码的人清楚的了解到什么时候能够使用你的代码,代码产生的效果是什么。一般来说REQUIRES重点需要考虑封装数据本身属性以及数据本身的实际含义。前者比如需要传对象或容器,一般都会要求对象不为空,容器有元素。后者是比较复杂也是更需要仔细考虑的地方比如输入格式跟系统输入规范之间关系,数据范围的要求,这些都是需要自己思考认真分析才能写出来合格的REQUIRES。
MODIFIES写方法会改变的数据。这里只需要列举出什么对象呗修改了即可,而不需要论述修改作用。一般最常见的就是对this对象的修改,但是往往有得时候有人会喜欢使用类的static变量,这时候如果对其他类的static变量不小心进行修改,一定记得写入。
EFFECTS即后置条件,是方法执行完毕返回结果、类属性等数据所满足的约定。这个是整个JSF中最难书写的部分。写EFFECTS一定要抓住老师上课所讲的这是一个抽象层次(说明了输入以及输出的关系,但是不关注其中的具体细节),切忌将算法细节卸载里面。经过几次的训练,我也慢慢体会到了EFFECTS的书写难度和方法的拆分细致程度成反比的(方法拆分的越小越细,对应的EFFECTS书写起来就越容易)。所以也启示我们在书写程序的时候尽量做到高内聚低耦合,让方法规模尽可能的小一点,这样不仅在写代码之前容易写出规格,更是方便后续的程序功能拓展以及相应的debug步骤。
2.repok书写思路以及感想
除了过程规格训练之外,repOK不变式书写也是这个单元的一大训练目标之一。其实我认为大部分类的repOK都不是很好写,因为在实际场景下(包括出租车最后作业)大部分时候的数据约束是很强的,限制条件很多,这也要求我们在书写的时候仔细、全面地思考数据的约束。
3.规格训练后的感想
相信大部分人在第一次听到老师所说JSF、规格的时候肯定是无法接受的,大部分都会对逻辑谓词的表达能力感到担忧,事实上第一次写JSF的时候的确是很混乱的,苦苦地思考如何将想要表达的自然语言逻辑转换成谓词逻辑表达。大部分同学(包括我自己在内)第一次写规格的时候并没有按照老师要求的顺序来,都是先写代码再补规格,现在反思来看,这样做基本上已经让规格的作用减到了0。我个人理解规格的书写其实是设计的一部分,在面向对象这门课中老师不停地在强调要重视设计,要设计与实现相分离,而编码后补规格的做法无疑是又回到了边设计边编码的方法上。
给我印象最深的是第12周的OO上机,看到老师要求根据规格完成代码的时候我当时还在怀疑能不能跟写代码的人想法一样完成他的函数。但是看到十分规范的规格之后,瞬间感觉轻松了许多,很容易就按照规格描述写出了目标代码。自己也逐渐意识到了规格对于程序设计的益处还是很大的。使用谓词逻辑来表达相应的要求和效果虽然写的时候比较困难但是更容易发挥规格的作用,以后在书写规格的时候一定会尽可能避免使用自然语言。