面向对象第三章总结
面向对象第三章总结
准备测试数据
本单元引入了Junit单元测试,可以针对每个方法自己构造数据测试。但是由于其使用起来需要手动针对每个方法写判断前提和结果约束,相当麻烦,因而关于Junit我便浅尝辄止了。类似之前单元的测试,本单元我还是通过随机生成数据进行测试。
方法的JML规格约束了方法的前提与产生的结果,因此针对方法的测试可以以JML位基础,约束生成数据的边界条件,保证数据合法且能覆盖所有范围。针对数据的JML约束在本单元JML规格中似乎没有体现,相关的约束条件都在指导书中给出。类满足的不变式在满足各个方法约束的前提上可以满足,因而也不需要刻意测试。因此,自己构造数据的主要目的是针对方法进行测试。
我在编写生成数据的代码时,考虑到需要针对方法进行测试,因而为每条指令赋予了一定的比例:均衡的比例可以测试各条指令均有分布的情况;刻意调大某些指令的比例可以一定程度上对其针对性测试。但是仅仅靠随机测试远远不够,因为本单元在图上运行,很多情况下需要构造特定形状的图针对特定的指令进行特定的测试,因此自己构造数据必不可少。比如第一次作业针对连通块的数据需要自己构造最坏情况,即每人属于一个独立的连通块,然后反复查找遍历,如果有同学设计实现不好便会超时。再比如第二次第三次针对生成树和最短路的数据更需要额外构造,但是由于这两类指令数受到限制,因而正常情况下都不会因此超时。
架构设计
本单元由于主要训练对JML的理解,对层次设计的考察相对弱。因此我在本单元的实现层次结构完全按照官方包给出的接口实现,具体实现也是根据官方JML完成。
值得一提的式几个算法,并查集、最小生成树和最短路,分别应用于三次作业。关于并查集的实现我加入了路径压缩,因为官方体贴地限制了加入人的指令条数,不会被卡爆栈。最小生成树和最短路我分别使用了Prim和Dijkstra的堆优化版本,两者实现起来也只有几行的区别,因而我在一个方法中实现,通过传入类型参数区分两者。
其次是容器的选择,由于需要通过id找到下层对象,因而我尽量选择HashMap管理,这样可以在需要寻找id对应的对象时以很低的时间复杂度完成。很多同学按照JML约束给的类似遍历方法用ArrayList管理对象,寻找时遍历容器,这样增大了超时被hack的风险。
Network扩展
/*@ public normal_behavior
@ requires product != null;
@ assignable customers;
@ ensures (\forall int i; 0 <= i && i < customers.length; customers[i].contains(product));
@ ensures (\forall int i; 0 <= i && i < customers.length;
\old(customers[i].contains(product)) ==>
customers[i].product.length == \old(customers[i].product.length));
@ ensures (\forall int i; 0 <= i && i < customers.length;
!\old(customers[i].contains(product)) ==>
customers[i].product.length == \old(customers[i].product.length) + 1);
@ ensures (\forall int i; 0 <= i && i < customers.length;
(\forall int j; 0 <= j && j < \old(customers[i].products.length);
(\exists int z; 0 <= z && z < customers[i].products.length; customers[i].products[z] == \old(customers[i].products[j]))));
public void advertiseForProducer(Product product);
/*@ public normal_behavior
@ requires product != null;
@ assignable producers;
@ ensures (\forall int i; 0 <= i && i < producers.length;
products[i] == product ==> producers[i].order[\old(producer[i].order.length)] == product);
@ ensures (\forall int i; 0 <= i && i < producers.length;
(\forall int j; 0 <= j && j < \old(producers[i].order.length);
producers[i].order[j] == \old(producers[i].order[j])));
public void buyForCustomer(Product product);
/*@ public normal_behavior
@ requires product != null;
@ assignable orders;
@ ensures order[order.length] == product;
@ ensures (\forall int i; 0 <= i && i < \old(order.length);
order[i] == \old(order[i]));
public void newOrder(Product product);
学习体会
规格在软件开发中具有不可忽视的地位。使用规格,不仅可以方便描述对类、方法和数据的约束,而且可以消除自然语言表达的歧义,便于不同开发人员之间的合作。JML是规格的一种具体实现,在本单元学习中可以明显感受到JML的语法较为简单,且能实现的语义丰富,理解语法后读起来也很顺畅。
关于具体作业方面的体会就是不能完全按照规格描述的去实现,而是要关注性能。规格只是一种约束,只要实现的效果满足约束即可。