zoukankan      html  css  js  c++  java
  • 面向对象第三单元博客作业

    0 引语

      在第一单元中,我们初步了解了java语言和面向对象的思想。紧随其后的第二单元,我们学习了多线程的相关知识。讲道理,与面向对象思想相关的知识我们已经掌握了,所以这一单元我们学习的是一种新的工具链:JML。在本单元的学习中,我们依旧是通过迭代开发的方法,完成了三次作业。这三次作业的目标是完成一个小型社交关系模拟系统,例如,可以使用add_person添加人,在两个人之间可以添加关系等,使用一些指令可以询问当前关系统计、检查状态等,我们要做的,就是在每一次输入指令之后给出对应的输出信息。

      嗯嗯,从判断正确性的角度看,比上一单元简单多了。毕竟不是多线程,既不用考虑线程安全问题,也不用考虑输出结果的正确性检验。在互测之中,也只需要简单的对拍就ok了,看起来还挺和蔼可亲的……

      桥豆麻袋,JML?那是个什么东西?

    一、JML简单梳理及实例分析

    1.1 JML是什么?

      JML的大名是Java Modeling Language,即Java统一建模语言,它以模式化的方式,描述了对于一个类或方法而言,应当满足什么条件,在执行后应该满足怎样的结果。但是,说实话,我并不喜欢这种描述语言,总觉得有些地方是在“不说人话”,可能是我还没有领悟到JML的精髓吧……虽然我知道能够用OpenJML等工具对方法进行是否符合JML正确性的检测,但这东西用起来实在麻烦,所以我最终就没有使用。

       嗯……写出求个最短路的JML需要这么多东西,这还不算exception behavior的部分,还好不是让我写,不然我人就没了。即使如此,在本单元的实验课中还是没能躲过去写JML的考验,然而我完成的并不是很好,有一说一,比起算法的设计,单独写这东西反而更令人头秃。【挺秃然的.jpg】

    1.2 JML实例分析

      无论如何,我们的JML之路还得继续。下面我来用一个实例来说明一下JML是如何对方法的行为进行定义的:

       以addPerson举例,这是我们作业中的第一个指令所对应的函数,也是一切的开始,它的jml如下:

    @ public normal_behavior
    @ requires !(exists int i; 0 <= i && i < people.length; people[i].equals(person));
    @ assignable people, money;
    @ ensures people.length == old(people.length) + 1;
    @ ensures money.length == people.length;
    @ ensures (forall int i; 0 <= i && i < old(people.length);
    @ (exists int j; 0 <= j && j < people.length;
    @ people[j] == old(people[i]) && money[j] == old(money[i])));
    @ ensures (exists int i; 0 <= i && i < people.length; people[i] == person && money[i] == 0);
    @ also
    @ public exceptional_behavior
    @ signals (EqualPersonIdException e) (exists int i; 0 <= i && i < people.length;
    @ people[i].equals(person));

      首先,它定义了两个行为:正常行为和异常行为。先来看相对简单的异常行为:当存在一个取值在[0,people.length)范围内的i,使得people[i]和person的值相等,则程序会抛出一个EqualPersonIdException异常。也就是说,当目前的people数组中存在和person相等的人时,就会满足此条件,并抛出异常。至于EqualPersonIdException具体是什么,应怎样处理,这些都不归我们管了。然后是相对复杂的正常行为部分。可以注意到,它的requires部分正好和异常行为的条件相反,也就是说,我们的程序不会再有这两种行为之外的任何行为了,这也能证明这个jml写得比较完善,涵盖了所有出现的情况。某需要判断length<1111的函数表示强烈谴责。

      那么,需要我们“确保”的又是什么内容呢?

      第一,people的长度等于旧的people长度加1。

      第二,money的长度等于people的长度(第三次作业新增)。

      第三,对于所有的“旧”范围之内的i,都存在一个j使得对应的people和money数值相等。

      第四,在“新”范围内存在一个i,使得people[i]是新增的person并且money[i]为0。

      说人话,实际上就是人员个数加一,钱跟着人员走,旧的都不能变,把新的放上去这样的要求。所以,只要读懂了JML,这个函数该做什么就一目了然了。

    二、JUnit单元测试检查

    2.1 这东西好难用

      尽管我们在上课中提到可以使用JUnit进行单元测试,分享课中也有同学分享过相关的内容,但我感觉它用起来还是过于复杂了。在三次作业中,我都没有使用Junit进行测试,所有的测试都是像前两单元那样构造数据去看结果的,在数据生成器的狂轰滥炸下,程序的正确性已经得到了保证(然而第二次作业在效率上还是翻车了)

      大概是为了强制让同学们使用JUnit进行测试吧,在本单元的第二次实验课中,我们通过“垃圾回收机制”练习,要求提交的代码必须包含JUnit测试,这就让人很难办了,于是,我直到那一晚才开始恶补JUnit知识,然后才稍~微的会用那么一丢丢。

      但是,这东西用起来就发现,它看上去难,用起来……更难!我真的想在这里吐槽一下JUnit,虽然它的用途是单元测试,可是多数时候我们难以想到完备的测试集(你想到了的情况错不了,错了的情况你想不到),况且很多时候,一个类和其它类有千丝万缕的联系,这种情况下我们更不好利用JUnit进行测试。编写JUnit测试往往要花费大量的时间,而这些时间本来可以用在更详细的debug上。而且,JUnit测试难以测出程序的效率问题。

      那么,这么难用的东西,我们为什么还要使用它呢?

      那就不用!

      你以为我要欲扬先抑?NO,测试程序的方法有很多种,干嘛偏要用难用的这一种呢?我大实话就放在这里,JUnit的测试这部分,如果不是为了得分,我早就把它扬了。

    2.2 JUnit测试实例

      这次的程序我从一开始就没用JUnit,为已经完成的程序编写测试没有意义,所以我就拿之前做过测试的实验题来举例子。

      在检测MyHeap的add方法时,我在MyHeapTest中是这样写的,首先输出“ready”的信息,然后重复进行10次添加人的操作,每次添加后,检测myHeap的大小是否比原来多1,并且判断是否已经存在这个新的人,如果不存在,那么assert就会报错。实际上,我这里缺少了对于Heap中人员顺序的判断,如果要测试的更完整一些,应该再加几个assert。程序运行的结果如下:

       顺带一提,这里Before Method和After Method并不在这个函数里,它们在setUp()和tearDown()中。

    三、程序架构分析与迭代历程

    3.1 第一次作业

    第一次作业方法复杂度分析(官方包未列出):

     第一次作业类复杂度分析:

      第一次作业其实还好啦还好啦,基本没有几个被标红的部分,因为我当时是几乎照搬了JML上的定义,JML让我用静态数组我就用静态数组,所以也没什么好说的。事实证明完全照着JML写虽然能够完成任务,但是程序的效率就难以保证了。

    3.2 第二次作业

    第二次作业方法复杂度分析:

    第二次作业类复杂度分析:

      不得不说官方接口是真的香,每一个函数都是完成一个很小的功能,使你的函数不再复杂。注意到类似addToGroup这样的函数还是被标红了,不过这东西本身就要处理很多情况,所以就不说什么了。但是!从这次作业开始,千万不要照搬JML的写法!这会使你的程序极其容易出现超时等问题!我这次翻车就是翻在了这里。关于我翻车的经历,请见第四部分。

    3.3 第三次作业

    第三次作业方法复杂度分析:

    第三次作业类复杂度分析:

      第三次作业很多东西在算法上就已经比较复杂,对于query_strong_link这个函数,我新建一个类QslChecker来专门完成它的检测,另外,本次作业我使用了HashMap代替原来的静态数组,HashMap在查找等方面都非常好用!同时,也为了HashMap使用便利的需要,搞了一个Link类。这一次的作业吸取了上一次作业翻车的教训,取得了很好的结果。话说你家Runner标红真的不用管管吗(滑稽)

    四、强测、互测与bug修复

    4.1 我的翻车历程

      很遗憾。我在本单元的第二次作业中,因为过于大意而轻敌了,最终导致了翻车惨剧的发生。接下来我就来讲述一下这段悲惨的经历……

      

      这是第二次课的数据限制,人最多有5000个,qnr指令最多只有333条,何况,这个qnr还是上次作业的指令,我就没把它当回事。反正是以前的指令,而且迭代的时候也没发生任何变化,这个函数不用改也行吧。当时我就觉得啊,三百条指令,你能秒杀我?你今天能三百条指令把我袁劭涵秒了,我当场就把这个强测作业吃掉!

      然而,从一进入互测起,我就觉得不对劲:怎么我随手交一个数据就能hack到人,甚至一串五?难道……

      果然,强测一出来,BOOM!翻 大 车 了

       我做了这么多次作业,身经百战见得多了,虽然也有强测出错和互测被疯狂hack的情况,可是强测炸成仅剩20分还是头一次见。悲伤之余,我痛定思痛,开始研究我究竟是哪里出了问题。最后终于发现了:我当时太照着JML写,JML怎么说我就怎么做,JML曾经曰过:

      它在qnr里用到了compare_name这个函数,我也是照着做了,但是,问题就出在这里,cn里面其实是重复判断了id1和id2是否包含在人员列表内,我用的是静态数组,所以contains本身的时间复杂度是O(n),进而qnr的复杂度就悄悄的变成了O(n²)了,然后就超时了。

      这个故事告诉我们,千万不要照着JML写代码!千万不要照着JML写代码!千万不要照着JML写代码!在需要讲究效率的时候,一定要自己构造合适的数据结构!

    4.2 互测bug知多少

      那啥,关于互测嘛……我发现,现在同学们逐渐变得佛系了,互测的时候打人的次数大大减少,不像以前各种naive满天飞了。

     第一次作业,大家和平共处,平安无事。(其实是因为第一次作业真的没什么bug可找,对了就是对了)

    第二次作业,这就要值得说说了,由于我强测的翻车,我不慎跌入了C屋,在这样的屋子中几乎是随便交数据就能hack到人,我自然也是杀疯了,最后似乎在bug修复环节赚到了十几分,然而尽管如此还是无法弥补强测足足80分的损失,所谓“战术C屋”是不存在的。C屋一个bug才一分真坑爹

     第三次作业,我又回到了A屋,重振雄风!然后,见到的自然也是不怎么hack的大家们。我和另一位同学各自hack出了别人的一个bug,我hack出的是有人的borrow_from写错了,把加号写成了减号(大家的钱都越借越少最后都成了负翁),最后当然也是小小的赚了3分,另一位同学hack出的是有人把dfg写错了。其实我的代码也有个bug,只是你们没hack出来(

      话说,A屋互测是正和游戏,建议大家能打就尽量打打,说不定就有惊喜。C屋是零和,总感觉我打他们之后有点损人利己的嫌疑(

    五、分享课PPT截图分享

      最后还是要提一下,我在本单元的分享课中分享了《第三单元算法讲解和技巧分享》,你以为这单元是JML,其实这单元是算法哒!由于PPT似乎不能上传到这里,再讲一遍也不太现实,这里我就放出几张PPT截图,感兴趣的同学可以自行了解。

  • 相关阅读:
    June. 26th 2018, Week 26th. Tuesday
    June. 25th 2018, Week 26th. Monday
    June. 24th 2018, Week 26th. Sunday
    June. 23rd 2018, Week 25th. Saturday
    June. 22 2018, Week 25th. Friday
    June. 21 2018, Week 25th. Thursday
    June. 20 2018, Week 25th. Wednesday
    【2018.10.11 C与C++基础】C Preprocessor的功能及缺陷(草稿)
    June.19 2018, Week 25th Tuesday
    June 18. 2018, Week 25th. Monday
  • 原文地址:https://www.cnblogs.com/yuanshaohan/p/OO_BlogHomeWork_Unit3.html
Copyright © 2011-2022 走看看