一.综述
第四单元的主题为学习UML类图,作业的目的是对于给定的输入(为加工后的UML类图代码)进行解析,并进行相关的查询操作。第一次作业只支持UML类图,第二次作业在此基础上添加了UML状态图和顺序图,第三次作业则在前两次作业的基础上,增加了错误检查功能,要求对给定的错误进行检查。
作业的代码解析部分已经通过官方包给出,只需完成核心交互类(即想JML一样完成所有的方法)即可。
本单元没有互测与性能分。
最后,给出一个在本单元给我帮了大忙的博客:https://www.cnblogs.com/zhaozijing1998/p/11079346.html。里面比较详细的介绍了本单元出现的UML类图元素。
不过有几点需要注意。
- 顺序图中也有UMLAttribute,且并未与类图中的作区分,但是类图中的parent是UMLClass,顺序图中的parent是从未出现过的UmlCollaboration,因此有部分UMLAttribute会是没马的孤儿(但是相应的,这部分也用不到)。
- UMLMessage的source和target未必全都是UMLLifeline(当然不是的部分也用不到)。
以上是我在完成作业时实际发现的不同,如果不考虑,可能会出现空指针或者死循环等错误(因为我就是通过出现的错误才发现的)。
二.作业与BUG分析
第一次作业
1.代码思路
第一次作业整体思路就是将所有的数据在输入时即存储,查询时直接调用。因为ID唯一,因此建立从ID到元素的HashMap,又因为官方解析文件已经通过继承的方式将输入的UMLElement分为许多不同的元素,因此通过一个巨大的switch进行分类后,对于每一种分别建立HashMap进行存储。因为所有的UML元素之间的数据结构都是通过parentID这种只能通过子女查找父母ID的反人类设计来组合,而实际应用更多的是通过父母查找下属元素,因此,对于使用的高一级元素,都直接在题目给的解析之外另外新建了一个类,然后在类里面通过HashSet或ArrayList存储下级元素。为了防止可能出现的对于JAVA的运行原理理解错误,导致两个不同的元素具有相同下属元素时,实际引用的两个下属元素其实互为拷贝的影流之主情况,因此在父母储存子女时,全部储存的是ID,而非具体的元素,所有的元素都要通过ID在整体的巨大HashMap中查询,这样虽然牺牲了一定的简洁性,但是降低了错误率,同时也更利于数据结构的组织。
因为元素的输入顺序未必是先父母后子女,因此可能出现子女进入找不到父母的情况,因此开设了孤儿数组,对于需要有父母但是没有查到的元素,存入孤儿数组中,然后在所有元素分配完毕后(部分存入对应HashMap,部分存入孤儿数组),对孤儿数组中的元素进行遍历分类循环,直到所有元素都得到分配且孤儿数组元素数量为0.
由于查询时大量使用类名而非ID,因此对于有些类分别建立了使用ID和name的两个HashMap,在属性中加入了存储次数的int变量,重名时只添加到ID的HashMap中,name的HashMap不变,对应的元素下属数量属性+1,当通过name查询到某个类的int大于1的时候,就报错。
剩下的函数部分则较为简单,逢山开路遇水搭桥即可。
2.代码度量分析
UML类图
第一次作业中,我将主要内容都放在了MyUMLInteraction中,另外针对接口、类和方法根据所属关系构建了类和成员变量,用于在查找时可以无序遍历直接寻找其亲属。不过由于association比较贴心的准备了getend方法直接得到两个AssociationEnd的id,因此专门构建的类就没有用上。
复杂度分析
复杂度较高的几个方法有:handleType,用于对传入的统计方法的方式进行分类,由于有大量的判断语句因此复杂度较高,getClassOperationCount同理,arrangeEle纯粹是因为对传入的元素分类处理,因此用了一个巨大的switch。
3.bug分析
本次作业公测有一个测试点未通过,是在查找类的所有实现接口的时候,将该类的所有的实现接口与父类均存放进队列,然后判断队首元素是类还是接口,再将所有的实现接口与父类存放进队列。但是此算法忽略了多个不同元素的实现接口和父类可能相同的情况,使得队列中添加大量重复元素,最终出现严重的重复查找问题以至于超时,修改后另外添加了一个HashSet用于判断元素是否重复。
第二次作业
1.代码思路
第二次作业在第一次作业的基础上,新加了状态图与顺序图,其查询内容较简单,在将数据结构整理好后功能很容易就可以实现,但是由于新增了许多的元素种类,因此原先的switch太长以至于无法通过checkstyle,导致我讲一个函数拆分成了三个,前面的default进入后面的函数。
在完成作业后,提交时发现有几个测试点未通过,经查发现原先的UMLAttribute在顺序图中也可以存在,其父母为UmlCollaboration,但是该类元素在此次作业中根本未出现,因此孤儿迟迟得不到父母分配。不过后来我在debug时发现现有元素中的UMLInteraction的父母也是UmlCollaboration,因此就可以记下这些ID,专门开设了孤儿院存放这些孤儿(其实不放也行,反正没用)。
2.代码度量分析
UML类图
由于状态图和顺序图的加入,导致第二次作业的类几乎翻倍,不过大多数类中的代码量并不多,由于所有方法全部在核心类中,导致其代码量巨大超出了checkstyle的要求,因此将部分代码转移到了class等类中,在核心类中调用方法得到结果,而核心类中所有的HashMap都通过传参的方式传入其它类相应的方法中。
复杂度分析
可以看出,此次作业中复杂度依然不高,大多数的复杂度来源都是判断而非循环。
4.bug分析
此次作业中出现了一个没有想到的bug,就是UMLMessage中的source和target的指向除了lifeline之外还可能有其他奇怪的东西,但是我不知道,因此没有考虑,这就导致在添加关系时找不到相应的lifeline造成空指针异常,解决方案非常简单,在添加关系前加上一句判断是否为空即可,这也反映出了我的程序鲁棒性有欠缺,没有养成提前检查的习惯。
第三次作业
1.代码思路
第三次作业与第二次作业相比,架构变化不大,甚至没有什么可以新增的类,主要就是添加了一些检查方法,而多数方法在理解要求后也较简单,尤其是在ban掉了类的多继承和接口继承类之后,继承实现关系几乎可以完全分开考虑,需要注意的就是在写算法的时候考虑要全面,不过我因为考虑不周出现的bug通过题给的测试点和弱测中测就被我发现了。
2.代码度量分析
UML类图
此次的架构几乎没有变化,就是方法更多了,另外由于核心类无论如何也压不到500行以内,因此又专门新建了一个祖安类来分担部分代码。
复杂度分析
复杂度明显上升,但是因为检查要遍历,所以我起了,一枪秒了,有什么好说的?
4.bug分析
此次作业没有出现bug。
三.OO课程总结
四个单元中架构设计及OO方法理解的演进
可以说,经过这个学期的OO课,我才算真正明白了面向对象的思想与方法,在此也不得不感慨一句JAVA大法好,之前用了一个学期的Python,但是却由于只学语法和数据结构,导致我从C开始就养成的面向过程方法没有任何改观,直到这学期OO课的到来。
可以说,寒假的作业很简单,也很有趣,但是难度并不很入门(至少上来就构建立方体我是有点懵逼的),再加上我寒假吃饭睡觉打电脑实在太香了,因此直到第一次作业到来我的寒假作业也只完成了两道题(别骂了别骂了),所以第一次作业我几乎就是干拉,但是由于题目过于简单,出奇的没有白给,反而得了第一次100。但是后续两次作业随着难度的提升,分数也从100下降到了80+和60+,其实原因也很简单,就是虽然自己知道后续要做拓展,但是根本就不知道如何去做,每次都会由于一些改动而做很大的重构,第三次为了保留前几次的成果,已有的不支持括号的多项式和单项式原封不动,又另外写了一个,属实憨批操作,最终工作量极大,第三次硬是从早到晚写了两天,最终代码还有不少bug。不过,正是在这一单元中,我体会到了架构的重要性。第一次作业还是一main到底,到了后续的二三次作业就开始有意识的构造多项式、因子、三角函数类。
第二单元作业中,我吸取了第一单元作业的教训。开始有意识的提前规划架构,同时也不再干保留前几次成果那种憨憨操作,直接在原有基础上改,结果就是一劳永逸,第一次作业稍微麻烦了点,后面两次就可以在第一次作业的基础上比较轻松的完成,唯一有点遗憾的就是到第三次作业时结构僵化,改进空间变小,导致稍微有些捉襟见肘,不过最终虽然出了几个bug,但还算是比较成功的完成了。
到了三四单元作业,架构相对已经比较熟练了,比起之前也没有什么太大的变化,无论是新建类还是组织数据结构,尽管过段时间看可能还是憨憨操作,但是至少自己用起来已经比较得心应手了,甚至因为checkstyle超了几百行,我还找了很多不同的方法转移代码来分担代码量。
总体感觉来看,第一单元的作业设计尽管越往后越难(与我自己吃饱了撑的非要追求超出我水平的性能分有关),但是我觉得真的对于我的面向对象水平来说是一个巨大的提升,第一次作业和所有的寒假作业,我都采取了一main到底的形式,最多在寒假作业后期建了一个word类,把一部分方法放到了里面,但是仍然对面向对象没有什么概念,每次都要重构。结果到了后面,main函数,甚至主类中几乎都没什么东西的时候,搞得我都不大好意思了。第一单元第二三次作业有些为了面向对象而面向对象的感觉,对于求导运算中的每个部分,似乎我都建立了一个专门的类来管理。直到第二单元,几乎从零开始的情况下,我看了看往届的几篇博客后,逐渐明白了什么是架构,原来并非有固定的标准,只要能够满足自己的要求就可以,尤其是在我尝到了一个不错的架构思路的甜头之后,终于开始顿悟了。
当我学期末的时候,已经无法直视自己第一单元代码了,无法想象这样的东西是哪个憨憨写的,但是看到自己当年的代码写成这样也挺高兴的,因为至少,这说明我这一个学期的OO,努力都没有白费。
四个单元中测试理解与实践的演进
这就触及到我的知识盲区了.jpg
其实有点不大好意思说,因为虽然自己的代码水平在进步,但是测试缺完全停留在原始阶段,依靠自己手动构造测试数据然后手动计算(废话我要是有能百分百正确运行的代码我还写这个代码干什么?评测机:嗯?谁在叫我?),最后和程序的输出比对,然后确定自己程序有没有算错,可以说就是完全手工的测试方式。
多数情况下,我的测试思路都是:完成代码——跑指导书上的测试样例——跑我觉得有困难的样例(完全靠脑洞,想不出来就直接跳了)——跑弱测和中测,如果在此过程中没有发现问题,那么就直接交上去了,实际上我的大多数代码在提交之前都没有得到比较全面的测试。当然不是没有bug,是我写的bug太低级,绝大多数程序都无法正常运行,基本过了弱测和中测后,再出bug的情况就寥寥无几了(第一单元是个例外,因为自己的水平实在太次,所以因为没有好的测试数据还被中测卡到过死线,交上去之后也是将将及格的分数,但是当时我太弱了,能写完代码就已经了不得了),除了第三单元第一次作业,因为跳过了前两步测试,而第三部弱测和中测放水导致我的程序根本就没测就交了上去,然后就崩盘了。
讽刺的是,正所谓消费所形成新的需要,对生产的调整和升级起着导向作用。由于绝大多数情况下,手动的测试样例就已经足够了,因此对于自动测试并没有需求。没有市场,自然也就没有生产的必要。有些bug甚至在出错后不跑测试数据,仅仅看出错的那行代码就能看出来,其余的bug在有测试数据的情况下,最长的单一bug的debug时长记录也是按照分钟计算的。得益于我自认为良好的编程习惯,导致绝大多数的bug都可以通过简单的方式de出来。唯独测试样例繁杂debug难度高的第一单元,因为当时我实在太菜了,代码都不会写,所以也用不了自动评测,因此我一整个学期其实都是一路依靠手动评测过来的。
不过,由于本课程的目的在于面向对象,自动评测属于课程之外的拓展内容,有能力的同学可以通过其来帮助自己测试程序,像我这样没有什么需求又怕麻烦的人就算了。根据我一学期的OO体验,课程组在课程理解和构建方面做得很好,相信他们也不会在这方面为难我们。
尽管我全程使用手动评测,但是我的测试策略也在演进,前期仅仅依靠指导书上的数据和弱测中测,后来也开始根据自己在写代码过程中的理解自行构造一部分测试数据,从最后的结果来看,覆盖率比起刚开始有了很大提高。
课程收获
OO可以说是本学期我收获最大的一门课程了,至少与OS课设相比,网络上的JAVA学习资源要多得多,这使得我在多数时候遇到困难都可以很容易的在网络上得到解决,即便有疑问也可以在群里得到带佬们的解答与点拨。而在自己理解之后,我这个菜鸡甚至也可以去群里回答别人的问题。而且自己写一个上千行代码的程序,可以带给人相当大的成就感(尽管这些成就感在看到测试结果一片红之后就会消失)。这使得我每周都痛并快乐着的完成OO作业:周二公布作业;周三看了看题,觉得这是人做的吗?周四开始搜集资料,着手写代码;周五疯狂去群里提问并肝代码;周六完成剩下的尾巴并debug(有一次de到晚上21:55的极限操作)。然后休息几天(写别的作业)再开始下一次轮回。我当初好不容易完成了第一单元的作业,看到第二单元的多线程的时候,感觉自己要完了,这根本不是我能学会的东西,但是通过周三的实验,再加上自己的理解和群内的答疑,我竟然真的写了一个98+的电梯出来。可以说,正是通过OO,我发现无论当初看到的是多难的难题,当你下定决心去做的时候,无论多难,你总会一点一点把它们完成(对OS课设不适用,不会就是不会)。
我自己都难以想象,在学期开始的时候,我还是一个开学之后狂补寒假作业(2月24截止的作业我23才下载完软件)的人,在完成第一次求导作业的时候,我的寒假作业一共13道题只完成了2道,剩下的都是3月10日之前补完的(当时第一单元都过了一半了)。(PS:寒假作业这种事情是我从小传承下来的习惯,假期做作业对我来说无异于噩梦,因此绝大多数情况的寒暑假作业都是开学前甚至开学后补的,不过代码这种事情肝是赶不出来的,所以就白给了)但是学期快结束的时候,我也可以好几次测试点全过,也可以在群里给别人答疑,甚至可以在讨论区发布有超过十个人赞的帖子,这在之前是想都不敢想的。
当然,我也并非一夜之间成了大佬,实际上直到最后,我与大佬之间的差距可能也并未缩小。自动评测从未用过,都是手撸测试数据,然后程序输出和自己手算的比对,相当多的bug都是源自于找不出好的测试数据导致测不出来,知道程序有错定位bug对我来说反倒不是问题;每个单元的学习都要依靠群内的大佬答疑才能进行下去,为此一天在群里更换好几次匿名。由于我自高中以来就一直养成的用古老的方法解决现有问题的习惯,因此除非不得已我是很少去学习新东西的,这也就导致虽然我的程序普遍效果还不错,但是实际上内部的运行原理都十分简陋,很多时候代码的风格就突然苏维埃了起来。很多别人已经习以为常的东西可能我一个学期下来也没用过,十分基础的HashMap直到最后一个单元才开始用,之前的ID和元素对应都是通过无脑遍历实现,甚至为了优化算法而专门写一个折半查找。我干的蠢事,其实并不比我的进步要少。
但是当我真正一个学期下来的时候,发现,我之前两个学期学习C和Python所养成的编程习惯,和对代码的理解,在这短短一个学期就被完全颠覆了。之前的代码都只是能运行即可,优化和算法根本都不在考虑范围之内。而这学期,有了性能,有了架构,有了代码风格,有了鲁棒性,程序也开始有意无意的加一些注释。可以说,经过OO课,我才学会了如何去写真正意义上的代码。
四.一些建议
- 对于JML第一次作业中,在没有提示的情况下,弱测和中测未测isCircle,强测全测isCircle的钓鱼行为表示强烈谴责,让包括我在内的许多人因为过于弱智的原因导致强测爆炸。
- 提供更多的学习资源,网络上的JAVA学习资源很多,但是良莠不齐,泥沙俱下,还有很多对于课程而言是答非所问,希望能给同学们提供质量更高,与我们的课程更加有针对性的学习资料。
- 希望研讨课可以更多分享一些和课程紧密相关的知识,很多同学分享的内容我整个学期都用不到,而且有些难度过高我听得稀里糊涂,再加上可用性很低,导致实际效果有限。
五.线上学习OO课程的体会
OO课相对来说,除了没办法与老师同学面对面之外,并没有因为线上学习而带来很大的影响,因为作业和实验什么的本就应该线上完成,反而因为其他课都是线上学习而带来了更多的时间写OO(逃)。
当然具体来说,不同点也是有的,比如录播课程可以回看可以设置倍速,因此学习起来更加个性化(缺点就是在家上课更容易走神)。时间安排更加自由,对自律性的要求也更高(因为家里好玩的比在学校多多了,过得也舒服极了)。
最后,再次称赞一下OO课程组,我在寒假结束的时候其实学习状态特别不好,甚至开学了寒假作业还一点没动,很多课程学起来都不在状态,前几周也根本不想学习,每天过的浑浑噩噩。
但是OO课,我是真的用心去做了,即便每周的代码都要写好久。不得不说,课程组是十分用心的,OO课给我的体验就像上学期的计组,虽然虐,但是我是真的学到了好多东西,痛,并快乐着。而且我也享受那种自己完成代码后的成就感(在测试结果出来之前),整个学期的学习状态,我都是通过OO找回来的,其他没有任何一门课能够让我如此用心。
最后的最后,我要给课程打个五星好评,一次付清。