目录
UNIT4作业架构设计
三次作业的基本思路都是构建树状结构来进行数据的管理。
第一次作业架构
新建了一个Tree类来将数据进行树状管理,每个元素都可以通过id和parentid建立起一个对应的Tree节点,同时采用了两个hashmap来根据id查找相应的元素和Tree节点
public class Tree {
private String id;
private String faid;
private ArrayList<UmlAssociation> ass;
private ArrayList<UmlAssociationEnd> assend;
private ArrayList<UmlAttribute> att;
private ArrayList<UmlClass> cla;
private ArrayList<UmlGeneralization> gen;
private ArrayList<UmlInterface> intf;
private ArrayList<UmlInterfaceRealization> intfre;
private ArrayList<UmlOperation> op;
private ArrayList<UmlParameter> pa;
private ArrayList<Tree> associated;
public Tree(String id, String faid) {
this.id = id;
this.faid = faid;
ass = new ArrayList<>();
assend = new ArrayList<>();
att = new ArrayList<>();
cla = new ArrayList<>();
gen = new ArrayList<>();
intf = new ArrayList<>();
intfre = new ArrayList<>();
op = new ArrayList<>();
pa = new ArrayList<>();
associated = new ArrayList<>();
}
public Tree(String id) {
this.id = id;
ass = new ArrayList<>();
assend = new ArrayList<>();
att = new ArrayList<>();
cla = new ArrayList<>();
gen = new ArrayList<>();
intf = new ArrayList<>();
intfre = new ArrayList<>();
op = new ArrayList<>();
pa = new ArrayList<>();
associated = new ArrayList<>();
}
.....//相应元素取值/修改方法
}
每一个数据类型都有一个相应的数组,在寻找某个类的元素时直接遍历该类的数组即可,不需要遍历所有的儿子元素。
第一个构造方法适用于有parent的节点,第二个构造方法则用于构造*父节点。
对于UMLClass,UMLInterface两个类,各设立一个*父亲节点,这两个父亲节点下分别管理了所有的UMLClass和UMLInterface,然后UMLCLass和UMLInterface的节点下各自管理着各自的UmlAttribute、UmlOperation、UmlGeneralization、UmlInterfaceRealization、UmlAssociation,在UmlOperation管理着相应的UmlParameter,UmlAssociation下管理着相应的UmlParameter。
同时在Tree结构体中存储了一个Tree数组associated
,用于记录和自己相关联的节点,方便实现类关联数、类的关联对端查找等指令的实现
结构如下图:
所有的函数实现放在了MyUmlInteraction
类中,显得该类非常的杂乱(甚至快要超过500行的限制)。
第一次作业bug分析
虽然使用了大量随机数据来评测,还是出现了特殊数据初始化的问题。在我原本初始化的过程中忘记考虑了输入的类图为空的情况,导致出现了空指针报错的异常,导致强测裂开了一个点。这个故事告诉我自动化测试还是有许多不足,特殊情况并不一定能做到足够的测试,自身测试的时候还是应该对特殊情况进行足够的测试,尤其是输入为空的这种情况,来避免不必要的失分。
第二次作业架构
第二次作业在第一次的基础上加上了对UML顺序图和状态图的分析,整体的架构同第一次基本相同,添加的6个函数也比较简单。
首先是Tree中加入了新加入的类,并且新增一个Tree数组transitto
用于记录下当前状态可以直接转移到的其他状态(每个状态也是一个Tree节点)。
private ArrayList<UmlEndpoint> ep;
private ArrayList<UmlEvent> event;
private ArrayList<UmlInteraction> intr;
private ArrayList<UmlLifeline> ll;
private ArrayList<UmlMessage> meesage;
private ArrayList<UmlOpaqueBehavior> ob;
private String pd = "empty";
private String fss = "empty";
private ArrayList<UmlRegion> region;
private ArrayList<UmlState> state;
private ArrayList<UmlStateMachine> statem;
private ArrayList<UmlTransition> tsit;
private ArrayList<Tree> transitto;
新增的元素结构如下图:
同时由于新加入了6个函数,导致整体行数超过了500行,违背了OO的checkstyle规则,因此我新建了一个类Something
,并且将MyUmlInteraction
类中一些高度重复的操作封装成方法写入Something
类中,直接在MyUmlInteraction
类创建一个Something
类的元素并且调用相应方法即可,这样使MyUmlInteraction
类简洁了不少,并且行数也少了不少。
第二次作业bug分析
第二次作业由于功能实现比较简单,因此只自己随便出了几组数据测一测,同时采用肉眼观察的方法检查代码(因为逻辑相对简单)。最后在处理起始状态和终止状态的地方发生了bug,导致强测错了一个点,错误的原因是初始化错误,不小心把初始化的特殊值覆盖掉了,导致检查初始、终止状态是否为空时发生了错误。
第三次作业架构
第三次作业相比起的作业并没有新增元素,只是增加了8个检查所给数据是否符合规范的方法。
在整体架构上我新增了一个MyUmlStandardPreCheck
类来实现新增的方法,这样在MyUmlInteraction
类中就可以直接调用MyUmlStandardPreCheck
类里写好的方法,不会使本就臃肿的函数变得更加臃肿。
第三次作业bug分析
R008中将判断规则写成了>=1......并且不知为何自己出的数据和肉眼读代码都没有测出这个问题.....最后强测错了一个点。
四个单元中架构设计及OO方法理解的演进
第一单元:基本是面向过程,在第三次作业时对每个因子建立了一个类来方便实现递归,算是虚假的面向对象(因为主要的部分都还是面向过程写的)。每次写新的作业基本等于重新写一遍,毫无迭代性可言,是名副其实的“高耦合低内聚”。
第二单元:这一单元的架构是我四个的单元中最满意的了,每次写新的作业都是在旧的作业上增加新的内容,而不是重构,第一次的架构可以用在第二次,第二次的架构可以用在第三次,采用了生产者消费者模式,并且较好的实现了功能的分离,调度器、电梯、输入请求的三个函数各司其职,在实现高内聚低耦合的方面比第一单元好了很多。
第三单元:这一单元我做的比较偷懒,在第一二次作业基本都是照着规格走,这也导致了第二次作业的裂开。也是因此我彻底的理解了JML规格不等于代码的含义,开始重新设计存储容器,减少嵌套循环,尽可能地采用低时间复杂度的算法。虽然这个单元错的很惨,但是不得不说确实帮助我深刻的理解了规格的用途和意义。
第四单元:在未接触这个单元之前我对uml类图的理解仅限于博客要用,但是在完成了第四个单元的任务之后我才明白了uml类图、顺序图、状态图是如何构建的,其中元素的含义、元素之间的结构关系等等。
四个单元中测试理解与实践的演进
第一单元:python,永远滴神!直接利用python的库可以很方便的随机生成测试数据,并且还可以很方便的进行答案的验算,然后跑足够多的数据来进行检验。但是实际效果并不理想,比如第三单元就有没测出来的bug。
第二单元:利用python搭建自动测试机,包括生成随机数据、答案合法性检验,纯利用测评机进行测试。
第三单元:采用肉眼观察和对拍两种方法的结合,先肉眼检查检查代码,同时利用对拍器和同学进行对拍。
第四单元:肉眼检查代码+手搓数据检测。
最开始的时候我是过分的迷信测评机,觉得只要是随机并且数据量够大,就可以保证测试的充分,但是在后面发现其实光靠测评机并不靠谱。首先我们写测评机的水平可能不足,很可能出现生成的数据不够全面的情况,同时答案的检验也是一件十分麻烦的事情,并不是所有的情况都好写。而边界数据的测试也是十分重要的一环,很多特殊情况需要我们手搓数据进行测试,这其实实现起来很简单,并且非常的具有必要性。
课程收获
1、心态的调整。虽然如今我们的oo课程内卷的成分已经很少很少了,但是不得不说竞争给我带来的压力确实还是十分大的。有时候看见自己和大佬的差距过于遥远,会感到自闭,有时候看见题目时毫无头绪,会感到自闭,在经历了无数次的自闭过后,我的心态相比之前会好很多,能够及时的进行调整。
2、学会了写java。在接触oo之前我从未触碰过java语言,虽然说现在对java的了解也只是一些皮毛,但是至少是可以用java写出一些还不算短的程序了。
3、面向对象的思想。OO课教会我们最大的东西当然是面向对象的思想。在接触OO之前这种思想是我根本想都没有想过的,所有写代码的方法都是面向过程。
4、除此之外,代码能力也有相应的提升,并且对jml和uml都有了些许的了解。
具体改进建议
1、第一单元难度过高,作为入门单元而言令我们感到头秃以及恐惧,虽然有预习课程做铺垫,但是进入第一单元时还是感到难度陡增。也许还可以适当的增加预习课程的难度。总之希望预习课程到第一单元的过度能够更加平滑。
2、希望OO实验能够及时给出成绩反馈之类的。
3、希望每次作业可以请一些优秀同学进行及时的展示以及讲解,使得我们可以更好地完成该单元的剩余作业。
线上OO课程感想与体会
个人感觉线上OO和线下OO的差距应该不会很大,毕竟讨论什么的在线上进行也还是挺方便的,唯一的不足可能是线上听网课的时候效果会比线下上课差一点,主要原因还是我自己听网课容易走神。
OO课程真的是令人十分的难忘,这种难忘贯穿了整个学期。
第一单元,难,是真的难。看到作业要求的我都是懵的,完全不知道怎么写,并且我是从OO的预习课程才开始接触的java,对语法、特性、思想统统不熟悉,第一单元写出来的作业也几乎是面向过程完成的。记得在完成第一单元作业的时候,每周都要花上好几天的时间来完成代码,光是整理出一个靠谱的想法就要花上好几天,更别提写代码和debug的时间了。完成代码后进行测试也是十分的漫长,因为有时候为了优化,优化出来的bug还挺多的,特别是第三单元,直到最后都还是有隐藏的bug没被我找出来,而是被互测的同学和测评机发现。第一单元可算是OO课程给我的一个下马威,当时一周的大部分时间都交给了OO,切身的体会到了OO的魔鬼之处。
第二单元,玄,是真的玄。多线程,在刚开始接触的时候是感觉真的玄学,因此第二单元我花费了大量的时间去理解多线程的相关知识。第二单元号称是OO难度的巅峰,但是就实现方面而言,我觉得我完成的还不错。首先很幸运的是,我的代码架构没有出现群里那些同学无比玄学的多线程bug,从头到尾debug都还算比较顺利,可能是因为我采用的模型比较普通,同时也比较稳妥。第二单元实现不难,难的是实现一个好的电梯调度算法,我从头到尾选择的都是贪心,第一次作业贪心效果还不错,有99+分,第二、三次作业的时候这个算法的缓慢之处就暴露出来了,都只有98点几分。第二单元在这四个单元中可以算得上是我完成的最满意的一个单元了,也是唯一一个三次强测都没出bug的单元。
第三单元,惨,是真的惨。要问我OO作业哪一次最刻骨铭心,第三单元的第二次作业必须拥有姓名,这是我OO最严重的一次翻车。还记得查看成绩的那天心情平静,因为互测中我并没有被刀,但是看到成绩的20分让我彻底傻了眼,由于在一个不起眼的地方出现了一个不明显的循环嵌套,导致我TLE了80分。JML规格虽然看起来简单,但是时间复杂度的要求令第三单元暗藏杀机。其实整个第三单元的难度都不是很高(第三次写算法有点难),但是在实现的时候必须要有一个好的架构。是这次翻车让我深刻的认识到了JML规格和代码的分离,也深刻的认识到了单纯照着规格写是不行的。
第四单元,菜,是真的菜。我太菜了是第四单元唯一的感想。第四单元里面我成功达成了每一次强测都错一个点的成就,同时引发这些错误的都是一些极其弱智的bug,从定位到修服总共不超过2分钟。第四单元其实相对而言,只要理解了类图顺序图和状态图,写起来应该是最简单的一个单元,但是我却总是出现低级错误导致丢分,但是总结来说还是测试不够到位的缘故。最后一个单元的OO也算是给我了一个警醒,即使课程即将结束,我们也需要使用十分的精力来对待每一次作业。
OO课程即将结束,回想起来这一段不容易的旅途,我还是挺为我自己开心的,虽然有写作业完成的并不满意,有时候写代码写到痛苦的哀嚎,有时候debug de到深夜,但是也有完成代码过中测的喜悦,强测出成绩的“惊喜”,好歹是全部完成了并且没有进入补给站。非常感谢课程组的改革以及投入,将OO从一门人人吐槽的魔鬼课改成了一门人人叫好的魔鬼课。同时非常感谢老师们和助教们这一学期的辛苦付出,让我们在疫情期间也能够度过一个完整的OO课程。