2020_OO_第三单元总结

目录

  1. 理论基础和工具链梳理
  2. SMT solver
  3. JMLUnitNG
  4. 架构设计
  5. bug分析
  6. 规格撰写、理解

一、理论基础和工具链梳理

  jml作为一种规格,体现了一种契约式的思想。从类使用者和类提供者的角度看,jml能够构建两者之间的信任关系:使用者能够清晰地知道被使用者的功能,而提供者声明了自己能够正常工作的条件,也不必担心使用者一些奇奇怪怪的行为,只有需要对合法的访问负责;也就是说,jml明确了类提供者,使用者的权利义务

  规格可以分为方法(过程)规格,数据规格,并且有不变式等其它约束,由此组成类规格。具体的语法课程组下发的Level0手册足够用了。

  jml是一种形式化建模语言,但其在大型工程中很难使用,因为书写的难度(见六)和自动化验证的不便利。现有的工具链,有openJML验证jml的规范,其中的SMT solver也可以对代码规格等价性进行检查,但是暂时没跑成功;有JMLUnitNG根据规格自动生成测试样例,但是测试基本只是针对边界,并没有做到很好的覆盖。

二、SMT slover

  折腾了好久,参考了许多博客还是失败了,遂放弃。

三、JMLunitNG

  2020_OO_第三单元总结_第1张图片

  先上结果。因为有一些方法跑出来会出一些莫名其妙的错误,加之有些方法需要network维护,所以删除了部分jml。经过观察,自动生成的样例似乎喜欢测试边界,比如int范围的极值,比如给需要传入对象的方法传null,感觉没有太大价值,因而没有深入研究。

四、架构分析

  笔者不幸是MyGroup,MyNetWork,MyPerson完事型选手。但是,这并不意味着只要机械地翻译jml就可以了,否则必然会造成CTLE等后果。

  第一次作业中,笔者在openJML等工具部署上花费了大量的时间(最后也没跑起来...),损耗了耐心,加之对jml具体考察形式的误解,在纵览全局之前就开始按照每个方法的jml撰写自己的实现。因为这次作业比较简单,没有特别的设计也没有翻车。

  第二次作业加入了子图的概念。在子图的边数计算、权值之和计算必须采用缓存的方法,其他如年龄均值、方差,character等都可以使用缓存的方式解决。这就是单纯看jml不能得知的,还得综合测试数据规模具体分析。方差的计算通过每次加人时累加年龄平方和实现,而边权值之和的缓存则比较有趣,其值分两种情况改变:一种是group中加入新的人,那么遍历一遍group,把权值加上去;第二种比较容易忽略的,就是连上新的边时候,要通过network遍历每一个group,维护其中的边权值。

  第三次作业主要是在第二次的基础上加强了对一些图算法的考察,至于删人等操作可以仿照之前的addtoGroup实现,不再赘述。为了实现高效率的最短路径查询,使用了堆优化的迪杰斯特拉算法(事后证明这是唯一解)。堆使用了java提供的优先队列,为了方便计算,构造了专门的节点类。强连通查询笔者使用了tarjan算法。后来从研讨课中学习了内部类的构造和使用。包括查询点双联通分量的tarjan算法,或者最短路径计算的Dijkstra算法,都可以以内部类的形式实现,既方便与外部类network相互调用,也较为简洁、美观。

五、BUG分析

  第一次作业中容易出现的bug主要是在jml理解上,最有代表性的是isLinked传入相同person时候的返回值,在性能上则没有做过多考察。

  第二次作业中隐含着对记忆化的考察,不采取记忆化是绝对要在一些点上翻车的。笔者在自测时,发现如果不采用记忆化的方式,强测允许范围内的极端数据跑下来要接近200S,遂启用缓存,避免了强测中TLE。

  第三次作业的难点主要在最短路径的时间限制和tarjan算法(如果用了)的代码实现。因为参考了讨论区dalao的帖子,一开始就使用了堆优化的Dijkstra算法,但由于实现的丑陋(比如每次求最短路径前先用BFS判断是否连通),导致最慢的点超过了1.3S,和优美的实现有不小差距。根据水群的一些反馈,即使堆优化的Dijkstra算法也是有翻车实例的:比如在第二次作业使用hashmap时,为了减少扩容次数,初始化一个很大的容量,而在第三次作业数据规模减少的情况下,仍然沿用容量相同的容器,导致了遍历的低效率。tarjan算法方面,笔者遇到的问题主要有:仅存在两个点的点双连通分量会被记录,但是不符合题目要求,需要特判;然后是更新搜索子树LOW值时,对于可追溯到的不在搜索子树中的节点,只能用其DFN值而不是LOW值更新。相比之下,可能用N-2次的BFS/DFS算法查询“强连通”实现起来容易很多,但是不容易想到。

  对于测试,Junit是很好用的工具,特别是在迭代或者修改时,可以对正确性做回归测试。写Junit的过程一般是对着规格写,这也是帮助我们重新理解规格。除此以外,如果对算法的时间不能很好地估计,手动捏造数据进行测试也是很好的方法,在第二次作业中,笔者通过自测意识到了记忆化的必要性。

六、jml撰写,理解

  通过第三单元第一次实验,笔者认识到jml是真的难写,助教大大太顶了。在掌握格式规范的基础上,对返回值的描述是巨大的难点。

  首先,我们经常需要构造中间变量来辅助描述result。比如第二次作业中子图的异或操作,jml中构造了一个辅助数组;再比如最短路径的描述,不仅需要用一个数组描述最短路径,还要用另一个数组概括其他所有路径。这里笔者认为,有时候常用的辅助变量可以单独提取成jml中的一个方法,方便复杂方法的jml撰写和阅读。

  此外,描述方法作用时,我们还要约束一些“副作用”,比如addPerson这个很基础的操作,我们必须说清楚,people中原有的在addPerson后也能找到,新的people的size之比原来大1,从而保证不会加入一些奇奇怪怪的东西。

 

你可能感兴趣的:(2020_OO_第三单元总结)