2020面向对象第三单元

JML理论基础

表达式 含义
\result 返回值
\old() 原值
\not_assigned() 值为“括号中变量没有被赋值”的真值
\not_modified() 值为“括号中变量值没有发生变化”的真值
\nonnullelements() 括号中没有null
\type() 括号中对应的类型
\typeof() 括号中对应的准确类型
(\forall ; ; ) 全称量词∀
(\exists ; ; ) 存在量词∃
(\sum ; ; ) 求和
(\product ; ; ) 求积
(\max ; ; ) 求最大值
(\min ; ; ) 求最小值
(\num_of ; ; ) 满足后一个条件的取值的个数
requires 前置条件
ensures 后置条件
assignable 可被修改的值
signals 抛出异常

JML工具链

常用工具有形式化比较好的SMT Solver、可以自动生成一些测试用例的JMLUnitNG和用于进行“白盒测试”的JUnit。我在本单元主要用的是JUnit。

JMLUnitNG测试

2020面向对象第三单元_第1张图片
2020面向对象第三单元_第2张图片
JMLUnitNG自动生成的测试样例都是边界情况,例如测了int的最大最小值和null等作为参数的情况。我认为没有测全规格的描述,需要自己用JUnit再进行测试。因此我自己用JUnit进行了一些测试,但也没有完全覆盖,因为工作量太大,写不过来。

构架设计

第一次我只是写了MyPerson和MyNetwork两个类来分别实现Person和Network接口,没有任何特别的东西。容器实现都是用的ArrayList。
第二次由于数据量比较大,我把容器都改成HashMap和HashSet了,为了提高查找速度。设计了Relation类,一个无序偶对,来记录相关联的两个节点的id,并设计了相应的hashcode和equals方法来满足MyGroup这个类对它的调用的需要。除此以外,我也只是写了MyGroup类来实现Group接口。在Group新加入Person时,我会把Group内和这个Person有关联的每一个Person都生成一个Relation记录在这个Group里,且作为键,对应的值是这两个Person的value,这样在后面计数时不用遍历Group内的Person就能获得结果。
第三次构架和第二次完全相同。
总的来说,我认为自己的模型构建策略就是不断地在找能更快速查找的容器,同时平衡插入和查找(删除)所用的时间,以防其中某类指令达到数量上限时我的算反不够好。

bug

第一次作业我在写DFS的时候进行了一个无用操作:在某一个点不可达目的地的情况下,回溯时我把这个点也由“搜索过”标记位“未搜索过”,当时想当然认为新的路径可能会通过这个点。然而,这个点不可达目的地说明没有连接出发点和目的地的路径是经过这一个点的,因此没必要在回溯时标记为“未搜索过”。这一操作增大了复杂度,导致很多测试点TLE。
第二次作业我对于复杂度控制得还好,但是我在Group内记录Relation的设计坑了自己。虽然我记得在Group的addPerson方法里面将现有的连接关系作为Relation添加到Group里,但是我忘了Network有addRelation方法,这个方法可能使已经在同一Group内的两个Person建立新的Relation,我却没有在这个位置补加新的Relation对象到Group里,导致错了一个强测点。我还没有准确实现关于Age的两个规格,未特判0人的情况,导致两个除0异常,错了很多点。
第三次作业中queryBlockSum方法我没有用比较好的算法,导致超时。

心得体会

我认为规格很大程度上解决了程序的正确性验证的问题,使得测试简单了很多。然而,规格的撰写有很大的发挥空间,即等价的描述非常多。我认为写规格很重要的一部分是突出什么变了什么没变,把这些写的清楚,相关的变量(例如同一变量的新和旧)的约束挨着写,尽量不要隔很多行,就会使规格更容易看懂。
规格写完后就有在某称程度上引导实现的作用,但是这是实现着应该避免的,因为性能上的要求。要把规格转化成自己的理解,知道要修改什么和/或求什么,再想合适的算法来完成。最清晰的规格往往引导人写出最简单却低效的算法,这是实现者要意识到的。

你可能感兴趣的:(2020面向对象第三单元)