北航2020年OO第三单元总结

1. JML 理论基础

1.1 初识 JML

JML(Java Modeling Language),顾名思义,就是用于对 Java 程序进行规格化设计的一种表示语言。第三单元我们的学习目标是“掌握规格化的面向对象设计方法”,而 JML 就能很好地表示规格抽象,为我们的代码书写保驾护航;

按照本人的理解,结合本单元所涉及到的知识,JML 语法可大体分为以下几个部分:

1.2 方法规格

相对较宏观的是方法规格,主要包括前置条件、后置条件和副作用约定三个方面。前置条件使用 requires 子句,要求调用者确保 requires 后的判断为真;后置条件使用 ensures 子句,要求方法实现者确保方法执行后 ensures 后的判断为真;副作用范围限定表示出对类的静态成员数据和对象的属性数据的修改。另外结合 java 语法,抛出异常用 signals 子句表示,用 exceptional_behavior 和正常执行的 normal_behavior 相对应;

1.3 量化表达式

也就是离散数学中量词逻辑的形式化描述,在我们的作业中主要有 \forall、\exists 和 \sum 三种形式(其他表达式类似),从名字就可以看出它们的功能,可类比离散数学中的表达方式,一般置于上述方法规格的子句中等位置作为判定条件,提升规范性和可读性;

1.4 原子表达式

履行原子操作,包括表示方法执行后的返回值 \result 表达式,表示方法执行前取值的 \old(expr) 表达式,表示未赋值的 \not_assigned 表达式等。搭配前两种语法使用,增加灵活性和准确性;

还有较为简单的操作符(<==> 等),另有类型规格(invariant 等),仅在一次试验中使用,了解不深,不再赘述。

总之,JML 作为一种较为成熟的辅助性语言,既能让我们完成规格化的代码设计,也能反过来依照代码编写规格,无不体现着契约式设计的思想,在对代码整体性能要求较高的场景下发挥着举足轻重的作用。

2. 工具的使用

2.1 JUnit

JUnit 的使用较为基础,并且课程组用第六次实验专门考察了这一工具的使用。
比如,这样一份求成绩的代码(有 bug):

我们使用JUnit来检验:

结果如下:

检查发现,原代码中 26 行 GPA计算有误,将其改为:

则顺利通过:

以上仅为演示,难免过于简单,不过 JUnit 在规格化代码的检验测试中发挥的作用可见一斑。

2.2 OpenJML(和 jmlunitng)

啊,此等工具,恕本人无能,忙活了一个下午 + 一个晚上,真是一 bug 未平,亿 bug 又起:




也参考了一些博客,奈何缺乏大佬线下指导,一些玄学 bug 实在无能无力,最终无法完成检验,望助教\老师谅解。

3. 架构设计及 bug 情况

鉴于整体架构已经由官方代码大体安排好,这里主要说说自己的数据结构选择和算法构思。

3.1 第一次作业

向来听闻第三单元难度下降,第一次作业我也确实感受如此。首先就是要熟悉 JML 的语法,理解给出接口中 JML 的具体语义,然后按部就班地完成代码编写即可。第一次作业数据量并不大,结构方面用 ArrayList 绰绰有余,大部分函数可几乎照搬 JML 的逻辑,主要是让我们初步建立起对 JML 的感性和理性认识。其中 isCircle 方法稍有些难度,写之前复习了一下 bfs 的相关知识便完成。而那个时候 OS 实验赶的比较紧,又临近五一假期心思有点飘,再加上“蜜汁自信”,我甚至代码写完了没看第二遍,几乎没做测试,就“胸有成竹”地交了上去。然而,灾难总是在意想不到的地方发生————没进互测,强测五分,喜提第一次大翻车。稳住情绪细细一检查,发现是 addRelation 异常的总条件忽视了,导致传入的 id 值相等并且均在网络图中这种本应什么都不做的情况却抛出了异常。看着这极具魔幻现实主义色彩的界面,我竟一时无语凝噎;

3.2 第二次作业

第二次作业难度提升,不仅新增带来大量新方法的 Group 接口,还极大地提升了数据量,显然对容器的选择和方法的高效率实现提出了更高的要求。重温了一遍相关教程,询问了一些同学,对 ArrayList 和 HashSet(以及HashMap) 的各自优劣性和使用场合有了初步了解。遂借鉴空间换时间的思想,搭配使用这几种容器,查询操作时使用 HashSet(或 HashMap),不得已必须遍历时使用 ArrayList。吸取了第一次翻车的教训,老老实实构造数据,和别人对拍,并且细细地把代码又查了两遍。可意外总以另一种方式带给你惊喜,getRelationSum 和 getValueSum 的双重循环让我在强测中错了几个点,互测疯狂被刀。后细看房间内存在一刀二的情况,便审查了一番其他 roommate 的代码,发现一位老兄和我的错误类似,于是构造出能让他和自己超时的数据来,成功 hack(苦笑)。bug 修复时经一位同学提醒,发现讨论区早有缓存思想的讨论,就是利用 addPerson 指令数的限制,让添加人时就完成对要查询数据的更新,这样查询指令复杂度瞬间降到 O(1)。完成修复;

3.3 第三次作业

这一次作业的重点是需要将看似摸不着头脑的 JML 转换为算法的理解(然而善良的助教已经用方法名给我们作了提示),并且需要正确准确地实现。算法小白吸取了上次的教训,先把讨论区细细过一遍。最短路用 DIj 的堆优化?马上学,马上理解,马上写!求连通块数用并查集?用 bfs 似乎更好写且不会超时(参考讨论区一位大佬的博客)!双联通用 Tarjan?对不起,这个对于我这样的菜鸡选手来说真的挺难且极易出错。苦思冥想,遂有一计:

OK,结合杨兄所说,先完成整体框架,再把对应的 dfs 写好即可。检查一番,测试一下,无手残错误便提交。
然而助教的好心提醒似乎在预示着某些事:

果不其然,强测和互测中还是被 qsl 弄得很伤。仔细一想,确实啊,若是关系错综复杂且不太友好,dfs 找两条符合的路径要到猴年马月!(何况还不一定找到,这样复杂度就更可怕了),bug 修复中参考以下做法通过:

4. 心得和体会

这一单元也确实十分有收获。了解到了 JML 这一种规格化设计的语言,体会了契约式设计的精髓,掌握了(大概还算不上掌握)一些工具的使用,对面向对象的各个层次理解也更深了一步。复习(准确来说叫预习)了数据结构和算法的相关知识。在我看来,使用 JML 虽然有些费时且容易考虑不全面(从助教屡次修锅可见),但有了这样的一个“利器”,我们在程序书写和正确性的检验上将会事半功倍,安全性要求很高等诸多场合也将会是 JML 的用武之地。
总的来说,我对自己在这一单元的整体表现很不满意,相对平稳地度过了相对而言较为魔鬼的一二单元,却在难度并不那么大的第三单元翻车翻大发了。为啥?学期后期摸鱼心切,盲目自信又有心无力,咋办?学呗,吃一堑长一智,以 JML 为切入点深刻掌握程序逻辑,在宏观和微观层次都需要牢牢把控程序的正确性,避免出现“顾客问时间,然后酒吧炸了”的笑话。
emmm,现在反思第三单元的错误当然从得分角度于事无补,但听闻第四单元可能与之类似,那就定要以此为戒,做好总结,站好 OO 课的最后一班岗,希望最后牌面上(指分数)不会太难看,自己也能受益匪浅,打好后续课程以及职业技能的基础。

posted @ 2020-05-23 00:30  _菜鸡  阅读(250)  评论(0编辑  收藏  举报