BUAA-OO-第四单元总结兼课程总结

BUAA-oo第四单元兼课程总结

一、第四单元总结

整体描述

​ 本单元的任务主要是解析输入进来的UML类图、顺序图与状态图元素并建立相关的模型,然后根据具体 的指令给出相关的结果,在最后一次作业中还加入了对于输入模型的正确性检查。

​ 输入的最原始的相关信息是UML中的各种元素的相关属性,在官方包的整合下,我们会得到一个UmlElement数组,里面包含了官方建立的对于里面所有元素已经建好的初步模型,我们只需要在这个基础上解析就可。

具体思路

​ 这个单元的整体思路就是建立自己的每一个元素的类,比如MyUmlClassMyUmlInterface等等,对于每一个需要用到的元素都建立这样的模型,然后根据需要的属性以及指令的特性来安排这些类的具体的属性与方法。

​ 在MyImplementation中,建立了大量的容器来储存相应的这些类,以方便后续查询使用。

解析

​ 因为在这次作业中,一种元素的parent可能在它之后出现,因此必须分成多轮解析来进行,根据需要,最后分成了五轮:

  1. 解析UmlClassUmlInterfaceUmlInteractionUmlStateMachineUmlCollaborationUmlAssociation
  2. 解析UmlAttributeUmlOperationUmlGeneralizationUmlInterfaceRealizationUmlLifelineUmlEndpointUmlRegionUmlAssociationEnd
  3. 解析UmlParameterUmlMessageUmlStateUmlFinalStateUmlPseudostate
  4. 解析UmlTransition
  5. 解析UmlEvent

初始化

​ 在开始指令之前还需要做一些方便后续的初始化动作,我大概做了一下几点:

  • 标记一个类里面的重复方法。
  • 对于每个MyUMLStateMachine标记其是否可以到达最终状态。
  • 根据Association在相应的UmlClass中写入与其联系的类的信息。

具体的指令的实现

​ 然后就是具体的指令实现,大部分都是它是怎么说的就怎么写就行,有几个我认为比较有意思的实现如下:

  • 针对所有的方法中的有多个重名对象异常和找不到对象异常,我的解决方法都是同一的,就是建立了两个储存相对应元素名称的HashSet,一个储存所有出现过的名字,一个储存所有带重复的名字,在给定一个name的时候,直接根据在不在这两个HashSet里面就可以。
  • 针对耦合度的计算:这次作业中出现了两个耦合度计算,整体思路大概就是根据规则计算自己的+父类的。
  • 针对最后一次作业中的check:这些check有些可以在解析过程中直接解决,比如001、007、008,剩下的可以专门在相应的类里面按照要求写对应的方法去解决就可以。

有关于自建元素类

​ 对于自建元素类的一切需要储存的属性,一切以需要为准,比如有些甚至只需要储存id或者name。

​ 同时,针对一些问题的答案,可以在计算过一次后直接储存下来防止多次计算不利于性能,比如类的继承深度等等。

最后,为了防止MyImplementation类因为太长而被checkstyle挂掉,建议分裂出来一个类来储存自建的一些方法和所有的解析过程。

出现的bug

​ 本单元的作业没有互测,只有强测,在第二次作业中因为我对于一个异常和输出结果的处理顺序错误导致强测挂了一个点。而在写的过程中,de出的bug就很多了,很多都是因为对于UML图的各种元素的不理解以及对于指导书上长长的各种解释漏看少看了一些。

二、四个单元的设计思维

第一单元

心路历程

​ 第一单元的作业是解析表达式,当时对于java并不熟悉,并且第一次见到这种层次化的写代码形式(当时c语言的面向过程深入我心),直接就给我整懵了,后来还是靠着周四上午训练中助教给的代码结构以及评论区的大家积极主动分享自己的架构,我才照猫画虎的用ParseLexer类的组合将代码给搞定了。

设计思路

​ 这一单元的设计思路主要是按照层次化建模,按照表达式、项、因子的顺序建立一个表达式树,然后将这个树按照特定的规则化简之后输出。

​ 在这个单元中最开始用到了正则表达式,但在最后我放弃了正则表达式解析的方法,因为一开始就没有接触过写正则表达式,而且这个写的容易被hack。

​ 在这个单元的最后一次作业中,因为前期建立模型的不合理,导致最后的化简合并极其困难,并最终导致我放弃了最后的性能分:当时我试图建立一个:

\[x\times常数\times\sin\times\cos \]

的通项,每一个自带指数,并且sin和cos都是一个数组。但是后来在合并的时候发现由于sin和cos中的每一个内部又是一个表达式,因此想要合并需要一项一项的逐个比较,工作量很大,后来就没有进行这样的合并。

第二单元

心路历程

​ 这个单元是多线程的电梯调度问题,当时听说第二单元涉及比较(玄学)的多线程问题,我就按照老师的推荐提前买了本《图解java多线程设计模型》,然后对这生产者消费者模型以及基础的术语看了两天,后来在作业出来的时候确实感觉简单很多。

​ 但是当时对于具体的唤醒,锁等概念感觉还是很抽象(这一问题最终在os的进程通信部分给整明白了)。

设计思路

​ 这单元的作业在开始之前因为有了第一单元的经验,我还比较谨慎的思考了电梯的相关架构,最后采用了一个候乘表+一个电梯类+读入线程+输出安全线程这样的结构,采用LOOK调度算法来进行电梯调度,电梯自己扫描候乘表并结合内部已有的乘客信息来决定自己的前进方向。

​ 在第二次作业中相关架构变化并不大,也只是新增了一个横向电梯类。

​ 这单元的第三次作业我进行了大重构,新建了文件夹并且重新写了一遍基础代码,这也是这次的十二次作业中唯一一次的大重构,原因是我在这次第一版中采用了看似能够优化性能实则特别复杂的一个思路:扫描与动态调整,后来也许是因为动态调整出现了问题结合多线程的不确定性以及不能方便的调试,我只能在最后一个晚上开展了紧急重构,采用了下面的思路:

​ 每一个请求在被输入进时就会被送到MyMap类中去规划一个路线(实际上是一个换乘楼层),利用已经储存的所有电梯的相关信息,根据:

​ 以请求出发楼层、所前往楼层的横向电梯为第一优先级;

​ 以请求出发楼层到所前往楼层的横向电梯为第二优先级;

​ 以1层横向电梯为第三优先级;

这三重优先级来规划换乘楼层,然后整体的调度器会根据这个请求当前的状态:

​ 在换乘楼层

​ 不在换乘楼层

​ 已经换乘完成

来决定接下来是将该请求放入横向电梯的列表中还是纵向电梯的列表中,在一个人从电梯下来时,会切换状态,然后又会根据以上的状态被调度器放到新的列表中,直到到达目的地。

​ 电梯方面的策略则是采用自由竞争的方式来接乘客,并且纵向电梯整体采用LOOK调度算法,横向电梯整体采用直接一个方向循环的策略。

​ 最后线程结束的判定是根据调度器类里的计数器来决定的,每加入一个人就count++,然后在调度器里存在一个方法,在输入结束时被输入线程启动,每次会询问当前的count是否为0,如果不为0就会陷入等待,等到被唤醒后继续询问,直到count为0,然后会给所有的线程下达结束标志,所有的电梯线程会由此结束。

第三单元

心路历程

​ 这一单元主要是根据官方给出的JML规格文件来写出对应的方法,自我感觉是这四个单元中最简单的一个单元,只有个别的几个方法在读JML规格上有些困难:queryLeastConnectionsendIndirectMessage这两个方法因为分别是最小生成树问题和计算最短路径的问题,用JML规格描述出来就是条件嵌套嵌套好多层条件,感觉读起来有点难度。

设计思路

​ 感觉这一单元我们写的代码虽说是完全按照官方给的约束,但是还是在一些细节方面可以谈到一些设计思路。

​ 比如针对元素容器的选择,一定要选择HashMap等而不要用ArrayList去遍历,否则性能堪忧。

​ 第二个就是针对isCirclequeryBlockSum可以采用并查集的结构来大大简化问题。

​ 第三个是针对一些中间变量,可以直接将其计算一遍之后储存,这样就可以在之后反复查询的时候大大节省开销。

第四单元

心路历程

​ 本次作业刚拿到的时候真的是一脸懵,因为虽然前几个单元画过UML图,但是对于UML图的理解基本没有,而且官方也并没有向之前一样给了一些些思路,看懂官方给我们的是UMLElement我都抱着官方包扫了一大部分才看懂。

​ 但是在知道具体要做的事情的时候,就比较简单了,当时首先就想到了第一单元的自顶向下的整体模型建立,并且这次作业确实很适合这样来操作,后面就顺理成章的迭代了。

设计思路

​ 具体的思路在这篇博客的开篇就比较详细的讲了一下,这里就不多说了。

三、对于OO方法理解的演进

​ 在接触oo这门课之前,我写过的代码基本上就是c代码,是面向过程的,根据需求直接设计一个流程然后一个文件写到低。

​ 在刚接触oo的时候,第一个感觉不一样的就是需要建立好多个不同的文件,同时刚开始比较大的疑惑是main函数在哪,这东西怎么运行(然后知道了main也是个方法需要在类里面建一个)。

​ 了解并写了第一单元的作业作业之后,对于面向对象这个感念其实还不是很清晰,因为比较表达式中的这些因子什么的是个比较抽象的概念,然后这个因子具有什么属性还能很好的理解,但是有什么方法确实是比较抽象。

​ 然后第二个单元直接解决了我对于这方面的疑惑,因为电梯确实是个很具体的物体(对象),日常生活中的电梯也确实有一些运行的方法以及决策的方法,这个就变得很容易理解。

​ 然后我就大概懂了这种面对一个问题的时候,首先拆解这个问题里面都分别有那些对象(比较抽象的比较具体的都有);然后这些对象该用什么来描述(属性);分别可以干什么或者说它的体系要求它能干什么(方法);然后这些对象都有着什么联系,这点通常体现在一个类与另一个类之间的继承(extends,继承父类的属性与方法),一个类与另一个类之间的关联(一个类里面有另一个类作为属性),一个类与另一个类之间通过一些中间类进行通信等等。

​ 在分清楚这些问题之后,就可以分别建立这些对象,然后再建立Main函数来按照指令与要求测试功能。

​ 在这过程中还有一个有用的小技巧,就是全局的工具类。有时候一个全局的工具可以节省很多麻烦的开销,而且如果设计成静态的,就不需要在每个类里面再实例化一次,便捷性很好,同时可能还可以完成一些需要链接多个类的工作,比如第二单元的调度器。

​ 同时,在这么多次的迭代中,也懂了一些写代码时需要注意的事项:高内聚、低耦合。为了方便需求改动,对于类的封装性要好,只留必要的接口给外界,如果与其他类的牵连过多,可能出现一改改一堆看起来还不如重构的情况,事实上我们的作业代码也就1~2k行代码量的样子,一般是十几二十几个类(除去官方包的和那些异常类),已经是很小的project了。

​ 还有一些与oo无关的一些经验:类的名字和方法名字一定要起好,有些方法名字如果起的具有迷惑性,很可能就是bug与混乱的根源;必要的注释一定要加上,尤其是一些tag,state和返回boolean的一些判断,一定要表明对应的变量代表的含义否则容易混淆(或者是用一些宏或者枚举类来利用变量名方便的区分)。

四、对测试与实践的理解与演进

测试理解

​ 在我们的每一次作业中,测试都是重要的一部分,无论是某个点挂掉了需要debug,还是寻找代码潜在的bug,都需要一定的方法。

​ 针对某个特定挂掉的点:在这种情况下,如果没有能拿到具体公开的数据,可能需要使用第二种方法。如果拿到了测试数据,就可以用最方便的断点调试直接测试,或者是根据错误输出来推断自己可能的逻辑漏洞然后阅读代码来寻找。这样debug的效率其实也很高。

​ 寻找潜在的bug:这种情况下一般是分为两个阶段,第一个阶段是直接回看需求,对于重要的代码逻辑在仔细阅读一下,看看有没有出现逻辑写反了(忘记!)或者是一些if-else的逻辑混乱问题(对于有很多循环与if-else嵌套的地方尤其要注意);第二个阶段基本就是自己编写测试数据(一般是根据逻辑自动生成),然后通过对拍或者采用写测评机的形式来进行大量的数据筛查。

​ 针对编写的测试数据,尤其要注意数据的有效性:比如第三单元生成数据时,不能完全按照随机数生成各种id,否则会导致触发大量的异常,但是正常功能的测试命中率极低,基本相当于无效测试。

​ 对拍的时候可以用一些shell来简化操作(终于学会打jar包了)。

​ 然后对拍的过程中如果发现了bug,也是需要先根据具体的错误输出定位出现问题的代码部分,然后再专门生成这一部分的数据来用自己喜欢的方式测出bug。

​ 测评机……实际上我现在也不会写测评机,最后几节课的分享中听了很多大佬们对于写测评机经验的分享,有机会一定尝试(我太菜了!!)

测试实践

​ 在第一单元和第二单元我都没有进行特别的测试,首先是第一单元当时因为对任务不是很熟悉,因此没有什么时间进行测试,很多时候刚刚写完就已经快结束了。第二单元则是不太会用官方的那个投喂包,因为多线程的不确定性bug也不好测。

​ 第三单元因为指令比较规整,所以我和两个同学一起写了数据构造和自动运行的相关脚本,然后利用对拍的方式成功避免了强测全军覆没的悲剧(一de一堆bug)。

​ 第四单元因为一个人在家,并且这个UML图实在是不知道怎么构建数据,加上官方的弱测也比较狠,所以没再怎么测试,只是反复确认了自己对于这几条指令的理解没有问题。

五、课程收获

​ 在本次oo的课程中,我可以说是从一个完全不懂面向对象人到基本上懂得了面向对象的一些思想(具体的思想都在上面啦)。同时还和os的学习一起理解了多线程的含义与相关的问题。最重要的是在和很多同学的交流以及观察评论区的过程中,学会了一些分析问题的方式:

  • 从一些已有的官方包代码入手,根据框架分析需求。
  • 灵活的利用各种数据结构来方便代码的实现。
  • 学会采用层次化的角度看待问题,首先要想到怎么将问题解构建模,然后一个一个处理,只要分割的足够合理,问题复杂也可以一点一点完成并且不会混乱(一个学期之前我写300行的代码都能写的忘了前面)。
  • 还有提前在草稿纸上先设计一番,脑补一下大部分方法,思考一下部分复杂的方法要怎么实现,能够让你提前规划一下需要什么属性以及可行性,真的很重要!!!

​ 同时这大概也是我第一次接触测评与观察其他同学的代码,虽然我并没有过多的在互测中出手,只是在几次互测中下载了同屋同学的代码来观察,就收获了一些在我看来很新奇的一些写代码的风格与想法(不得不说就算是同一个思路,不同的人写出的东西都差距很大)。

​ 同时,我还接触到了JML规格与UML文件的相关知识。而且为了解读每一次的任务,这种获得的知识可能比单独学习这些文件的知识还要记得清楚整的明白。

六、对课程的三点建议

  1. 希望能够加强对于怎么搭建测评机这方面的指导(给个指导书让大家学学也好啊……一学期都没完全整明白这个真的是我的遗憾啊),既然我们的课程有互测,课程组也希望大家学会测试,而同学的分享有的时候理论偏多,希望能够出点这方面的教程!!!
  2. 然后是关于上课的时间:能不能少一点早八,真的困起来学习效率直线下降,还有希望把每次那个训练课程安排的早一点(这学期周一发布任务,周四才上机,我看出来课程组可能想给我们一些指导,希望可以提早一点)。
  3. 也许可以在pre的时候讲解一些基础的java语法?因为java里面有好多与c不太一样的特性(比如关于BigInteger,迭代器,String等等),还有遍历的时候删除元素这种操作怎么做比较好这些因为容器和C中数组不一样带来的措手不及的问题,变量定义只是一个类似指针的名字这种影响函数传参的设定等等。我感觉如果能在pre中讲解一下这些东西,会让学弟学妹少很多因为语言特性导致的奇葩bug。

尾声

//最后开一个代码块写个尾声
//我们对于程序的初见一直很美
"hello world"
//我们很少写一个结尾,因为代码块的end只是暂时的
//我想,我们很快就会
"to be continue"
//这个学期很多个超过凌晨一点的夜都是在看你,包括现在,2022/6/28/1:01
posted @ 2022-06-28 01:12  银雪影寒  阅读(5)  评论(0编辑  收藏  举报