OO第三单元总结
设计策略
首先根据JML规格定义需要的属性,再理解各个方法的目的,最后根据理解来完成各个方法。对于方法的实现,需要先大致理解方法实现了怎样的功能,而不是直接根据规格就开始写代码,在明白方法规格的具体含义以后,要明确进入各个分支的前提条件,然后是每个分支造成了怎样的结果,最后是检查是否没有出现遗漏。在写代码的过程中,一般是先完成所有异常的抛出,再根据异常所避免的情况进行下一分支代码的实现。
测试策略
由于本单元中测数据极为简单,对于代码的评测极具迷惑性,导致不进行良好的测试可能导致强测翻车。并且,测试需要保证自己对规格的理解没有出现偏差,否则可能出现认为错误程序的输出正确的情况。在本单元的测试过程中,针对异常的测试只需要编写一些边界数据即可,针对方法功能的测试主要是确保测试数据全面,对于每种方法以及有关联性的方法组合都进行测试。在本单元的测试中只是编写了一些数据点来测试各方法的功能,且有几个细节上的错误没有测到。
容器选择和使用
在本单元作业中容器是比较多的,大部分选择了Map
类容器,用于id
与对象的对应,此外关于emoji
的容器为List
类,是因为规格规定了其通过两个List
进行id
与相应heat
值的对应。总之还是根据规格选择需要的容器,不匹配规格而去选择另外的容器可能导致代码不适合迭代。
例如,在queryNameRank
方法的实现过程中,我首先的想法是选择TreeMap
容器实现name
到MyPerson
的映射,并且只需要使用for
循环遍历即可通过TreeMap
的特性实现Rank
的计算,但是在具体测试过程中发现,虽然这样的做法在意义上与规格所要实现的功能相近,但是并不能很便利地完全符合规格,因此在容器的选择和使用上要以规格为准。
性能问题分析
本单元的显著特点就是需要进行大量属性的缓存,并设置相应的flag
,在与属性关联的方法未被调用时再询问该属性,直接返回缓存作为输出。只有这样才能避免频繁计算导致的性能过低问题。在实现过程中针对每个能够缓存的属性都进行了缓存,例如mean
,var
,qbs
,sim
等。
其中针对var
的计算需要注意/
运算,因此将公式展开,缓存平方和ageSquarSum
、n
个平均值平方和persons.size()*ageMean*ageMean
,和ageSum
,计算时ageVar=(ageSquarSum+meanSquar-2*ageMean*ageSum)/persons.size()
避免整除带来的bug。
针对sim
中由于计算最短路径采用的算法时间复杂度为O(n²)
,因此第三次强测有一个数据点性能过低,这是算法实现上的问题。
此外还可能存在的性能问题是qbs
和isCircle
的计算需要使用并查集,能够极大减少查询的时间消耗。通过在每个Person
对象中存储一个BiggestBrother
表示该成员的根成员,在每次addRelation
时遍历修改相关Person
的,对于一个Person
数组,遍历一遍即可知道qbs
的数量以及是否isCircle
。
架构设计
本单元的作业架构完全根据JML规格实现,将官方包提供的类或接口实例化,同时建立正确的继承或实现关系。但在实现过程中基本上没有自主建立的类之间的继承关系,实际实现上造成了代码冗余部分过多,复用处能够通过继承关系简化。
针对部分需要从Network
到Group
再到Person
进行询问的方法,在代码实现过程中尽量地一层层调用名称类似的方法,实现代码耦合降低。此外需要实现异常类的设计,针对本次异常类的实例化,需要定义static
属性来实现不同类抛出异常时都能够进行计数。总之,本单元的架构设计还是比较简洁。