又到了单元小结的时间了,开心!
最后这几次作业是出租车系列作业,每次作业在前次作业的基础上增加一些功能,并在这个过程中体会规格化编程。总体觉得,这个过程还是比较痛苦的,在作业和沟通中度过。。。
不过,能力的提升倒是真的有。刚开始接触这门课的时候,拿到指导书直接开始编程,不会去思考代码的内在逻辑是如何的,对自己的要求也很低,只需要实现功能,尽可能减少作业中的BUG。这样的后果是代码逻辑混乱,可读性很差。经历了规格化的作业风格之后,作业的完成度提高了。
调研
软件的规格化是提高软件可靠性的重要方式之一,当软件的规模随着软件行业的成熟与发展,软件系统的复杂性使得错误出现几率大幅度提升,这个问题的解决也日益困难。许多研究者在这个领域提出了各种规格化方法。规格化方法是基于数学的技术,这些技术通常由推理工具支持,提供了一个严格的、有效的方式去建模、设计和分析计算机系统。这些方法解决了一些问题,但是也是有缺陷的。
抽象变量
数据抽象
主要思想是使用抽象的数据类型来降低和减少设计和实现大型软件的复杂度,用代数公理证明规格的正确性,这是可以得到证明的。代数公理的有点是允许数据类型表现为独立的规格,代数公理的使用可以简化抽闲数据类型实现的正确性的过程。
契约式编程
这种方法就是我们所采用的的规格化方式。通过在编程时给组件添加前置、后置条件和不变式来提高代码的可依赖度、健壮性和可重用性等。前置条件是任何调用该模块必须满足的要求,后置条件是在运行该模块后必定会拥有的特性,不变式是在运行中不会改变的量。
除了抽象变量,还有抽象状态、堆方法以及查询方法等,有兴趣的同学可以自行google。
前三次作业BUG汇总与分析
BUG类型 错误分支树 第九次作业公测与互测 ERROR 第九次作业公测->出租车状态测试->服务状态抢单 ERROR 第九次作业公测->出租车状态测试->接单状态抢单 第十次作业公测与互测 ERROR 第十次作业公测->程序测试->针对历史版本的回归测试->地图读入时没有忽略空格和制表符 ERROR 第十次作业公测->程序测试->针对历史版本的回归测试->是否选择流量最少路径 ERROR 第十次作业公测->程序测试->针对历史版本的回归测试->运行时关闭道路出租车是否停下 ERROR 第十次作业公测->程序测试->针对历史版本的回归测试->打开了不存在的道路 incomplete repOK incomplete modifies不完整 第十一次作业 无
在第九次作业中,在分派乘客请求的时候,忘了对出租车的状态加以判断,以至于服务状态和接单状态的出租车参与抢单。之后便修复了这个BUG
第十次作业中,没想到被测试者找出这么多BUG,并且很大一部分是针对历史版本的回归测试,这也说明了程序的确是存在问题的。不过,查看过BUG分支后,可以发现,有一些是真的很愚蠢的。首先关于地图的问题,因为地图读入一直使用的官方的版本,自以为不会有什么问题,结果被测试者指出指导书中要求地图读入支持空格与制表符,但是官方版本是没有考虑的,导致被挂了一个BUG。接着便是关于地图中不存在的道路的开关问题,因为在第九次作业中,最终讨论的结果是要求测试者保证这一点,结果到了第十次作业,竟然变成了不需要测试者保证,由开发者来保证,这是我感觉被坑的一次,打的我猝不及防,不过,我也挂了对方的这个BUG,因为我已经预感到自己会被挂。我比较在意的是另外一个BUG,运行时关闭道路出租车是否停下。真的感觉测试者实力很强,测试者构造了这样一种情况,在出租车等待红绿灯的过程中关闭道路,结果出租车无视道路的临时关闭而直接穿了过去。这个错误时与我的出租车调度有关的,我是先寻找流量最小的边,然后判断是否需要等待红绿灯,而在之后就不再判断道路是否联通,从而导致了这样潜在的BUG。关于两个规格BUG,首先是repOK,并没有完全满足初始状态为真的情况,有些属性没有被初始化;关于modifies不完整,则是没有将所有的情况考虑清楚,因为我是先写代码,然后根据代码逻辑来组织JSF的,所以在某些情况下会漏掉某些条件。
第十一次作业中,我修复了之前出现的所有BUG,(当然,潜在的BUG不清楚),第十一次作业还是挺满意的,毕竟没有被挂BUG。
规格优化
前置条件不规范
1、改进之前
/**@REQUIRES: time >= 0 && (\all int i; 0<=i<Main.rl.size; Main.rl.get(i) instanceof Request); * @MODIFIES: Main.rl * @EFFECTS: * (\exist Request r; \old(Main.rl)==null ==> (Main.rl.size == \old(Main.rl).size + 1 && (Main.rl.contains(r)))); * (\exist Request r; \all i; 0 <= i < \old(Main.rl).size; \old(Main.rl).get(i) != r) ==> (Main.rl.size == \old(Main.rl).size + 1 && Main.rl.contains(r)); * @THREAD_EFFECTS: \locked(Main.rl); */ public boolean check(String str,double time) { Pattern pr=Pattern.compile(cr); Matcher ma=pr.matcher(str); if(ma.find() && ma.group(0).compareTo(str)==0) { // System.out. int srcx,srcy,dstx,dsty; srcx=Integer.parseInt(ma.group(1)); srcy=Integer.parseInt(ma.group(2)); dstx=Integer.parseInt(ma.group(3)); dsty=Integer.parseInt(ma.group(4)); if(srcx>=0 && srcx<80 && srcy>=0 && srcy<80 && dstx>=0 && dstx<80 && dsty>=0 && dsty<80 && !(srcx==dstx && srcy==dsty)) { Point src=new Point(srcx,srcy); Point dst=new Point(dstx,dsty); synchronized(Main.rl) { Main.rl.add(new Request(time,src,dst)); } // System.out.println("SUCCESS"); return true; } } System.out.println("ERROR\n"+str); return false; }
改进之后
/**@REQUIRES: time >= 0; str!=null; * @MODIFIES: Main.rl * @EFFECTS: * (\exist Request r; \old(Main.rl)==null ==> (Main.rl.size == \old(Main.rl).size + 1 && (Main.rl.contains(r)))); * (\exist Request r; \all i; 0 <= i < \old(Main.rl).size; \old(Main.rl).get(i) != r) ==> (Main.rl.size == \old(Main.rl).size + 1 && Main.rl.contains(r)); * @THREAD_EFFECTS: \locked(Main.rl); */ public boolean check(String str,double time) { Pattern pr=Pattern.compile(cr); Matcher ma=pr.matcher(str); if(ma.find() && ma.group(0).compareTo(str)==0) { // System.out. int srcx,srcy,dstx,dsty; srcx=Integer.parseInt(ma.group(1)); srcy=Integer.parseInt(ma.group(2)); dstx=Integer.parseInt(ma.group(3)); dsty=Integer.parseInt(ma.group(4)); if(srcx>=0 && srcx<80 && srcy>=0 && srcy<80 && dstx>=0 && dstx<80 && dsty>=0 && dsty<80 && !(srcx==dstx && srcy==dsty)) { Point src=new Point(srcx,srcy); Point dst=new Point(dstx,dsty); synchronized(Main.rl) { Main.rl.add(new Request(time,src,dst)); } // System.out.println("SUCCESS"); return true; } } System.out.println("ERROR\n"+str); return false; }
2、改进之前
/**@REQUIRES: None; * @MODIFIES: RequestTaxi.txt; * @EFFECTS: write content to the file RequestTaxi.txt */ public static synchronized void write(String content) { try { File file=new File("RequestTaxi.txt"); FileWriter writer=new FileWriter(file,true); writer.write(content); writer.close(); }catch(Exception e) {} }
改进之后
/**@REQUIRES: content!=null; * @MODIFIES: RequestTaxi.txt; * @EFFECTS: write content to the file RequestTaxi.txt */ public static synchronized void write(String content) { try { File file=new File("RequestTaxi.txt"); FileWriter writer=new FileWriter(file,true); writer.write(content); writer.close(); }catch(Exception e) {} }
3、改进之前
/**@REQUIRES: None * @MODIFIES: guigv.flowmap;System.out; * @EFFECTS: * (\exist Point src, dst; \exist int flow; guigv.flowmap.get(Key(src.x,src.y,dst.x,dst.y) == flow); */ public void readflow(String str) { String re_flow="\\((\\d?\\d),(\\d?\\d)\\)\\((\\d?\\d),(\\d?\\d)\\)(\\d+)"; Pattern p = Pattern.compile(re_flow); Matcher m = p.matcher(str); { if(m.find() && m.group(0).compareTo(str)==0) { Point src = new Point(Integer.parseInt(m.group(1)),Integer.parseInt(m.group(2))); Point dst = new Point(Integer.parseInt(m.group(3)),Integer.parseInt(m.group(4))); int f = Integer.parseInt(m.group(5)); if(!(src.x >= 0 && src.x < 80 && src.y >= 0 && src.y < 80 && dst.x>=0 && dst.x < 80 && dst.y >= 0 && dst.y <80)) { System.out.println("ERROR\n"+str); return; } // Main.flow.flow Main.flow.record(src, dst, Double.parseDouble(String.format("%.1f", gv.getTime()/1000.0)),f); // guigv.SetFlow(src.x, src.y, dst.x, dst.y, f); } else { System.out.println("ERROR\n"+str); } } }
改进之后
/**@REQUIRES: str!=null; * @MODIFIES: guigv.flowmap;System.out; * @EFFECTS: * (\exist Point src, dst; \exist int flow; guigv.flowmap.get(Key(src.x,src.y,dst.x,dst.y) == flow); */ public void readflow(String str) { String re_flow="\\((\\d?\\d),(\\d?\\d)\\)\\((\\d?\\d),(\\d?\\d)\\)(\\d+)"; Pattern p = Pattern.compile(re_flow); Matcher m = p.matcher(str); { if(m.find() && m.group(0).compareTo(str)==0) { Point src = new Point(Integer.parseInt(m.group(1)),Integer.parseInt(m.group(2))); Point dst = new Point(Integer.parseInt(m.group(3)),Integer.parseInt(m.group(4))); int f = Integer.parseInt(m.group(5)); if(!(src.x >= 0 && src.x < 80 && src.y >= 0 && src.y < 80 && dst.x>=0 && dst.x < 80 && dst.y >= 0 && dst.y <80)) { System.out.println("ERROR\n"+str); return; } // Main.flow.flow Main.flow.record(src, dst, Double.parseDouble(String.format("%.1f", gv.getTime()/1000.0)),f); // guigv.SetFlow(src.x, src.y, dst.x, dst.y, f); } else { System.out.println("ERROR\n"+str); } } }
4、改进之前
/**@REQUIRES: t>=0; 0<=s.x<80; 0<=s.y<80; 0<=d.x<80; 0<=d.y<80; * @MODIFIES: this.time; this.src; this.dst; this.start; this.end; this.str; * @EFFECTS: * init the Request's properties; */ public Request(double t, Point s, Point d) { time=t; src=s; dst=d; start.x=s.x-2; start.y=s.y-2; end.x=s.x+2; end.y=s.y+2; if(start.x<0) start.x=0; if(start.y<0) start.y=0; if(end.x>=80) end.x=79; if(end.y>=80) end.y=79; str=String.format("乘客请求\r\n[%.1f,(%d,%d),(%d,%d)]\r\n", this.time,src.x,src.y,dst.x,dst.y); }
改进之后
/**@REQUIRES: t>=0; 0<=s.x<80; 0<=s.y<80; 0<=d.x<80; 0<=d.y<80; s.equals(d) == false; * @MODIFIES: this.time; this.src; this.dst; this.start; this.end; this.str; * @EFFECTS: * init the Request's properties; */ public Request(double t, Point s, Point d) { time=t; src=s; dst=d; start.x=s.x-2; start.y=s.y-2; end.x=s.x+2; end.y=s.y+2; if(start.x<0) start.x=0; if(start.y<0) start.y=0; if(end.x>=80) end.x=79; if(end.y>=80) end.y=79; str=String.format("乘客请求\r\n[%.1f,(%d,%d),(%d,%d)]\r\n", this.time,src.x,src.y,dst.x,dst.y); }
5、改进之前
/**@REQUIRES: None * @MODIFIES: this.queue; System.out; * @EFFECTS: * if there is a same request in this.queue, then output the r and return; * else, add r to this.queue; */ public synchronized void add(Request r) { for(Request tmp:queue) { if(tmp.src.x==r.src.x && tmp.src.y==r.src.y && tmp.dst.x==r.dst.x && tmp.dst.y==r.dst.y && r.time-tmp.time <= 0.1) { System.out.println("同质请求\n"+r.toString()); return; } } queue.add(r); }
改进之后
/**@REQUIRES: r!=null; * @MODIFIES: this.queue; System.out; * @EFFECTS: * if there is a same request in this.queue, then output the r and return; * else, add r to this.queue; */ public synchronized void add(Request r) { for(Request tmp:queue) { if(tmp.src.x==r.src.x && tmp.src.y==r.src.y && tmp.dst.x==r.dst.x && tmp.dst.y==r.dst.y && r.time-tmp.time <= 0.1) { System.out.println("同质请求\n"+r.toString()); return; } } queue.add(r); }
后置条件不规范
1、改进之前
1 /**@REQUIRES: None 2 * @MODIFIES: None 3 * @EFFECTS: 4 * 以相同概率返回下一条道路 5 */ 6 public int nextstep() 7 {
/***/ 8 }
改进之后
/**@REQUIRES: None * @MODIFIES: None * @EFFECTS: * 选择流量最小的边作为行走的路线,如果有多条流量相同的边,则以相同概率返回其中一条边 */ public int nextstep() { /***/ }
2、改进之前
/**@REQUIRES: None * @MODIFIES: flow[] * @EFFECTS: * 初始化属性 */ public Flow() { // flow = new edge[6405]; for(int i=0;i<6400;i++) flow[i]=new edge(); }
改进之后
/**@REQUIRES: None * @MODIFIES: flow[] * @EFFECTS: * (\all i; 0<=i<flow.size; flow[i] instanceof edge); */ public Flow() { // flow = new edge[6405]; for(int i=0;i<6400;i++) flow[i]=new edge(); }
3、改进之前
/**@REQUIRES: None * @MPDIFIES: right;down;rightflow;downflow; * @EFFECTS: * 实例化right、down、rightflow */ public edge() { right = new ArrayList<double[]>(); down = new ArrayList<double[]>(); rightflow=downflow=0; }
改进之后
/**@REQUIRES: None * @MPDIFIES: right;down;rightflow;downflow; * @EFFECTS: * (right instanceof double[] == true); * (down instanceof double[] == true); * (rightflow==0 && downflow=0); */ public edge() { right = new ArrayList<double[]>(); down = new ArrayList<double[]>(); rightflow=downflow=0; }
4、改进之前
/**@REQUIRES: t>=0 && f>=0; * @MODIFIES: right;rightflow; * @EFFECTS: * 将 t 加入this.right中并从小到大排序 *@THREAD_EFFECTS: \locked(this.right); */ public void addright(double t, int f) { synchronized(this.right) { this.rightflow+=f; double []tf= {t,this.rightflow}; right.add(tf); if(right.size()==1) return; int i; double [] tmp; for(i=right.size()-2;i>=0;i--) { tmp=right.get(i); if(tmp[0]>tf[0]) right.set(i+1, tmp); else break; } if(i==-1) right.set(i+1, tf); } }
改进之后
/**@REQUIRES: t>=0 && f>=0; * @MODIFIES: right;rightflow; * @EFFECTS: * (rightflow == \old(rightflow)+f); * (\exist double[] tf={rightflow,f};\all double t in \old(right))==>(\old(right) isIn(tf)==false && right isIn(t) && right isIn(tf)); * (\all i,j; 0<=i<j<right.size; right[i][0]<=right[j][0]); *@THREAD_EFFECTS: \locked(this.right); */ public void addright(double t, int f) { synchronized(this.right) { this.rightflow+=f; double []tf= {t,this.rightflow}; right.add(tf); if(right.size()==1) return; int i; double [] tmp; for(i=right.size()-2;i>=0;i--) { tmp=right.get(i); if(tmp[0]>tf[0]) right.set(i+1, tmp); else break; } if(i==-1) right.set(i+1, tf); } }
5、改进之前
/**@REQUIRES: t>=0 && f>=0; * @MODIFIES: down;downflow; * @EFFECTS: * 将 t 加入 down 中,并按照升序进行排序 *@THREAD_EFFECTS: \locked(this.down); */ public void adddown(double t, int f) { synchronized(this.down) { this.downflow+=f; double []tf= {t,this.downflow}; down.add(tf); if(down.size()==1) return; int i; double []tmp; for(i=down.size()-2;i>=0;i--) { tmp=down.get(i); if(tmp[0]>tf[0]) down.set(i+1, tmp); else break; } if(i==-1) down.set(i+1, tf); } }
改进之后
/**@REQUIRES: t>=0 && f>=0; * @MODIFIES: down;downflow; * @EFFECTS: * (downflow == \old(downflow)+f); * (\exist double[] tf={downflow,f};\all double t in \old(down))==>(\old(down) isIn(tf)==false && down isIn(t) && down isIn(tf)); * (\all i,j; 0<=i<j<down.size; down[i][0]<=down[j][0]); *@THREAD_EFFECTS: \locked(this.down); */ public void adddown(double t, int f) { synchronized(this.down) { this.downflow+=f; double []tf= {t,this.downflow}; down.add(tf); if(down.size()==1) return; int i; double []tmp; for(i=down.size()-2;i>=0;i--) { tmp=down.get(i); if(tmp[0]>tf[0]) down.set(i+1, tmp); else break; } if(i==-1) down.set(i+1, tf); } }
聚类分析
方法名 | 规格BUG数 | 功能BUG数 | |
第九次作业 | run | 0 | 2 |
第十次作业 | readmap | 0 | 1 |
nextstep | 1 | 2 | |
openroad | 0 | 1 | |
Light | 1 | 0 |
心得体会
因为这几次作业在实现基本功能的前提下,增加了JSF规格书写,工作量还是会增加的,并且在写规格的时候,基本是先实现功能,接着按照程序代码给出规格化说明。。。感觉这并不是真正的规格化。不过,即使是这样,对规格化能力还是有一些提升的,对整个代码的质量提升还是有帮助的,毕竟,在编写JSF和码代码的过程中,这两个可以说是相互辅助吧,因为对于一个功能很完备、代码量超大的方法而言,规格的撰写还是比较困难且庞大的,因此,这会帮助开发者思考使得类之间的功能更加均衡,类之间的逻辑也会更加清晰。