面向对象第三单元总结
面向对象第三单元总结
第三单元的核心主题是有关JML规格相关内容的学习,主要解决的是工程中的设计正确性问题,实现形式化验证,以最为严谨的数学方法保证设计上的正确性。由于本单元的作业是迭代开发的,故以第三次作业作为分析样本。
实现规格所采取的设计策略
- 了解各部分之间大致架构,由易到难,由简入繁。在实现规格之前,我先大致浏览JML规格中各个部分大致完成了哪些任务,哪些部分需要使用其他部分。在具体实现的时候,我会把那些功能更为简单、更为单一的部分作为首先需要实现的部分。不仅如此,这些部分一般来说由于自身实现功能不多,所以一般是需要配合其他部分实现功能的,所以先实现这一部分的内容有助于我们实现更为复杂的部分。
- 局部专注实现。在JML规格实现当中,有的函数、方法的内容是比较复杂的,在实现过程当中需要连贯地针对这些内容进行思考和设计。即使其他需要协作的部分没有完成,也要先把这一局部完成好,再去补全那些内容,防止前后因为实现思路的不一样造成了衔接有缝隙,造成工程中出现隐性BUG。
基于JML规格设计的测试方法和策略
每次针对性测试一句JML规格,”控制变量“。在保证JML规格的设计正确性的前提下,想要检测程序具体实现是否完美实现了规格的设计,实际上可以从JML规格的每一项设计规格着手。例如规格要求要删除数据,同时保留原有数据。在测试的时候就可以先构造很多的测试集,先去测试程序是否正确删除了数据;然后再构造新的测试集去测试删除数据的时候,是否完美地保证了其他所有数据的正确保留。
容器选择和使用的经验
- 一般类中使用的是ArrayList。一直以来使用
List的次数和概率远高于Map,且List的结构在理解上来说要比Map更为直观,使用也就更为灵活。但是实际上在本单元作业中实现起来List的效率比Map要更低,因为本单元作业中增删查改的操作非常频繁,特别是查,尤其是Person和Group都还专门实现了一个查找函数来进行这一操作。在这一点上,List和Map的差距就非常明显了。但是在具体视线中,由于查找这一操作再次被封装了,所以实现规格的过程中,一直忽视了这方面的影响,所以也就没有想到要去改变List的数据结构,这是一大败笔。 - 异常类中使用的是HashMap。异常类中由于主要保存的数据是
id和次数这两块内容,这样的绑定式结构让我很自然地想要用HashMap去存储数据。遗憾的是我没有关注到不同容器具体使用上的性能区别,导致没有在一般类中也去使用HashMap。
性能问题及其原因
- 频繁的查找操作由于方法的封装,没有考虑到去优化底层的具体实现。在规格中,大量使用了
getPerson和getGroup的查找操作,我在具体实现时,也是直接去调用了这两个已经实现的方法,所以因此我在考虑性能问题时,没有考虑到这一已封装方法的其底下的具体实现的性能。在这两个方法的实现上,我使用的是遍历储存对象的容器,并将每一个对象的id和待查找的id进行比对,返回比对成功的对象。单独看这一方法的实现,潜意识始终只觉得查找一次的情况下,不同的数据结构带来的效果差距并不太明显。但实际上在反复调用的情况下双方有着天差地别。这是性能问题的其中一大原因。 - 在查找两个人之间是否连通和最短加权路径时总会反复查找重复元素。在这两个关键的查找算法中,总是会重复查找一些已经被查找过的元素,这会花费一定的时间。同时在像
qbs这样会反复大量查找元素之间关系的行为当中。有的元素之间的关系在之前的查找当中就已经可以被确定了,应当在能确定的时候将其保存下来,以此省略后面的查询操作。
作业架构设计
在本单元作业中,基本上我都是按照规格中各个类之间的关系来架构。设计上的关键点在于数据结构的确立。在图模型的建立上,我是用的是最普通的ArrayList结构来进行构建数据结构,但问题很大。规格要求的应当是一个图,而List构建的更是一个线形的结构,通过各个元素内置的次级List来实现途中元素的访问和关系的建立。这样的图在构建时访问效率不够高,因为访问效率主要依靠的还是主要依靠遍历的方法。不仅如此,如果方法实现不够合理,还会出现访问深度过深的问题。在维护策略上,我只是通过List中最普通的增删操作进行维护。同时我的维护是比较浅层次的消极维护策略,元素被添加的时候一定会被添加进结构中——然而在元素被删除时,则只是必须要删除的地方进行了删除,没有在其他不影响正确性但影响效率的地方进行改变。例如在Message对象中的删除元素操作中,我只会对必要的列表进行元素删除操作。这是性能提升需要改变的地方。

浙公网安备 33010602011771号